Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 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 if (!$doc->loadXML($manifestfilecontents, LIBXML_NONET)) { 115 return null; 116 } 117 118 // We put this fake URL as base in order to detect path changes caused by xml:base attributes. 119 $doc->documentURI = 'http://grrr/'; 120 121 $xmlorganizations = $doc->getElementsByTagName('organizations'); 122 if (empty($xmlorganizations->length)) { 123 return null; 124 } 125 $default = null; 126 if ($xmlorganizations->item(0)->attributes->getNamedItem('default')) { 127 $default = $xmlorganizations->item(0)->attributes->getNamedItem('default')->nodeValue; 128 } 129 $xmlorganization = $doc->getElementsByTagName('organization'); 130 if (empty($xmlorganization->length)) { 131 return null; 132 } 133 $organization = null; 134 foreach ($xmlorganization as $org) { 135 if (is_null($organization)) { 136 // Use first if default nor found. 137 $organization = $org; 138 } 139 if (!$org->attributes->getNamedItem('identifier')) { 140 continue; 141 } 142 if ($default === $org->attributes->getNamedItem('identifier')->nodeValue) { 143 // Found default - use it. 144 $organization = $org; 145 break; 146 } 147 } 148 149 // Load all resources. 150 $resources = array(); 151 152 $xmlresources = $doc->getElementsByTagName('resource'); 153 foreach ($xmlresources as $res) { 154 if (!$identifier = $res->attributes->getNamedItem('identifier')) { 155 continue; 156 } 157 $identifier = $identifier->nodeValue; 158 if ($xmlbase = $res->baseURI) { 159 // Undo the fake URL, we are interested in relative links only. 160 $xmlbase = str_replace('http://grrr/', '/', $xmlbase); 161 $xmlbase = rtrim($xmlbase, '/').'/'; 162 } else { 163 $xmlbase = ''; 164 } 165 if (!$href = $res->attributes->getNamedItem('href')) { 166 // If href not found look for <file href="help.htm"/>. 167 $fileresources = $res->getElementsByTagName('file'); 168 foreach ($fileresources as $file) { 169 $href = $file->getAttribute('href'); 170 } 171 if (pathinfo($href, PATHINFO_EXTENSION) == 'xml') { 172 $href = imscp_recursive_href($href, $imscp, $context); 173 } 174 if (empty($href)) { 175 continue; 176 } 177 } else { 178 $href = $href->nodeValue; 179 } 180 if (strpos($href, 'http://') !== 0) { 181 $href = $xmlbase.$href; 182 } 183 // Item href cleanup - Some packages are poorly done and use \ in urls. 184 $href = ltrim(strtr($href, "\\", '/'), '/'); 185 $resources[$identifier] = $href; 186 } 187 188 $items = array(); 189 foreach ($organization->childNodes as $child) { 190 if ($child->nodeName === 'item') { 191 if (!$item = imscp_recursive_item($child, 0, $resources)) { 192 continue; 193 } 194 $items[] = $item; 195 } 196 } 197 198 return $items; 199 } 200 201 function imscp_recursive_href($manifestfilename, $imscp, $context) { 202 $fs = get_file_storage(); 203 204 $dirname = dirname($manifestfilename); 205 $filename = basename($manifestfilename); 206 207 if ($dirname !== '/') { 208 $dirname = "/$dirname/"; 209 } 210 211 if (!$manifestfile = $fs->get_file($context->id, 'mod_imscp', 'content', $imscp->revision, $dirname, $filename)) { 212 return null; 213 } 214 215 $doc = new DOMDocument(); 216 if (!$doc->loadXML($manifestfile->get_content(), LIBXML_NONET)) { 217 return null; 218 } 219 220 $xmlresources = $doc->getElementsByTagName('resource'); 221 foreach ($xmlresources as $res) { 222 if (!$href = $res->attributes->getNamedItem('href')) { 223 $fileresources = $res->getElementsByTagName('file'); 224 foreach ($fileresources as $file) { 225 $href = $file->getAttribute('href'); 226 if (pathinfo($href, PATHINFO_EXTENSION) == 'xml') { 227 $href = imscp_recursive_href($href, $imscp, $context); 228 } 229 230 if (pathinfo($href, PATHINFO_EXTENSION) == 'htm' || pathinfo($href, PATHINFO_EXTENSION) == 'html') { 231 return $href; 232 } 233 } 234 } 235 } 236 237 return $manifestfilename; 238 } 239 240 function imscp_recursive_item($xmlitem, $level, $resources) { 241 $identifierref = ''; 242 if ($identifierref = $xmlitem->attributes->getNamedItem('identifierref')) { 243 $identifierref = $identifierref->nodeValue; 244 } 245 246 $title = '?'; 247 $subitems = array(); 248 249 foreach ($xmlitem->childNodes as $child) { 250 if ($child->nodeName === 'title') { 251 $title = $child->textContent; 252 253 } else if ($child->nodeName === 'item') { 254 if ($subitem = imscp_recursive_item($child, $level + 1, $resources)) { 255 $subitems[] = $subitem; 256 } 257 } 258 } 259 260 return array('href' => isset($resources[$identifierref]) ? $resources[$identifierref] : '', 261 'title' => $title, 262 'level' => $level, 263 'subitems' => $subitems, 264 ); 265 } 266 267 /** 268 * Wrapper for function libxml_disable_entity_loader() deprecated in PHP 8 269 * 270 * Method was deprecated in PHP 8 and it shows deprecation message. However it is still 271 * required in the previous versions on PHP. While Moodle supports both PHP 7 and 8 we need to keep it. 272 * @see https://php.watch/versions/8.0/libxml_disable_entity_loader-deprecation 273 * 274 * @param bool $value 275 * @return bool 276 * 277 * @deprecated since Moodle 4.3 278 */ 279 function imscp_libxml_disable_entity_loader(bool $value): bool { 280 debugging(__FUNCTION__ . '() is deprecated, please do not use it any more', DEBUG_DEVELOPER); 281 return true; 282 } 283 284 /** 285 * File browsing support class 286 * 287 * @copyright 2009 Petr Skoda {@link http://skodak.org} 288 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 289 */ 290 class imscp_file_info extends file_info { 291 protected $course; 292 protected $cm; 293 protected $areas; 294 protected $filearea; 295 296 public function __construct($browser, $course, $cm, $context, $areas, $filearea) { 297 parent::__construct($browser, $context); 298 $this->course = $course; 299 $this->cm = $cm; 300 $this->areas = $areas; 301 $this->filearea = $filearea; 302 } 303 304 /** 305 * Returns list of standard virtual file/directory identification. 306 * The difference from stored_file parameters is that null values 307 * are allowed in all fields 308 * @return array with keys contextid, filearea, itemid, filepath and filename 309 */ 310 public function get_params() { 311 return array('contextid' => $this->context->id, 312 'component' => 'mod_imscp', 313 'filearea' => $this->filearea, 314 'itemid' => null, 315 'filepath' => null, 316 'filename' => null); 317 } 318 319 /** 320 * Returns localised visible name. 321 * @return string 322 */ 323 public function get_visible_name() { 324 return $this->areas[$this->filearea]; 325 } 326 327 /** 328 * Can I add new files or directories? 329 * @return bool 330 */ 331 public function is_writable() { 332 return false; 333 } 334 335 /** 336 * Is directory? 337 * @return bool 338 */ 339 public function is_directory() { 340 return true; 341 } 342 343 /** 344 * Returns list of children. 345 * @return array of file_info instances 346 */ 347 public function get_children() { 348 return $this->get_filtered_children('*', false, true); 349 } 350 351 /** 352 * Help function to return files matching extensions or their count 353 * 354 * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg') 355 * @param bool|int $countonly if false returns the children, if an int returns just the 356 * count of children but stops counting when $countonly number of children is reached 357 * @param bool $returnemptyfolders if true returns items that don't have matching files inside 358 * @return array|int array of file_info instances or the count 359 */ 360 private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) { 361 global $DB; 362 $params = array('contextid' => $this->context->id, 363 'component' => 'mod_imscp', 364 'filearea' => $this->filearea); 365 $sql = 'SELECT DISTINCT itemid 366 FROM {files} 367 WHERE contextid = :contextid 368 AND component = :component 369 AND filearea = :filearea'; 370 if (!$returnemptyfolders) { 371 $sql .= ' AND filename <> :emptyfilename'; 372 $params['emptyfilename'] = '.'; 373 } 374 list($sql2, $params2) = $this->build_search_files_sql($extensions); 375 $sql .= ' '.$sql2; 376 $params = array_merge($params, $params2); 377 if ($countonly !== false) { 378 $sql .= ' ORDER BY itemid'; 379 } 380 381 $rs = $DB->get_recordset_sql($sql, $params); 382 $children = array(); 383 foreach ($rs as $record) { 384 if ($child = $this->browser->get_file_info($this->context, 'mod_imscp', $this->filearea, $record->itemid)) { 385 $children[] = $child; 386 if ($countonly !== false && count($children) >= $countonly) { 387 break; 388 } 389 } 390 } 391 $rs->close(); 392 if ($countonly !== false) { 393 return count($children); 394 } 395 return $children; 396 } 397 398 /** 399 * Returns list of children which are either files matching the specified extensions 400 * or folders that contain at least one such file. 401 * 402 * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg') 403 * @return array of file_info instances 404 */ 405 public function get_non_empty_children($extensions = '*') { 406 return $this->get_filtered_children($extensions, false); 407 } 408 409 /** 410 * Returns the number of children which are either files matching the specified extensions 411 * or folders containing at least one such file. 412 * 413 * @param string|array $extensions, for example '*' or array('.gif','.jpg') 414 * @param int $limit stop counting after at least $limit non-empty children are found 415 * @return int 416 */ 417 public function count_non_empty_children($extensions = '*', $limit = 1) { 418 return $this->get_filtered_children($extensions, $limit); 419 } 420 421 /** 422 * Returns parent file_info instance 423 * @return file_info or null for root 424 */ 425 public function get_parent() { 426 return $this->browser->get_file_info($this->context); 427 } 428 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body