See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 39 and 401] [Versions 401 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Private imscp module utility functions 19 * 20 * @package mod_imscp 21 * @copyright 2009 Petr Skoda {@link http://skodak.org} 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 defined('MOODLE_INTERNAL') || die(); 26 27 require_once("$CFG->dirroot/mod/imscp/lib.php"); 28 require_once("$CFG->libdir/filelib.php"); 29 require_once("$CFG->libdir/resourcelib.php"); 30 31 /** 32 * Print IMSCP content to page. 33 * 34 * @param stdClass $imscp module instance. 35 * @param stdClass $cm course module. 36 * @param stdClass $course record. 37 */ 38 function imscp_print_content($imscp, $cm, $course) { 39 global $PAGE, $CFG; 40 41 $items = array_filter((array) unserialize_array($imscp->structure)); 42 43 echo '<div id="imscp_layout">'; 44 echo '<div id="imscp_toc">'; 45 echo '<div id="imscp_tree"><ul>'; 46 foreach ($items as $item) { 47 echo imscp_htmllize_item($item, $imscp, $cm); 48 } 49 echo '</ul></div>'; 50 echo '<div id="imscp_nav" style="display:none">'; 51 echo '<button id="nav_skipprev"><<</button><button id="nav_prev"><</button><button id="nav_up">^</button>'; 52 echo '<button id="nav_next">></button><button id="nav_skipnext">>></button>'; 53 echo '</div>'; 54 echo '</div>'; 55 echo '</div>'; 56 57 $PAGE->requires->js_init_call('M.mod_imscp.init'); 58 } 59 60 /** 61 * Internal function - creates htmls structure suitable for YUI tree. 62 */ 63 function imscp_htmllize_item($item, $imscp, $cm) { 64 global $CFG; 65 66 if ($item['href']) { 67 if (preg_match('|^https?://|', $item['href'])) { 68 $url = $item['href']; 69 } else { 70 $context = context_module::instance($cm->id); 71 $urlbase = "$CFG->wwwroot/pluginfile.php"; 72 $path = '/'.$context->id.'/mod_imscp/content/'.$imscp->revision.'/'.$item['href']; 73 $url = file_encode_url($urlbase, $path, false); 74 } 75 $result = "<li><a href=\"$url\">".$item['title'].'</a>'; 76 } else { 77 $result = '<li>'.$item['title']; 78 } 79 if ($item['subitems']) { 80 $result .= '<ul>'; 81 foreach ($item['subitems'] as $subitem) { 82 $result .= imscp_htmllize_item($subitem, $imscp, $cm); 83 } 84 $result .= '</ul>'; 85 } 86 $result .= '</li>'; 87 88 return $result; 89 } 90 91 /** 92 * Parse an IMS content package's manifest file to determine its structure 93 * @param object $imscp 94 * @param object $context 95 * @return array 96 */ 97 function imscp_parse_structure($imscp, $context) { 98 $fs = get_file_storage(); 99 100 if (!$manifestfile = $fs->get_file($context->id, 'mod_imscp', 'content', $imscp->revision, '/', 'imsmanifest.xml')) { 101 return null; 102 } 103 104 return imscp_parse_manifestfile($manifestfile->get_content(), $imscp, $context); 105 } 106 107 /** 108 * Parse the contents of a IMS package's manifest file. 109 * @param string $manifestfilecontents the contents of the manifest file 110 * @return array 111 */ 112 function imscp_parse_manifestfile($manifestfilecontents, $imscp, $context) { 113 $doc = new DOMDocument(); 114 $oldentities = imscp_libxml_disable_entity_loader(true); 115 if (!$doc->loadXML($manifestfilecontents, LIBXML_NONET)) { 116 return null; 117 } 118 imscp_libxml_disable_entity_loader($oldentities); 119 120 // We put this fake URL as base in order to detect path changes caused by xml:base attributes. 121 $doc->documentURI = 'http://grrr/'; 122 123 $xmlorganizations = $doc->getElementsByTagName('organizations'); 124 if (empty($xmlorganizations->length)) { 125 return null; 126 } 127 $default = null; 128 if ($xmlorganizations->item(0)->attributes->getNamedItem('default')) { 129 $default = $xmlorganizations->item(0)->attributes->getNamedItem('default')->nodeValue; 130 } 131 $xmlorganization = $doc->getElementsByTagName('organization'); 132 if (empty($xmlorganization->length)) { 133 return null; 134 } 135 $organization = null; 136 foreach ($xmlorganization as $org) { 137 if (is_null($organization)) { 138 // Use first if default nor found. 139 $organization = $org; 140 } 141 if (!$org->attributes->getNamedItem('identifier')) { 142 continue; 143 } 144 if ($default === $org->attributes->getNamedItem('identifier')->nodeValue) { 145 // Found default - use it. 146 $organization = $org; 147 break; 148 } 149 } 150 151 // Load all resources. 152 $resources = array(); 153 154 $xmlresources = $doc->getElementsByTagName('resource'); 155 foreach ($xmlresources as $res) { 156 if (!$identifier = $res->attributes->getNamedItem('identifier')) { 157 continue; 158 } 159 $identifier = $identifier->nodeValue; 160 if ($xmlbase = $res->baseURI) { 161 // Undo the fake URL, we are interested in relative links only. 162 $xmlbase = str_replace('http://grrr/', '/', $xmlbase); 163 $xmlbase = rtrim($xmlbase, '/').'/'; 164 } else { 165 $xmlbase = ''; 166 } 167 if (!$href = $res->attributes->getNamedItem('href')) { 168 // If href not found look for <file href="help.htm"/>. 169 $fileresources = $res->getElementsByTagName('file'); 170 foreach ($fileresources as $file) { 171 $href = $file->getAttribute('href'); 172 } 173 if (pathinfo($href, PATHINFO_EXTENSION) == 'xml') { 174 $href = imscp_recursive_href($href, $imscp, $context); 175 } 176 if (empty($href)) { 177 continue; 178 } 179 } else { 180 $href = $href->nodeValue; 181 } 182 if (strpos($href, 'http://') !== 0) { 183 $href = $xmlbase.$href; 184 } 185 // Item href cleanup - Some packages are poorly done and use \ in urls. 186 $href = ltrim(strtr($href, "\\", '/'), '/'); 187 $resources[$identifier] = $href; 188 } 189 190 $items = array(); 191 foreach ($organization->childNodes as $child) { 192 if ($child->nodeName === 'item') { 193 if (!$item = imscp_recursive_item($child, 0, $resources)) { 194 continue; 195 } 196 $items[] = $item; 197 } 198 } 199 200 return $items; 201 } 202 203 function imscp_recursive_href($manifestfilename, $imscp, $context) { 204 $fs = get_file_storage(); 205 206 $dirname = dirname($manifestfilename); 207 $filename = basename($manifestfilename); 208 209 if ($dirname !== '/') { 210 $dirname = "/$dirname/"; 211 } 212 213 if (!$manifestfile = $fs->get_file($context->id, 'mod_imscp', 'content', $imscp->revision, $dirname, $filename)) { 214 return null; 215 } 216 217 $doc = new DOMDocument(); 218 $oldentities = imscp_libxml_disable_entity_loader(true); 219 if (!$doc->loadXML($manifestfile->get_content(), LIBXML_NONET)) { 220 return null; 221 } 222 imscp_libxml_disable_entity_loader($oldentities); 223 224 $xmlresources = $doc->getElementsByTagName('resource'); 225 foreach ($xmlresources as $res) { 226 if (!$href = $res->attributes->getNamedItem('href')) { 227 $fileresources = $res->getElementsByTagName('file'); 228 foreach ($fileresources as $file) { 229 $href = $file->getAttribute('href'); 230 if (pathinfo($href, PATHINFO_EXTENSION) == 'xml') { 231 $href = imscp_recursive_href($href, $imscp, $context); 232 } 233 234 if (pathinfo($href, PATHINFO_EXTENSION) == 'htm' || pathinfo($href, PATHINFO_EXTENSION) == 'html') { 235 return $href; 236 } 237 } 238 } 239 } 240 241 return $manifestfilename; 242 } 243 244 function imscp_recursive_item($xmlitem, $level, $resources) { 245 $identifierref = ''; 246 if ($identifierref = $xmlitem->attributes->getNamedItem('identifierref')) { 247 $identifierref = $identifierref->nodeValue; 248 } 249 250 $title = '?'; 251 $subitems = array(); 252 253 foreach ($xmlitem->childNodes as $child) { 254 if ($child->nodeName === 'title') { 255 $title = $child->textContent; 256 257 } else if ($child->nodeName === 'item') { 258 if ($subitem = imscp_recursive_item($child, $level + 1, $resources)) { 259 $subitems[] = $subitem; 260 } 261 } 262 } 263 264 return array('href' => isset($resources[$identifierref]) ? $resources[$identifierref] : '', 265 'title' => $title, 266 'level' => $level, 267 'subitems' => $subitems, 268 ); 269 } 270 271 /** 272 * Wrapper for function libxml_disable_entity_loader() deprecated in PHP 8 273 * 274 * Method was deprecated in PHP 8 and it shows deprecation message. However it is still 275 * required in the previous versions on PHP. While Moodle supports both PHP 7 and 8 we need to keep it. 276 * @see https://php.watch/versions/8.0/libxml_disable_entity_loader-deprecation 277 * 278 * @param bool $value 279 * @return bool 280 */ 281 function imscp_libxml_disable_entity_loader(bool $value): bool { 282 if (PHP_VERSION_ID < 80000) { 283 return (bool)libxml_disable_entity_loader($value); 284 } 285 return true; 286 } 287 288 /** 289 * File browsing support class 290 * 291 * @copyright 2009 Petr Skoda {@link http://skodak.org} 292 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 293 */ 294 class imscp_file_info extends file_info { 295 protected $course; 296 protected $cm; 297 protected $areas; 298 protected $filearea; 299 300 public function __construct($browser, $course, $cm, $context, $areas, $filearea) { 301 parent::__construct($browser, $context); 302 $this->course = $course; 303 $this->cm = $cm; 304 $this->areas = $areas; 305 $this->filearea = $filearea; 306 } 307 308 /** 309 * Returns list of standard virtual file/directory identification. 310 * The difference from stored_file parameters is that null values 311 * are allowed in all fields 312 * @return array with keys contextid, filearea, itemid, filepath and filename 313 */ 314 public function get_params() { 315 return array('contextid' => $this->context->id, 316 'component' => 'mod_imscp', 317 'filearea' => $this->filearea, 318 'itemid' => null, 319 'filepath' => null, 320 'filename' => null); 321 } 322 323 /** 324 * Returns localised visible name. 325 * @return string 326 */ 327 public function get_visible_name() { 328 return $this->areas[$this->filearea]; 329 } 330 331 /** 332 * Can I add new files or directories? 333 * @return bool 334 */ 335 public function is_writable() { 336 return false; 337 } 338 339 /** 340 * Is directory? 341 * @return bool 342 */ 343 public function is_directory() { 344 return true; 345 } 346 347 /** 348 * Returns list of children. 349 * @return array of file_info instances 350 */ 351 public function get_children() { 352 return $this->get_filtered_children('*', false, true); 353 } 354 355 /** 356 * Help function to return files matching extensions or their count 357 * 358 * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg') 359 * @param bool|int $countonly if false returns the children, if an int returns just the 360 * count of children but stops counting when $countonly number of children is reached 361 * @param bool $returnemptyfolders if true returns items that don't have matching files inside 362 * @return array|int array of file_info instances or the count 363 */ 364 private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) { 365 global $DB; 366 $params = array('contextid' => $this->context->id, 367 'component' => 'mod_imscp', 368 'filearea' => $this->filearea); 369 $sql = 'SELECT DISTINCT itemid 370 FROM {files} 371 WHERE contextid = :contextid 372 AND component = :component 373 AND filearea = :filearea'; 374 if (!$returnemptyfolders) { 375 $sql .= ' AND filename <> :emptyfilename'; 376 $params['emptyfilename'] = '.'; 377 } 378 list($sql2, $params2) = $this->build_search_files_sql($extensions); 379 $sql .= ' '.$sql2; 380 $params = array_merge($params, $params2); 381 if ($countonly !== false) { 382 $sql .= ' ORDER BY itemid'; 383 } 384 385 $rs = $DB->get_recordset_sql($sql, $params); 386 $children = array(); 387 foreach ($rs as $record) { 388 if ($child = $this->browser->get_file_info($this->context, 'mod_imscp', $this->filearea, $record->itemid)) { 389 $children[] = $child; 390 if ($countonly !== false && count($children) >= $countonly) { 391 break; 392 } 393 } 394 } 395 $rs->close(); 396 if ($countonly !== false) { 397 return count($children); 398 } 399 return $children; 400 } 401 402 /** 403 * Returns list of children which are either files matching the specified extensions 404 * or folders that contain at least one such file. 405 * 406 * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg') 407 * @return array of file_info instances 408 */ 409 public function get_non_empty_children($extensions = '*') { 410 return $this->get_filtered_children($extensions, false); 411 } 412 413 /** 414 * Returns the number of children which are either files matching the specified extensions 415 * or folders containing at least one such file. 416 * 417 * @param string|array $extensions, for example '*' or array('.gif','.jpg') 418 * @param int $limit stop counting after at least $limit non-empty children are found 419 * @return int 420 */ 421 public function count_non_empty_children($extensions = '*', $limit = 1) { 422 return $this->get_filtered_children($extensions, $limit); 423 } 424 425 /** 426 * Returns parent file_info instance 427 * @return file_info or null for root 428 */ 429 public function get_parent() { 430 return $this->browser->get_file_info($this->context); 431 } 432 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body