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 * @package backup-convert 19 * @subpackage cc-library 20 * @copyright 2011 Darko Miletic <dmiletic@moodlerooms.com> 21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 */ 23 24 require_once ('xmlbase.php'); 25 26 /** 27 * 28 * Various helper utils 29 * @author Darko Miletic dmiletic@moodlerooms.com 30 * 31 */ 32 abstract class cc_helpers { 33 34 /** 35 * Checks extension of the supplied filename 36 * 37 * @param string $filename 38 */ 39 public static function is_html($filename) { 40 $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); 41 return in_array($extension, array('htm', 'html')); 42 } 43 44 /** 45 * Generates unique identifier 46 * @param string $prefix 47 * @param string $suffix 48 * @return string 49 */ 50 public static function uuidgen($prefix = '', $suffix = '', $uppercase = true) { 51 $uuid = trim(sprintf('%s%04x%04x%s', $prefix, mt_rand(0, 65535), mt_rand(0, 65535), $suffix)); 52 $result = $uppercase ? strtoupper($uuid) : strtolower($uuid); 53 return $result; 54 } 55 56 /** 57 * Creates new folder with random name 58 * @param string $where 59 * @param string $prefix 60 * @param string $suffix 61 * @return mixed - directory short name or false in case of failure 62 */ 63 public static function randomdir($where, $prefix = '', $suffix = '') { 64 global $CFG; 65 66 $dirname = false; 67 $randomname = self::uuidgen($prefix, $suffix, false); 68 $newdirname = $where.DIRECTORY_SEPARATOR.$randomname; 69 if (mkdir($newdirname)) { 70 chmod($newdirname, $CFG->directorypermissions); 71 $dirname = $randomname; 72 } 73 return $dirname; 74 } 75 76 public static function build_query($attributes, $search) { 77 $result = ''; 78 foreach ($attributes as $attribute) { 79 if ($result != '') { 80 $result .= ' | '; 81 } 82 $result .= "//*[starts-with(@{$attribute},'{$search}')]/@{$attribute}"; 83 } 84 return $result; 85 } 86 87 public static function process_embedded_files(&$doc, $attributes, $search, $customslash = null) { 88 $result = array(); 89 $query = self::build_query($attributes, $search); 90 $list = $doc->nodeList($query); 91 foreach ($list as $filelink) { 92 $rvalue = str_replace($search, '', $filelink->nodeValue); 93 if (!empty($customslash)) { 94 $rvalue = str_replace($customslash, '/', $rvalue); 95 } 96 $result[] = rawurldecode($rvalue); 97 } 98 return $result; 99 } 100 101 /** 102 * 103 * Get list of embedded files 104 * @param string $html 105 * @return multitype:mixed 106 */ 107 public static function embedded_files($html) { 108 $result = array(); 109 $doc = new XMLGenericDocument(); 110 $doc->doc->validateOnParse = false; 111 $doc->doc->strictErrorChecking = false; 112 if (!empty($html) && $doc->loadHTML($html)) { 113 $attributes = array('src', 'href'); 114 $result1 = self::process_embedded_files($doc, $attributes, '@@PLUGINFILE@@'); 115 $result2 = self::process_embedded_files($doc, $attributes, '$@FILEPHP@$', '$@SLASH@$'); 116 $result = array_merge($result1, $result2); 117 } 118 return $result; 119 } 120 121 public static function embedded_mapping($packageroot, $contextid = null) { 122 $main_file = $packageroot . DIRECTORY_SEPARATOR . 'files.xml'; 123 $mfile = new XMLGenericDocument(); 124 if (!$mfile->load($main_file)) { 125 return false; 126 } 127 $query = "/files/file[filename!='.']"; 128 if (!empty($contextid)) { 129 $query .= "[contextid='{$contextid}']"; 130 } 131 $files = $mfile->nodeList($query); 132 $depfiles = array(); 133 foreach ($files as $node) { 134 $mainfile = intval($mfile->nodeValue('sortorder', $node)); 135 $filename = $mfile->nodeValue('filename', $node); 136 $filepath = $mfile->nodeValue('filepath', $node); 137 $source = $mfile->nodeValue('source', $node); 138 $author = $mfile->nodeValue('author', $node); 139 $license = $mfile->nodeValue('license', $node); 140 $hashedname = $mfile->nodeValue('contenthash', $node); 141 $hashpart = substr($hashedname, 0, 2); 142 $location = 'files'.DIRECTORY_SEPARATOR.$hashpart.DIRECTORY_SEPARATOR.$hashedname; 143 $type = $mfile->nodeValue('mimetype', $node); 144 $depfiles[$filepath.$filename] = array( $location, 145 ($mainfile == 1), 146 strtolower(str_replace(' ', '_', $filename)), 147 $type, 148 $source, 149 $author, 150 $license, 151 strtolower(str_replace(' ', '_', $filepath))); 152 } 153 154 return $depfiles; 155 } 156 157 public static function add_files(cc_i_manifest &$manifest, $packageroot, $outdir, $allinone = true) { 158 global $CFG; 159 160 if (pkg_static_resources::instance()->finished) { 161 return; 162 } 163 $files = cc_helpers::embedded_mapping($packageroot); 164 $rdir = $allinone ? new cc_resource_location($outdir) : null; 165 foreach ($files as $virtual => $values) { 166 $clean_filename = $values[2]; 167 if (!$allinone) { 168 $rdir = new cc_resource_location($outdir); 169 } 170 $rtp = $rdir->fullpath().$values[7].$clean_filename; 171 //Are there any relative virtual directories? 172 //let us try to recreate them 173 $justdir = $rdir->fullpath(false).$values[7]; 174 if (!file_exists($justdir)) { 175 if (!mkdir($justdir, $CFG->directorypermissions, true)) { 176 throw new RuntimeException('Unable to create directories!'); 177 } 178 } 179 180 $source = $packageroot.DIRECTORY_SEPARATOR.$values[0]; 181 if (!copy($source, $rtp)) { 182 throw new RuntimeException('Unable to copy files!'); 183 } 184 $resource = new cc_resource($rdir->rootdir(), 185 $values[7].$clean_filename, 186 $rdir->dirname(false)); 187 $res = $manifest->add_resource($resource, null, cc_version11::webcontent); 188 pkg_static_resources::instance()->add($virtual, 189 $res[0], 190 $rdir->dirname(false).$values[7].$clean_filename, 191 $values[1], 192 $resource); 193 } 194 195 pkg_static_resources::instance()->finished = true; 196 } 197 198 /** 199 * 200 * Excerpt from IMS CC 1.1 overview : 201 * No spaces in filenames, directory and file references should 202 * employ all lowercase or all uppercase - no mixed case 203 * 204 * @param cc_i_manifest $manifest 205 * @param string $packageroot 206 * @param integer $contextid 207 * @param string $outdir 208 * @param boolean $allinone 209 * @throws RuntimeException 210 */ 211 public static function handle_static_content(cc_i_manifest &$manifest, $packageroot, $contextid, $outdir, $allinone = true) { 212 self::add_files($manifest, $packageroot, $outdir, $allinone); 213 return pkg_static_resources::instance()->get_values(); 214 } 215 216 public static function handle_resource_content(cc_i_manifest &$manifest, $packageroot, $contextid, $outdir, $allinone = true) { 217 $result = array(); 218 self::add_files($manifest, $packageroot, $outdir, $allinone); 219 $files = self::embedded_mapping($packageroot, $contextid); 220 $rootnode = null; 221 $rootvals = null; 222 $depfiles = array(); 223 $depres = array(); 224 $flocation = null; 225 foreach ($files as $virtual => $values) { 226 $vals = pkg_static_resources::instance()->get_identifier($virtual); 227 $resource = $vals[3]; 228 $identifier = $resource->identifier; 229 $flocation = $vals[1]; 230 if ($values[1]) { 231 $rootnode = $resource; 232 $rootvals = $flocation; 233 continue; 234 } 235 236 $depres[] = $identifier; 237 $depfiles[] = $vals[1]; 238 $result[$virtual] = array($identifier, $flocation, false); 239 } 240 241 if (!empty($rootnode)) { 242 $rootnode->files = array_merge($rootnode->files, $depfiles); 243 $result[$virtual] = array($rootnode->identifier, $rootvals, true); 244 } 245 246 return $result; 247 } 248 249 public static function process_linked_files($content, cc_i_manifest &$manifest, $packageroot, 250 $contextid, $outdir, $webcontent = false) { 251 // Detect all embedded files 252 // locate their physical counterparts in moodle 2 backup 253 // copy all files in the cc package stripping any spaces and using only lowercase letters 254 // add those files as resources of the type webcontent to the manifest 255 // replace the links to the resource using $IMS-CC-FILEBASE$ and their new locations 256 // cc_resource has array of files and array of dependencies 257 // most likely we would need to add all files as independent resources and than 258 // attach them all as dependencies to the forum tag. 259 $lfiles = self::embedded_files($content); 260 $text = $content; 261 $deps = array(); 262 if (!empty($lfiles)) { 263 $files = self::handle_static_content($manifest, 264 $packageroot, 265 $contextid, 266 $outdir); 267 $replaceprefix = $webcontent ? '' : '$IMS-CC-FILEBASE$'; 268 foreach ($lfiles as $lfile) { 269 if (isset($files[$lfile])) { 270 $filename = str_replace('%2F', '/', rawurlencode($lfile)); 271 $content = str_replace('@@PLUGINFILE@@'.$filename, 272 $replaceprefix.'../'.$files[$lfile][1], 273 $content); 274 // For the legacy stuff. 275 $content = str_replace('$@FILEPHP@$'.str_replace('/', '$@SLASH@$', $filename), 276 $replaceprefix.'../'.$files[$lfile][1], 277 $content); 278 $deps[] = $files[$lfile][0]; 279 } 280 } 281 $text = $content; 282 } 283 return array($text, $deps); 284 } 285 286 public static function relative_location($originpath, $linkingpath) { 287 return false; 288 } 289 290 } 291 292 293 final class cc_resource_location { 294 /** 295 * 296 * Root directory 297 * @var string 298 */ 299 private $rootdir = null; 300 /** 301 * 302 * new directory 303 * @var string 304 */ 305 private $dir = null; 306 /** 307 * 308 * Full precalculated path 309 * @var string 310 */ 311 private $fullpath = null; 312 313 /** 314 * 315 * ctor 316 * @param string $rootdir - path to the containing directory 317 * @throws InvalidArgumentException 318 * @throws RuntimeException 319 */ 320 public function __construct($rootdir) { 321 $rdir = realpath($rootdir); 322 if (empty($rdir)) { 323 throw new InvalidArgumentException('Invalid path!'); 324 } 325 $dir = cc_helpers::randomdir($rdir, 'i_'); 326 if ($dir === false) { 327 throw new RuntimeException('Unable to create directory!'); 328 } 329 $this->rootdir = $rdir; 330 $this->dir = $dir; 331 $this->fullpath = $rdir.DIRECTORY_SEPARATOR.$dir; 332 } 333 334 /** 335 * 336 * Newly created directory 337 * @return string 338 */ 339 public function dirname($endseparator=false) { 340 return $this->dir.($endseparator ? '/' : ''); 341 } 342 343 /** 344 * 345 * Full path to the new directory 346 * @return string 347 */ 348 public function fullpath($endseparator=false) { 349 return $this->fullpath.($endseparator ? DIRECTORY_SEPARATOR : ''); 350 } 351 352 /** 353 * Returns containing dir 354 * @return string 355 */ 356 public function rootdir($endseparator=false) { 357 return $this->rootdir.($endseparator ? DIRECTORY_SEPARATOR : ''); 358 } 359 } 360 361 class pkg_static_resources { 362 363 /** 364 * @var array 365 */ 366 private $values = array(); 367 368 /** 369 * @var boolean 370 */ 371 public $finished = false; 372 373 /** 374 * @var pkg_static_resources 375 */ 376 private static $instance = null; 377 378 private function __clone() { 379 } 380 381 private function __construct() { 382 } 383 384 /** 385 * @return pkg_static_resources 386 */ 387 public static function instance() { 388 if (empty(self::$instance)) { 389 $c = __CLASS__; 390 self::$instance = new $c(); 391 } 392 return self::$instance; 393 } 394 395 /** 396 * 397 * add new element 398 * @param string $identifier 399 * @param string $file 400 * @param boolean $main 401 */ 402 public function add($key, $identifier, $file, $main, $node = null) { 403 $this->values[$key] = array($identifier, $file, $main, $node); 404 } 405 406 /** 407 * @return array 408 */ 409 public function get_values() { 410 return $this->values; 411 } 412 413 public function get_identifier($location) { 414 return isset($this->values[$location]) ? $this->values[$location] : false; 415 } 416 417 public function reset() { 418 $this->values = array(); 419 $this->finished = false; 420 } 421 } 422 423 424 class pkg_resource_dependencies { 425 /** 426 * @var array 427 */ 428 private $values = array(); 429 430 /** 431 * @var pkg_resource_dependencies 432 */ 433 private static $instance = null; 434 435 private function __clone() { 436 } 437 private function __construct() { 438 } 439 440 /** 441 * @return pkg_resource_dependencies 442 */ 443 public static function instance() { 444 if (empty(self::$instance)) { 445 $c = __CLASS__; 446 self::$instance = new $c(); 447 } 448 return self::$instance; 449 } 450 451 /** 452 * @param array $deps 453 */ 454 public function add(array $deps) { 455 $this->values = array_merge($this->values, $deps); 456 } 457 458 public function reset() { 459 $this->values = array(); 460 } 461 462 /** 463 * @return array 464 */ 465 public function get_deps() { 466 return $this->values; 467 } 468 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body