[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/resourceloader/ -> ResourceLoaderFileModule.php (source)

   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  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1