[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Resource loader module based on local JavaScript/CSS files. 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License along 16 * with this program; if not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 * http://www.gnu.org/copyleft/gpl.html 19 * 20 * @file 21 * @author Trevor Parscal 22 * @author Roan Kattouw 23 */ 24 25 /** 26 * ResourceLoader module based on local JavaScript/CSS files. 27 */ 28 class ResourceLoaderFileModule extends ResourceLoaderModule { 29 /* Protected Members */ 30 31 /** @var string Local base path, see __construct() */ 32 protected $localBasePath = ''; 33 34 /** @var string Remote base path, see __construct() */ 35 protected $remoteBasePath = ''; 36 37 /** 38 * @var array List of paths to JavaScript files to always include 39 * @par Usage: 40 * @code 41 * array( [file-path], [file-path], ... ) 42 * @endcode 43 */ 44 protected $scripts = array(); 45 46 /** 47 * @var array List of JavaScript files to include when using a specific language 48 * @par Usage: 49 * @code 50 * array( [language-code] => array( [file-path], [file-path], ... ), ... ) 51 * @endcode 52 */ 53 protected $languageScripts = array(); 54 55 /** 56 * @var array List of JavaScript files to include when using a specific skin 57 * @par Usage: 58 * @code 59 * array( [skin-name] => array( [file-path], [file-path], ... ), ... ) 60 * @endcode 61 */ 62 protected $skinScripts = array(); 63 64 /** 65 * @var array List of paths to JavaScript files to include in debug mode 66 * @par Usage: 67 * @code 68 * array( [skin-name] => array( [file-path], [file-path], ... ), ... ) 69 * @endcode 70 */ 71 protected $debugScripts = array(); 72 73 /** 74 * @var array List of paths to JavaScript files to include in the startup module 75 * @par Usage: 76 * @code 77 * array( [file-path], [file-path], ... ) 78 * @endcode 79 */ 80 protected $loaderScripts = array(); 81 82 /** 83 * @var array List of paths to CSS files to always include 84 * @par Usage: 85 * @code 86 * array( [file-path], [file-path], ... ) 87 * @endcode 88 */ 89 protected $styles = array(); 90 91 /** 92 * @var array List of paths to CSS files to include when using specific skins 93 * @par Usage: 94 * @code 95 * array( [file-path], [file-path], ... ) 96 * @endcode 97 */ 98 protected $skinStyles = array(); 99 100 /** 101 * @var array List of modules this module depends on 102 * @par Usage: 103 * @code 104 * array( [file-path], [file-path], ... ) 105 * @endcode 106 */ 107 protected $dependencies = array(); 108 109 /** 110 * @var string File name containing the body of the skip function 111 */ 112 protected $skipFunction = null; 113 114 /** 115 * @var array List of message keys used by this module 116 * @par Usage: 117 * @code 118 * array( [message-key], [message-key], ... ) 119 * @endcode 120 */ 121 protected $messages = array(); 122 123 /** @var string Name of group to load this module in */ 124 protected $group; 125 126 /** @var string Position on the page to load this module at */ 127 protected $position = 'bottom'; 128 129 /** @var bool Link to raw files in debug mode */ 130 protected $debugRaw = true; 131 132 /** @var bool Whether mw.loader.state() call should be omitted */ 133 protected $raw = false; 134 135 protected $targets = array( 'desktop' ); 136 137 /** 138 * @var bool Whether getStyleURLsForDebug should return raw file paths, 139 * or return load.php urls 140 */ 141 protected $hasGeneratedStyles = false; 142 143 /** 144 * @var array Cache for mtime 145 * @par Usage: 146 * @code 147 * array( [hash] => [mtime], [hash] => [mtime], ... ) 148 * @endcode 149 */ 150 protected $modifiedTime = array(); 151 152 /** 153 * @var array Place where readStyleFile() tracks file dependencies 154 * @par Usage: 155 * @code 156 * array( [file-path], [file-path], ... ) 157 * @endcode 158 */ 159 protected $localFileRefs = array(); 160 161 /* Methods */ 162 163 /** 164 * Constructs a new module from an options array. 165 * 166 * @param array $options List of options; if not given or empty, an empty module will be 167 * constructed 168 * @param string $localBasePath Base path to prepend to all local paths in $options. Defaults 169 * to $IP 170 * @param string $remoteBasePath Base path to prepend to all remote paths in $options. Defaults 171 * to $wgResourceBasePath 172 * 173 * Below is a description for the $options array: 174 * @throws MWException 175 * @par Construction options: 176 * @code 177 * array( 178 * // Base path to prepend to all local paths in $options. Defaults to $IP 179 * 'localBasePath' => [base path], 180 * // Base path to prepend to all remote paths in $options. Defaults to $wgResourceBasePath 181 * 'remoteBasePath' => [base path], 182 * // Equivalent of remoteBasePath, but relative to $wgExtensionAssetsPath 183 * 'remoteExtPath' => [base path], 184 * // Equivalent of remoteBasePath, but relative to $wgStylePath 185 * 'remoteSkinPath' => [base path], 186 * // Scripts to always include 187 * 'scripts' => [file path string or array of file path strings], 188 * // Scripts to include in specific language contexts 189 * 'languageScripts' => array( 190 * [language code] => [file path string or array of file path strings], 191 * ), 192 * // Scripts to include in specific skin contexts 193 * 'skinScripts' => array( 194 * [skin name] => [file path string or array of file path strings], 195 * ), 196 * // Scripts to include in debug contexts 197 * 'debugScripts' => [file path string or array of file path strings], 198 * // Scripts to include in the startup module 199 * 'loaderScripts' => [file path string or array of file path strings], 200 * // Modules which must be loaded before this module 201 * 'dependencies' => [module name string or array of module name strings], 202 * // Styles to always load 203 * 'styles' => [file path string or array of file path strings], 204 * // Styles to include in specific skin contexts 205 * 'skinStyles' => array( 206 * [skin name] => [file path string or array of file path strings], 207 * ), 208 * // Messages to always load 209 * 'messages' => [array of message key strings], 210 * // Group which this module should be loaded together with 211 * 'group' => [group name string], 212 * // Position on the page to load this module at 213 * 'position' => ['bottom' (default) or 'top'] 214 * // Function that, if it returns true, makes the loader skip this module. 215 * // The file must contain valid JavaScript for execution in a private function. 216 * // The file must not contain the "function () {" and "}" wrapper though. 217 * 'skipFunction' => [file path] 218 * ) 219 * @endcode 220 */ 221 public function __construct( 222 $options = array(), 223 $localBasePath = null, 224 $remoteBasePath = null 225 ) { 226 // localBasePath and remoteBasePath both have unbelievably long fallback chains 227 // and need to be handled separately. 228 list( $this->localBasePath, $this->remoteBasePath ) = 229 self::extractBasePaths( $options, $localBasePath, $remoteBasePath ); 230 231 // Extract, validate and normalise remaining options 232 foreach ( $options as $member => $option ) { 233 switch ( $member ) { 234 // Lists of file paths 235 case 'scripts': 236 case 'debugScripts': 237 case 'loaderScripts': 238 case 'styles': 239 $this->{$member} = (array)$option; 240 break; 241 // Collated lists of file paths 242 case 'languageScripts': 243 case 'skinScripts': 244 case 'skinStyles': 245 if ( !is_array( $option ) ) { 246 throw new MWException( 247 "Invalid collated file path list error. " . 248 "'$option' given, array expected." 249 ); 250 } 251 foreach ( $option as $key => $value ) { 252 if ( !is_string( $key ) ) { 253 throw new MWException( 254 "Invalid collated file path list key error. " . 255 "'$key' given, string expected." 256 ); 257 } 258 $this->{$member}[$key] = (array)$value; 259 } 260 break; 261 // Lists of strings 262 case 'dependencies': 263 case 'messages': 264 case 'targets': 265 // Normalise 266 $option = array_values( array_unique( (array)$option ) ); 267 sort( $option ); 268 269 $this->{$member} = $option; 270 break; 271 // Single strings 272 case 'group': 273 case 'position': 274 case 'skipFunction': 275 $this->{$member} = (string)$option; 276 break; 277 // Single booleans 278 case 'debugRaw': 279 case 'raw': 280 $this->{$member} = (bool)$option; 281 break; 282 } 283 } 284 } 285 286 /** 287 * Extract a pair of local and remote base paths from module definition information. 288 * Implementation note: the amount of global state used in this function is staggering. 289 * 290 * @param array $options Module definition 291 * @param string $localBasePath Path to use if not provided in module definition. Defaults 292 * to $IP 293 * @param string $remoteBasePath Path to use if not provided in module definition. Defaults 294 * to $wgResourceBasePath 295 * @return array Array( localBasePath, remoteBasePath ) 296 */ 297 public static function extractBasePaths( 298 $options = array(), 299 $localBasePath = null, 300 $remoteBasePath = null 301 ) { 302 global $IP, $wgResourceBasePath; 303 304 // The different ways these checks are done, and their ordering, look very silly, 305 // but were preserved for backwards-compatibility just in case. Tread lightly. 306 307 $localBasePath = $localBasePath === null ? $IP : $localBasePath; 308 if ( $remoteBasePath === null ) { 309 $remoteBasePath = $wgResourceBasePath; 310 } 311 312 if ( isset( $options['remoteExtPath'] ) ) { 313 global $wgExtensionAssetsPath; 314 $remoteBasePath = $wgExtensionAssetsPath . '/' . $options['remoteExtPath']; 315 } 316 317 if ( isset( $options['remoteSkinPath'] ) ) { 318 global $wgStylePath; 319 $remoteBasePath = $wgStylePath . '/' . $options['remoteSkinPath']; 320 } 321 322 if ( array_key_exists( 'localBasePath', $options ) ) { 323 $localBasePath = (string)$options['localBasePath']; 324 } 325 326 if ( array_key_exists( 'remoteBasePath', $options ) ) { 327 $remoteBasePath = (string)$options['remoteBasePath']; 328 } 329 330 // Make sure the remote base path is a complete valid URL, 331 // but possibly protocol-relative to avoid cache pollution 332 $remoteBasePath = wfExpandUrl( $remoteBasePath, PROTO_RELATIVE ); 333 334 return array( $localBasePath, $remoteBasePath ); 335 } 336 337 /** 338 * Gets all scripts for a given context concatenated together. 339 * 340 * @param ResourceLoaderContext $context Context in which to generate script 341 * @return string JavaScript code for $context 342 */ 343 public function getScript( ResourceLoaderContext $context ) { 344 $files = $this->getScriptFiles( $context ); 345 return $this->readScriptFiles( $files ); 346 } 347 348 /** 349 * @param ResourceLoaderContext $context 350 * @return array 351 */ 352 public function getScriptURLsForDebug( ResourceLoaderContext $context ) { 353 $urls = array(); 354 foreach ( $this->getScriptFiles( $context ) as $file ) { 355 $urls[] = $this->getRemotePath( $file ); 356 } 357 return $urls; 358 } 359 360 /** 361 * @return bool 362 */ 363 public function supportsURLLoading() { 364 return $this->debugRaw; 365 } 366 367 /** 368 * Get loader script. 369 * 370 * @return string|bool JavaScript code to be added to startup module 371 */ 372 public function getLoaderScript() { 373 if ( count( $this->loaderScripts ) === 0 ) { 374 return false; 375 } 376 return $this->readScriptFiles( $this->loaderScripts ); 377 } 378 379 /** 380 * Get all styles for a given context. 381 * 382 * @param ResourceLoaderContext $context 383 * @return array CSS code for $context as an associative array mapping media type to CSS text. 384 */ 385 public function getStyles( ResourceLoaderContext $context ) { 386 $styles = $this->readStyleFiles( 387 $this->getStyleFiles( $context ), 388 $this->getFlip( $context ), 389 $context 390 ); 391 // Collect referenced files 392 $this->localFileRefs = array_unique( $this->localFileRefs ); 393 // If the list has been modified since last time we cached it, update the cache 394 try { 395 if ( $this->localFileRefs !== $this->getFileDependencies( $context->getSkin() ) ) { 396 $dbw = wfGetDB( DB_MASTER ); 397 $dbw->replace( 'module_deps', 398 array( array( 'md_module', 'md_skin' ) ), array( 399 'md_module' => $this->getName(), 400 'md_skin' => $context->getSkin(), 401 'md_deps' => FormatJson::encode( $this->localFileRefs ), 402 ) 403 ); 404 } 405 } catch ( Exception $e ) { 406 wfDebugLog( 'resourceloader', __METHOD__ . ": failed to update DB: $e" ); 407 } 408 return $styles; 409 } 410 411 /** 412 * @param ResourceLoaderContext $context 413 * @return array 414 */ 415 public function getStyleURLsForDebug( ResourceLoaderContext $context ) { 416 if ( $this->hasGeneratedStyles ) { 417 // Do the default behaviour of returning a url back to load.php 418 // but with only=styles. 419 return parent::getStyleURLsForDebug( $context ); 420 } 421 // Our module consists entirely of real css files, 422 // in debug mode we can load those directly. 423 $urls = array(); 424 foreach ( $this->getStyleFiles( $context ) as $mediaType => $list ) { 425 $urls[$mediaType] = array(); 426 foreach ( $list as $file ) { 427 $urls[$mediaType][] = $this->getRemotePath( $file ); 428 } 429 } 430 return $urls; 431 } 432 433 /** 434 * Gets list of message keys used by this module. 435 * 436 * @return array List of message keys 437 */ 438 public function getMessages() { 439 return $this->messages; 440 } 441 442 /** 443 * Gets the name of the group this module should be loaded in. 444 * 445 * @return string Group name 446 */ 447 public function getGroup() { 448 return $this->group; 449 } 450 451 /** 452 * @return string 453 */ 454 public function getPosition() { 455 return $this->position; 456 } 457 458 /** 459 * Gets list of names of modules this module depends on. 460 * 461 * @return array List of module names 462 */ 463 public function getDependencies() { 464 return $this->dependencies; 465 } 466 467 /** 468 * Get the skip function. 469 * 470 * @return string|null 471 */ 472 public function getSkipFunction() { 473 if ( !$this->skipFunction ) { 474 return null; 475 } 476 477 $localPath = $this->getLocalPath( $this->skipFunction ); 478 if ( !file_exists( $localPath ) ) { 479 throw new MWException( __METHOD__ . ": skip function file not found: \"$localPath\"" ); 480 } 481 $contents = file_get_contents( $localPath ); 482 if ( $this->getConfig()->get( 'ResourceLoaderValidateStaticJS' ) ) { 483 $contents = $this->validateScriptFile( $localPath, $contents ); 484 } 485 return $contents; 486 } 487 488 /** 489 * @return bool 490 */ 491 public function isRaw() { 492 return $this->raw; 493 } 494 495 /** 496 * Get the last modified timestamp of this module. 497 * 498 * Last modified timestamps are calculated from the highest last modified 499 * timestamp of this module's constituent files as well as the files it 500 * depends on. This function is context-sensitive, only performing 501 * calculations on files relevant to the given language, skin and debug 502 * mode. 503 * 504 * @param ResourceLoaderContext $context Context in which to calculate 505 * the modified time 506 * @return int UNIX timestamp 507 * @see ResourceLoaderModule::getFileDependencies 508 */ 509 public function getModifiedTime( ResourceLoaderContext $context ) { 510 if ( isset( $this->modifiedTime[$context->getHash()] ) ) { 511 return $this->modifiedTime[$context->getHash()]; 512 } 513 wfProfileIn( __METHOD__ ); 514 515 $files = array(); 516 517 // Flatten style files into $files 518 $styles = self::collateFilePathListByOption( $this->styles, 'media', 'all' ); 519 foreach ( $styles as $styleFiles ) { 520 $files = array_merge( $files, $styleFiles ); 521 } 522 523 $skinFiles = self::collateFilePathListByOption( 524 self::tryForKey( $this->skinStyles, $context->getSkin(), 'default' ), 525 'media', 526 'all' 527 ); 528 foreach ( $skinFiles as $styleFiles ) { 529 $files = array_merge( $files, $styleFiles ); 530 } 531 532 // Final merge, this should result in a master list of dependent files 533 $files = array_merge( 534 $files, 535 $this->scripts, 536 $context->getDebug() ? $this->debugScripts : array(), 537 self::tryForKey( $this->languageScripts, $context->getLanguage() ), 538 self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' ), 539 $this->loaderScripts 540 ); 541 if ( $this->skipFunction ) { 542 $files[] = $this->skipFunction; 543 } 544 $files = array_map( array( $this, 'getLocalPath' ), $files ); 545 // File deps need to be treated separately because they're already prefixed 546 $files = array_merge( $files, $this->getFileDependencies( $context->getSkin() ) ); 547 548 // If a module is nothing but a list of dependencies, we need to avoid 549 // giving max() an empty array 550 if ( count( $files ) === 0 ) { 551 $this->modifiedTime[$context->getHash()] = 1; 552 wfProfileOut( __METHOD__ ); 553 return $this->modifiedTime[$context->getHash()]; 554 } 555 556 wfProfileIn( __METHOD__ . '-filemtime' ); 557 $filesMtime = max( array_map( array( __CLASS__, 'safeFilemtime' ), $files ) ); 558 wfProfileOut( __METHOD__ . '-filemtime' ); 559 560 $this->modifiedTime[$context->getHash()] = max( 561 $filesMtime, 562 $this->getMsgBlobMtime( $context->getLanguage() ), 563 $this->getDefinitionMtime( $context ) 564 ); 565 566 wfProfileOut( __METHOD__ ); 567 return $this->modifiedTime[$context->getHash()]; 568 } 569 570 /** 571 * Get the definition summary for this module. 572 * 573 * @param ResourceLoaderContext $context 574 * @return array 575 */ 576 public function getDefinitionSummary( ResourceLoaderContext $context ) { 577 $summary = array( 578 'class' => get_class( $this ), 579 ); 580 foreach ( array( 581 'scripts', 582 'debugScripts', 583 'loaderScripts', 584 'styles', 585 'languageScripts', 586 'skinScripts', 587 'skinStyles', 588 'dependencies', 589 'messages', 590 'targets', 591 'group', 592 'position', 593 'skipFunction', 594 'localBasePath', 595 'remoteBasePath', 596 'debugRaw', 597 'raw', 598 ) as $member ) { 599 $summary[$member] = $this->{$member}; 600 }; 601 return $summary; 602 } 603 604 /* Protected Methods */ 605 606 /** 607 * @param string|ResourceLoaderFilePath $path 608 * @return string 609 */ 610 protected function getLocalPath( $path ) { 611 if ( $path instanceof ResourceLoaderFilePath ) { 612 return $path->getLocalPath(); 613 } 614 615 return "{$this->localBasePath}/$path"; 616 } 617 618 /** 619 * @param string|ResourceLoaderFilePath $path 620 * @return string 621 */ 622 protected function getRemotePath( $path ) { 623 if ( $path instanceof ResourceLoaderFilePath ) { 624 return $path->getRemotePath(); 625 } 626 627 return "{$this->remoteBasePath}/$path"; 628 } 629 630 /** 631 * Infer the stylesheet language from a stylesheet file path. 632 * 633 * @since 1.22 634 * @param string $path 635 * @return string The stylesheet language name 636 */ 637 public function getStyleSheetLang( $path ) { 638 return preg_match( '/\.less$/i', $path ) ? 'less' : 'css'; 639 } 640 641 /** 642 * Collates file paths by option (where provided). 643 * 644 * @param array $list List of file paths in any combination of index/path 645 * or path/options pairs 646 * @param string $option Option name 647 * @param mixed $default Default value if the option isn't set 648 * @return array List of file paths, collated by $option 649 */ 650 protected static function collateFilePathListByOption( array $list, $option, $default ) { 651 $collatedFiles = array(); 652 foreach ( (array)$list as $key => $value ) { 653 if ( is_int( $key ) ) { 654 // File name as the value 655 if ( !isset( $collatedFiles[$default] ) ) { 656 $collatedFiles[$default] = array(); 657 } 658 $collatedFiles[$default][] = $value; 659 } elseif ( is_array( $value ) ) { 660 // File name as the key, options array as the value 661 $optionValue = isset( $value[$option] ) ? $value[$option] : $default; 662 if ( !isset( $collatedFiles[$optionValue] ) ) { 663 $collatedFiles[$optionValue] = array(); 664 } 665 $collatedFiles[$optionValue][] = $key; 666 } 667 } 668 return $collatedFiles; 669 } 670 671 /** 672 * Get a list of element that match a key, optionally using a fallback key. 673 * 674 * @param array $list List of lists to select from 675 * @param string $key Key to look for in $map 676 * @param string $fallback Key to look for in $list if $key doesn't exist 677 * @return array List of elements from $map which matched $key or $fallback, 678 * or an empty list in case of no match 679 */ 680 protected static function tryForKey( array $list, $key, $fallback = null ) { 681 if ( isset( $list[$key] ) && is_array( $list[$key] ) ) { 682 return $list[$key]; 683 } elseif ( is_string( $fallback ) 684 && isset( $list[$fallback] ) 685 && is_array( $list[$fallback] ) 686 ) { 687 return $list[$fallback]; 688 } 689 return array(); 690 } 691 692 /** 693 * Get a list of file paths for all scripts in this module, in order of proper execution. 694 * 695 * @param ResourceLoaderContext $context 696 * @return array List of file paths 697 */ 698 protected function getScriptFiles( ResourceLoaderContext $context ) { 699 $files = array_merge( 700 $this->scripts, 701 self::tryForKey( $this->languageScripts, $context->getLanguage() ), 702 self::tryForKey( $this->skinScripts, $context->getSkin(), 'default' ) 703 ); 704 if ( $context->getDebug() ) { 705 $files = array_merge( $files, $this->debugScripts ); 706 } 707 708 return array_unique( $files, SORT_REGULAR ); 709 } 710 711 /** 712 * Get a list of file paths for all styles in this module, in order of proper inclusion. 713 * 714 * @param ResourceLoaderContext $context 715 * @return array List of file paths 716 */ 717 public function getStyleFiles( ResourceLoaderContext $context ) { 718 return array_merge_recursive( 719 self::collateFilePathListByOption( $this->styles, 'media', 'all' ), 720 self::collateFilePathListByOption( 721 self::tryForKey( $this->skinStyles, $context->getSkin(), 'default' ), 722 'media', 723 'all' 724 ) 725 ); 726 } 727 728 /** 729 * Gets a list of file paths for all skin styles in the module used by 730 * the skin. 731 * 732 * @param string $skinName The name of the skin 733 * @return array A list of file paths collated by media type 734 */ 735 protected function getSkinStyleFiles( $skinName ) { 736 return self::collateFilePathListByOption( 737 self::tryForKey( $this->skinStyles, $skinName ), 738 'media', 739 'all' 740 ); 741 } 742 743 /** 744 * Gets a list of file paths for all skin style files in the module, 745 * for all available skins. 746 * 747 * @return array A list of file paths collated by media type 748 */ 749 protected function getAllSkinStyleFiles() { 750 $styleFiles = array(); 751 $internalSkinNames = array_keys( Skin::getSkinNames() ); 752 $internalSkinNames[] = 'default'; 753 754 foreach ( $internalSkinNames as $internalSkinName ) { 755 $styleFiles = array_merge_recursive( 756 $styleFiles, 757 $this->getSkinStyleFiles( $internalSkinName ) 758 ); 759 } 760 761 return $styleFiles; 762 } 763 764 /** 765 * Returns all style files and all skin style files used by this module. 766 * 767 * @return array 768 */ 769 public function getAllStyleFiles() { 770 $collatedStyleFiles = array_merge_recursive( 771 self::collateFilePathListByOption( $this->styles, 'media', 'all' ), 772 $this->getAllSkinStyleFiles() 773 ); 774 775 $result = array(); 776 777 foreach ( $collatedStyleFiles as $media => $styleFiles ) { 778 foreach ( $styleFiles as $styleFile ) { 779 $result[] = $this->getLocalPath( $styleFile ); 780 } 781 } 782 783 return $result; 784 } 785 786 /** 787 * Gets the contents of a list of JavaScript files. 788 * 789 * @param array $scripts List of file paths to scripts to read, remap and concetenate 790 * @throws MWException 791 * @return string Concatenated and remapped JavaScript data from $scripts 792 */ 793 protected function readScriptFiles( array $scripts ) { 794 if ( empty( $scripts ) ) { 795 return ''; 796 } 797 $js = ''; 798 foreach ( array_unique( $scripts, SORT_REGULAR ) as $fileName ) { 799 $localPath = $this->getLocalPath( $fileName ); 800 if ( !file_exists( $localPath ) ) { 801 throw new MWException( __METHOD__ . ": script file not found: \"$localPath\"" ); 802 } 803 $contents = file_get_contents( $localPath ); 804 if ( $this->getConfig()->get( 'ResourceLoaderValidateStaticJS' ) ) { 805 // Static files don't really need to be checked as often; unlike 806 // on-wiki module they shouldn't change unexpectedly without 807 // admin interference. 808 $contents = $this->validateScriptFile( $fileName, $contents ); 809 } 810 $js .= $contents . "\n"; 811 } 812 return $js; 813 } 814 815 /** 816 * Gets the contents of a list of CSS files. 817 * 818 * @param array $styles List of media type/list of file paths pairs, to read, remap and 819 * concetenate 820 * @param bool $flip 821 * @param ResourceLoaderContext $context (optional) 822 * 823 * @throws MWException 824 * @return array List of concatenated and remapped CSS data from $styles, 825 * keyed by media type 826 */ 827 public function readStyleFiles( array $styles, $flip, $context = null ) { 828 if ( empty( $styles ) ) { 829 return array(); 830 } 831 foreach ( $styles as $media => $files ) { 832 $uniqueFiles = array_unique( $files, SORT_REGULAR ); 833 $styleFiles = array(); 834 foreach ( $uniqueFiles as $file ) { 835 $styleFiles[] = $this->readStyleFile( $file, $flip, $context ); 836 } 837 $styles[$media] = implode( "\n", $styleFiles ); 838 } 839 return $styles; 840 } 841 842 /** 843 * Reads a style file. 844 * 845 * This method can be used as a callback for array_map() 846 * 847 * @param string $path File path of style file to read 848 * @param bool $flip 849 * @param ResourceLoaderContext $context (optional) 850 * 851 * @return string CSS data in script file 852 * @throws MWException If the file doesn't exist 853 */ 854 protected function readStyleFile( $path, $flip, $context = null ) { 855 $localPath = $this->getLocalPath( $path ); 856 $remotePath = $this->getRemotePath( $path ); 857 if ( !file_exists( $localPath ) ) { 858 $msg = __METHOD__ . ": style file not found: \"$localPath\""; 859 wfDebugLog( 'resourceloader', $msg ); 860 throw new MWException( $msg ); 861 } 862 863 if ( $this->getStyleSheetLang( $localPath ) === 'less' ) { 864 $compiler = $this->getLessCompiler( $context ); 865 $style = $this->compileLessFile( $localPath, $compiler ); 866 $this->hasGeneratedStyles = true; 867 } else { 868 $style = file_get_contents( $localPath ); 869 } 870 871 if ( $flip ) { 872 $style = CSSJanus::transform( $style, true, false ); 873 } 874 $localDir = dirname( $localPath ); 875 $remoteDir = dirname( $remotePath ); 876 // Get and register local file references 877 $this->localFileRefs = array_merge( 878 $this->localFileRefs, 879 CSSMin::getLocalFileReferences( $style, $localDir ) 880 ); 881 return CSSMin::remap( 882 $style, $localDir, $remoteDir, true 883 ); 884 } 885 886 /** 887 * Get whether CSS for this module should be flipped 888 * @param ResourceLoaderContext $context 889 * @return bool 890 */ 891 public function getFlip( $context ) { 892 return $context->getDirection() === 'rtl'; 893 } 894 895 /** 896 * Get target(s) for the module, eg ['desktop'] or ['desktop', 'mobile'] 897 * 898 * @return array Array of strings 899 */ 900 public function getTargets() { 901 return $this->targets; 902 } 903 904 /** 905 * Compile a LESS file into CSS. 906 * 907 * Keeps track of all used files and adds them to localFileRefs. 908 * 909 * @since 1.22 910 * @throws Exception If lessc encounters a parse error 911 * @param string $fileName File path of LESS source 912 * @param lessc $compiler Compiler to use, if not default 913 * @return string CSS source 914 */ 915 protected function compileLessFile( $fileName, $compiler = null ) { 916 if ( !$compiler ) { 917 $compiler = $this->getLessCompiler(); 918 } 919 $result = $compiler->compileFile( $fileName ); 920 $this->localFileRefs += array_keys( $compiler->allParsedFiles() ); 921 return $result; 922 } 923 924 /** 925 * Get a LESS compiler instance for this module in given context. 926 * 927 * Just calls ResourceLoader::getLessCompiler() by default to get a global compiler. 928 * 929 * @param ResourceLoaderContext $context 930 * @throws MWException 931 * @since 1.24 932 * @return lessc 933 */ 934 protected function getLessCompiler( ResourceLoaderContext $context = null ) { 935 return ResourceLoader::getLessCompiler( $this->getConfig() ); 936 } 937 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |