summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/Bootstrap/src
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/extensions/Bootstrap/src')
-rw-r--r--www/wiki/extensions/Bootstrap/src/BootstrapManager.php205
-rw-r--r--www/wiki/extensions/Bootstrap/src/Definition/ModuleDefinition.php43
-rw-r--r--www/wiki/extensions/Bootstrap/src/Definition/V3ModuleDefinition.php127
-rw-r--r--www/wiki/extensions/Bootstrap/src/Hooks/SetupAfterCache.php195
-rw-r--r--www/wiki/extensions/Bootstrap/src/ResourceLoaderBootstrapModule.php218
5 files changed, 788 insertions, 0 deletions
diff --git a/www/wiki/extensions/Bootstrap/src/BootstrapManager.php b/www/wiki/extensions/Bootstrap/src/BootstrapManager.php
new file mode 100644
index 00000000..7248320e
--- /dev/null
+++ b/www/wiki/extensions/Bootstrap/src/BootstrapManager.php
@@ -0,0 +1,205 @@
+<?php
+
+namespace Bootstrap;
+
+use Bootstrap\Definition\V3ModuleDefinition;
+use Bootstrap\Definition\ModuleDefinition;
+
+/**
+ * File holding the Bootstrap class
+ *
+ * @copyright (C) 2013, Stephan Gambke
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License, version 3 (or later)
+ *
+ * This file is part of the MediaWiki extension Bootstrap.
+ * The Bootstrap extension is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The Bootstrap extension is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @file
+ * @ingroup Bootstrap
+ */
+
+/**
+ * Class managing the Bootstrap framework.
+ */
+class BootstrapManager {
+
+ /** @var ModuleDefinition */
+ protected $moduleDefinition = null;
+
+ /** @var BootstrapManager */
+ private static $instance = null;
+
+ private $mModuleDescriptions;
+
+ /**
+ * @since 1.0
+ *
+ * @param ModuleDefinition $moduleDefinition
+ */
+ public function __construct( ModuleDefinition $moduleDefinition ) {
+ $this->moduleDefinition = $moduleDefinition;
+ $this->initCoreModules();
+ }
+
+ /**
+ * Returns the Bootstrap singleton.
+ *
+ * @since 1.0
+ *
+ * @return BootstrapManager
+ */
+ public static function getInstance() {
+
+ if ( self::$instance === null ) {
+ self::$instance = new self( new V3ModuleDefinition );
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * @since 1.0
+ */
+ public static function clear() {
+ self::$instance = null;
+ }
+
+ /**
+ * Adds the given Bootstrap module or modules.
+ *
+ * @since 1.0
+ *
+ * @param string|string[] $modules
+ */
+ public function addBootstrapModule( $modules ) {
+
+ $modules = (array) $modules;
+
+ foreach ( $modules as $module ) {
+
+ // if the module is known
+ if ( isset( $this->mModuleDescriptions[ $module ] ) ) {
+
+ $description = $this->mModuleDescriptions[ $module ];
+
+ // prevent adding this module again; this also prevents infinite recursion in case
+ // of dependency resolution
+ unset( $this->mModuleDescriptions[ $module ] );
+
+ // first add any dependencies recursively, so they are available when the styles and
+ // scripts of $module are loaded
+ if ( isset( $description[ 'dependencies' ] ) ) {
+ $this->addBootstrapModule( $description[ 'dependencies' ] );
+ }
+
+ $this->addFilesToGlobalResourceModules( 'styles', $description, '.less' );
+ $this->addFilesToGlobalResourceModules( 'scripts', $description, '.js' );
+
+ }
+ }
+
+ }
+
+ /**
+ * @param string $filetype 'styles'|'scripts'
+ * @param mixed[] $description
+ * @param $fileExt
+ */
+ protected function addFilesToGlobalResourceModules ( $filetype, $description, $fileExt ) {
+
+ if ( isset( $description[ $filetype ] ) ) {
+
+ $files = array_map(
+ function ( $filename ) use ( $fileExt ) {
+ return $filename . $fileExt;
+ },
+ (array) $description[ $filetype ]
+ );
+
+ $this->adjustArrayElementOfResourceModuleDescription( $filetype, $files, $filetype );
+
+ }
+ }
+
+ /**
+ * Adds all bootstrap modules
+ *
+ * @since 1.0
+ */
+ public function addAllBootstrapModules() {
+ $this->addBootstrapModule( $this->moduleDefinition->get( 'optional' ) );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param string $file
+ * @param string $remotePath
+ *
+ * @internal param string $path
+ */
+ public function addExternalModule( $file, $remotePath = '' ) {
+ $this->adjustArrayElementOfResourceModuleDescription( 'external styles', array( $file => $remotePath ) );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param string $key the LESS variable name
+ * @param string $value the value to assign to the variable
+ */
+ public function setLessVariable( $key, $value ) {
+ $this->setLessVariables( array( $key => $value ) );
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param mixed[] $variables
+ */
+ public function setLessVariables( $variables ) {
+ $this->adjustArrayElementOfResourceModuleDescription( 'variables', $variables );
+ }
+
+ /**
+ * @since 1.1
+ * @param string|string[] $files
+ */
+ public function addCacheTriggerFile( $files ){
+ $this->adjustArrayElementOfResourceModuleDescription( 'cachetriggers', $files );
+ }
+
+ protected function initCoreModules() {
+ $this->mModuleDescriptions = $this->moduleDefinition->get( 'descriptions' );
+ $this->addBootstrapModule( $this->moduleDefinition->get( 'core' ) );
+ }
+
+ /**
+ * @param string $key
+ * @param mixed $value
+ * @param string $filetype 'styles'|'scripts'
+ */
+ protected function adjustArrayElementOfResourceModuleDescription( $key, $value, $filetype = 'styles' ) {
+
+ if (!isset($GLOBALS[ 'wgResourceModules' ][ 'ext.bootstrap.' . $filetype ][ $key ])) {
+ $GLOBALS[ 'wgResourceModules' ][ 'ext.bootstrap.' . $filetype ][ $key ] = $value;
+ } else {
+ $GLOBALS[ 'wgResourceModules' ][ 'ext.bootstrap.' . $filetype ][ $key ] =
+ array_merge(
+ $GLOBALS[ 'wgResourceModules' ][ 'ext.bootstrap.' . $filetype ][ $key ],
+ (array) $value
+ );
+ }
+ }
+}
diff --git a/www/wiki/extensions/Bootstrap/src/Definition/ModuleDefinition.php b/www/wiki/extensions/Bootstrap/src/Definition/ModuleDefinition.php
new file mode 100644
index 00000000..a40dd7f5
--- /dev/null
+++ b/www/wiki/extensions/Bootstrap/src/Definition/ModuleDefinition.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Bootstrap\Definition;
+
+/**
+ * @copyright (C) 2013, Stephan Gambke
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License, version 3 (or later)
+ *
+ * This file is part of the MediaWiki extension Bootstrap.
+ * The Bootstrap extension is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The Bootstrap extension is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @file
+ * @ingroup Bootstrap
+ */
+
+/**
+ * Interface describing module definitions
+ */
+interface ModuleDefinition {
+
+ /**
+ * Returns a definition array
+ *
+ * @since 1.0
+ *
+ * @param string $key
+ *
+ * @return array
+ */
+ public function get( $key );
+
+}
diff --git a/www/wiki/extensions/Bootstrap/src/Definition/V3ModuleDefinition.php b/www/wiki/extensions/Bootstrap/src/Definition/V3ModuleDefinition.php
new file mode 100644
index 00000000..8cf9c0fb
--- /dev/null
+++ b/www/wiki/extensions/Bootstrap/src/Definition/V3ModuleDefinition.php
@@ -0,0 +1,127 @@
+<?php
+
+namespace Bootstrap\Definition;
+
+use InvalidArgumentException;
+
+/**
+ * @copyright (C) 2013, Stephan Gambke
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License, version 3 (or later)
+ *
+ * This file is part of the MediaWiki extension Bootstrap.
+ * The Bootstrap extension is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The Bootstrap extension is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @file
+ * @ingroup Bootstrap
+ */
+
+/**
+ * Class describing the V3 Bootstrap module definitions
+ */
+class V3ModuleDefinition implements ModuleDefinition {
+
+ static private $moduleDescriptions = array(
+ 'variables' => array( 'styles' => 'variables' ),
+ 'mixins' => array( 'styles' => 'mixins' ),
+ 'normalize' => array( 'styles' => 'normalize' ),
+ 'print' => array( 'styles' => 'print' ),
+ 'scaffolding' => array( 'styles' => 'scaffolding' ),
+ 'type' => array( 'styles' => 'type' ),
+ 'code' => array( 'styles' => 'code' ),
+ 'grid' => array( 'styles' => 'grid' ),
+ 'tables' => array( 'styles' => 'tables' ),
+ 'forms' => array( 'styles' => 'forms' ),
+ 'buttons' => array( 'styles' => 'buttons' ),
+ 'component-animations' => array( 'styles' => 'component-animations' ),
+ 'glyphicons' => array( 'styles' => 'glyphicons' ),
+ 'dropdowns' => array( 'styles' => 'dropdowns' ),
+ 'button-groups' => array( 'styles' => 'button-groups' ),
+ 'input-groups' => array( 'styles' => 'input-groups' ),
+ 'navs' => array( 'styles' => 'navs' ),
+ 'navbar' => array( 'styles' => 'navbar' ),
+ 'breadcrumbs' => array( 'styles' => 'breadcrumbs' ),
+ 'pagination' => array( 'styles' => 'pagination' ),
+ 'pager' => array( 'styles' => 'pager' ),
+ 'labels' => array( 'styles' => 'labels' ),
+ 'badges' => array( 'styles' => 'badges' ),
+ 'jumbotron' => array( 'styles' => 'jumbotron' ),
+ 'thumbnails' => array( 'styles' => 'thumbnails' ),
+ 'alerts' => array( 'styles' => 'alerts' ),
+ 'progress-bars' => array( 'styles' => 'progress-bars' ),
+ 'media' => array( 'styles' => 'media' ),
+ 'list-group' => array( 'styles' => 'list-group' ),
+ 'panels' => array( 'styles' => 'panels' ),
+ 'wells' => array( 'styles' => 'wells' ),
+ 'close' => array( 'styles' => 'close' ),
+
+ // Components w/ JavaScript
+ 'modals' => array( 'styles' => 'modals', 'scripts' => 'modal' ),
+ 'tooltip' => array( 'styles' => 'tooltip', 'scripts' => 'tooltip' ),
+ 'popovers' => array( 'styles' => 'popovers', 'scripts' => 'popover', 'dependencies' => 'tooltip' ),
+ 'carousel' => array( 'styles' => 'carousel', 'scripts' => 'carousel' ),
+
+ // Utility classes
+ 'utilities' => array( 'styles' => 'utilities' ),
+ 'responsive-utilities' => array( 'styles' => 'responsive-utilities' ),
+
+ // JS-only components
+ 'affix' => array( 'scripts' => 'affix' ),
+ 'alert' => array( 'scripts' => 'alert' ),
+ 'button' => array( 'scripts' => 'button' ),
+ 'collapse' => array( 'scripts' => 'collapse' ),
+ 'dropdown' => array( 'scripts' => 'dropdown' ),
+ 'scrollspy' => array( 'scripts' => 'scrollspy' ),
+ 'tab' => array( 'scripts' => 'tab' ),
+ 'transition' => array( 'scripts' => 'transition' ),
+
+ );
+
+ static private $coreModules = array(
+ 'variables', 'mixins', 'normalize', 'print', 'scaffolding', 'type', 'code', 'grid',
+ 'tables', 'forms', 'buttons'
+ );
+
+ static private $optionalModules = array(
+ 'component-animations', 'glyphicons', 'dropdowns', 'button-groups', 'input-groups', 'navs',
+ 'navbar', 'breadcrumbs', 'pagination', 'pager', 'labels', 'badges', 'jumbotron',
+ 'thumbnails', 'alerts', 'progress-bars', 'media', 'list-group', 'panels', 'wells', 'close',
+ 'modals', 'tooltip', 'popovers', 'carousel', 'utilities', 'responsive-utilities', 'affix',
+ 'alert', 'button', 'collapse', 'dropdown', 'scrollspy', 'tab', 'transition'
+ );
+
+ /**
+ * @see ModuleDefinition::get
+ *
+ * @since 1.0
+ *
+ * @param string $key
+ *
+ * @return array
+ * @throws InvalidArgumentException
+ */
+ public function get( $key ) {
+
+ switch ( $key ) {
+ case 'core':
+ return self::$coreModules;
+ case 'optional':
+ return self::$optionalModules;
+ case 'descriptions':
+ return self::$moduleDescriptions;
+ }
+
+ throw new InvalidArgumentException( 'Expected a valid key' );
+ }
+
+}
diff --git a/www/wiki/extensions/Bootstrap/src/Hooks/SetupAfterCache.php b/www/wiki/extensions/Bootstrap/src/Hooks/SetupAfterCache.php
new file mode 100644
index 00000000..8ef76943
--- /dev/null
+++ b/www/wiki/extensions/Bootstrap/src/Hooks/SetupAfterCache.php
@@ -0,0 +1,195 @@
+<?php
+
+namespace Bootstrap\Hooks;
+
+use RuntimeException;
+use InvalidArgumentException;
+
+/**
+ * File holding the Hooks class
+ *
+ * @copyright (C) 2013, Stephan Gambke
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License, version 3 (or later)
+ *
+ * This file is part of the MediaWiki extension Bootstrap.
+ * The Bootstrap extension is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The Bootstrap extension is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @file
+ * @ingroup Bootstrap
+ */
+
+/**
+ * @see https://www.mediawiki.org/wiki/Manual:Hooks/SetupAfterCache
+ *
+ * @package bootstrap
+ * @license GNU GPL v3+
+ * @since 1.0
+ *
+ * @author mwjames
+ * @author Stephan Gambke
+ */
+class SetupAfterCache {
+
+ protected $configuration = array();
+
+ /**
+ * @since 1.0
+ *
+ * @param array $configuration
+ */
+ public function __construct( array $configuration ) {
+ $this->configuration = $configuration;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @throws InvalidArgumentException
+ * @throws RuntimeException
+ */
+ public function process() {
+
+ $this->assertAcceptableConfiguration();
+
+ $this->removeLegacyLessCompilerFromComposerAutoloader();
+
+ $this->registerBootstrapResourcePaths(
+ $this->isReadablePath( $this->configuration['localBasePath'] ),
+ $this->configuration[ 'remoteBasePath' ]
+ );
+
+ $this->registerCacheTriggers();
+
+ return true;
+ }
+
+ /**
+ * Add paths to resource modules if they are not there yet (e.g. set in LocalSettings.php)
+ * @param string $localBasePath
+ * @param string $remoteBasePath
+ */
+ protected function registerBootstrapResourcePaths( $localBasePath, $remoteBasePath ) {
+
+ $GLOBALS[ 'wgResourceModules' ][ 'ext.bootstrap.styles' ] = array_replace_recursive( array(
+ 'localBasePath' => $localBasePath . '/less',
+ 'remoteBasePath' => $remoteBasePath . '/less',
+ 'variables' => array(
+ 'icon-font-path' => "\"$remoteBasePath/fonts/\"",
+ ),
+ ),
+ $GLOBALS[ 'wgResourceModules' ][ 'ext.bootstrap.styles' ]
+ );
+
+ $GLOBALS[ 'wgResourceModules' ][ 'ext.bootstrap.scripts' ] = array_replace_recursive( array(
+ 'localBasePath' => $localBasePath . '/js',
+ 'remoteBasePath' => $remoteBasePath . '/js',
+ ),
+ $GLOBALS[ 'wgResourceModules' ][ 'ext.bootstrap.scripts' ]
+ );
+ }
+
+ /**
+ * Remove lessc adapter of the less.php compiler from Composer autoloader
+ *
+ * MediaWiki core uses the lessc compiler from http://leafo.net/lessphp .
+ * This compiler requires non-standard Less files which are incompatible
+ * with the compiler used by the Bootstrap extension. It is therefore
+ * necessary to ensure that MW will load its own lessc compiler class
+ * and not the adapter class provided by the Less compiler used by the
+ * Bootstrap extension or else it will not be able to compile its broken
+ * Less files.
+ */
+ protected function removeLegacyLessCompilerFromComposerAutoloader() {
+
+ $autoloadFunctions = spl_autoload_functions();
+
+ foreach ( $autoloadFunctions as $autoloadFunction ) {
+
+ if ( is_object( $autoloadFunction ) && ( $autoloadFunction instanceof Closure ) ) {
+ continue;
+ }
+
+ $classLoader = $autoloadFunction[ 0 ];
+
+ if ( is_a( $classLoader, '\Composer\Autoload\ClassLoader' ) ) {
+
+ $classMap = $classLoader->getClassMap();
+
+ if ( !is_array( $classMap ) ||
+ !array_key_exists( 'lessc', $classMap ) ||
+ strpos( $classMap[ 'lessc' ], '/less.php/less.php/lessc.inc.php') !== false ) {
+
+ $classLoader->addClassMap( array( 'lessc' => null ) );
+ }
+ break;
+ }
+ }
+
+ }
+
+ /**
+ * @param string $id
+ * @return bool
+ */
+ protected function hasConfiguration( $id ) {
+ return isset( $this->configuration[ $id ] );
+ }
+
+ /**
+ * @param string $localBasePath
+ * @return string
+ * @throws RuntimeException
+ */
+ protected function isReadablePath( $localBasePath ) {
+
+ $localBasePath = str_replace( array( '\\', '/' ), DIRECTORY_SEPARATOR, $localBasePath );
+
+ if ( is_readable( $localBasePath ) ) {
+ return $localBasePath;
+ }
+
+ throw new RuntimeException( "Expected an accessible {$localBasePath} path" );
+ }
+
+ protected function registerCacheTriggers() {
+
+ $defaultRecacheTriggers = array(
+ 'LocalSettings.php' => $this->configuration[ 'IP' ] . '/LocalSettings.php',
+ 'composer.lock' => $this->configuration[ 'IP' ] . '/composer.lock',
+ );
+
+ foreach ( $defaultRecacheTriggers as $key => $filename ) {
+ if ( array_key_exists( $key, $GLOBALS[ 'wgResourceModules' ][ 'ext.bootstrap.styles' ][ 'cachetriggers' ] ) &&
+ $GLOBALS[ 'wgResourceModules' ][ 'ext.bootstrap.styles' ][ 'cachetriggers' ][ $key ] === null ) {
+ $GLOBALS[ 'wgResourceModules' ][ 'ext.bootstrap.styles' ][ 'cachetriggers' ][ $key ] = $filename;
+ }
+ }
+ }
+
+ public function assertAcceptableConfiguration() {
+
+ $configElements = array(
+ 'localBasePath' => 'Local base path to Bootstrap modules not found.',
+ 'remoteBasePath' => 'Remote base path to Bootstrap modules not found.',
+ 'IP' => 'Full path to working directory ($IP) not found.',
+ );
+
+ foreach ( $configElements as $key => $errorMessage ) {
+ if ( !$this->hasConfiguration( $key ) ) {
+ throw new InvalidArgumentException( $errorMessage );
+ }
+ }
+ }
+
+}
diff --git a/www/wiki/extensions/Bootstrap/src/ResourceLoaderBootstrapModule.php b/www/wiki/extensions/Bootstrap/src/ResourceLoaderBootstrapModule.php
new file mode 100644
index 00000000..ab2ada58
--- /dev/null
+++ b/www/wiki/extensions/Bootstrap/src/ResourceLoaderBootstrapModule.php
@@ -0,0 +1,218 @@
+<?php
+
+namespace Bootstrap;
+
+use Less_Parser;
+use ResourceLoaderContext;
+use ResourceLoaderFileModule;
+use BagOStuff;
+
+use Exception;
+
+/**
+ * File holding the ResourceLoaderBootstrapModule class
+ *
+ * @copyright (C) 2013, Stephan Gambke
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License, version 3 (or later)
+ *
+ * This file is part of the MediaWiki extension Bootstrap.
+ * The Bootstrap extension is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The Bootstrap extension is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @file
+ * @ingroup Bootstrap
+ */
+
+/**
+ * ResourceLoader module based on local JavaScript/LESS files.
+ *
+ * Different to the behaviour of ResourceLoaderFileModule this module compiles all LESS files in one compile context.
+ *
+ * It recognizes the following additional fields in $wgResourceModules:
+ * * styles: array of LESS file names (with or without extension .less)
+ * * variables: array of key value pairs representing LESS variables, that will be added to the LESS script after all
+ * files imports, i.e. that may override any variable set in style files
+ * * paths: array of paths to search for style files; all these paths together represent one virtual file base and will
+ * be searched for a style file; this means it is not possible to include two LESS files with the same name
+ * even if in different paths
+ *
+ * @package Bootstrap
+ */
+class ResourceLoaderBootstrapModule extends ResourceLoaderFileModule {
+
+ /** @var BagOStuff */
+ protected $cache = null;
+
+ protected $variables = array();
+ protected $paths = array();
+ protected $extStyles = array();
+ protected $cacheTriggers = array();
+
+ protected $styleText = null;
+
+ public function __construct( $options = array(), $localBasePath = null, $remoteBasePath = null
+ ) {
+
+ parent::__construct( $options, $localBasePath, $remoteBasePath );
+
+ $this->applyOptions( $options );
+ }
+
+ /**
+ * Get the compiled Bootstrap styles
+ *
+ * @param ResourceLoaderContext $context
+ *
+ * @return array
+ */
+ public function getStyles( ResourceLoaderContext $context ) {
+
+ if ( $this->styleText === null ) {
+
+ $this->retrieveStylesFromCache( $context );
+
+ if ( $this->styleText === null ) {
+ $this->compileStyles( $context );
+ }
+ }
+
+ return array( 'all' => $this->styleText );
+ }
+
+ /**
+ * @see ResourceLoaderFileModule::supportsURLLoading
+ *
+ * @since 1.0
+ */
+ public function supportsURLLoading() {
+ return false;
+ }
+
+ /**
+ * @since 1.0
+ *
+ * @param BagOStuff $cache
+ */
+ public function setCache( BagOStuff $cache ) {
+ $this->cache = $cache;
+ }
+
+ protected function getCache() {
+
+ if ( $this->cache === null ) {
+ $this->cache = wfGetCache( CACHE_ANYTHING );
+ }
+
+ return $this->cache;
+ }
+
+ protected function getCacheKey( ResourceLoaderContext $context ) {
+ return wfMemcKey( 'ext', 'bootstrap', $context->getHash() );
+ }
+
+ protected function retrieveStylesFromCache( ResourceLoaderContext $context ) {
+
+ // Try for cache hit
+ $cacheResult = $this->getCache()->get( $this->getCacheKey( $context ) );
+
+ if ( is_array( $cacheResult ) ) {
+
+ if ( $this->isCacheOutdated( $cacheResult[ 'storetime' ] ) ) {
+ wfDebug( __METHOD__ . " ext.bootstrap: Cache miss: Cache outdated.\n" );
+ } else {
+ $this->styleText = $cacheResult[ 'styles' ];
+ wfDebug( __METHOD__ . " ext.bootstrap: Cache hit: Got styles from cache.\n" );
+ }
+
+ } else {
+ wfDebug( __METHOD__ . " ext.bootstrap: Cache miss: Styles not found in cache.\n" );
+ }
+ }
+
+ protected function updateCache( ResourceLoaderContext $context ) {
+
+ $this->getCache()->set(
+ $this->getCacheKey( $context ),
+ array( 'styles' => $this->styleText, 'storetime' => time() )
+ );
+ }
+
+ protected function purgeCache( ResourceLoaderContext $context ) {
+ $this->getCache()->delete( $this->getCacheKey( $context ) );
+ }
+
+ protected function compileStyles( ResourceLoaderContext $context ) {
+
+ $lessParser = new Less_Parser();
+ $remotePath = $this->getRemotePath( '' );
+
+ try {
+
+ foreach ( $this->styles as $style ) {
+ $lessParser->parseFile( $this->getLocalPath( $style ), $remotePath );
+ }
+
+ foreach ( $this->extStyles as $stylefile => $remotePath ) {
+ $lessParser->parseFile( $stylefile, $remotePath );
+ }
+
+ $lessParser->ModifyVars( $this->variables );
+
+ $this->styleText = $lessParser->getCss();
+
+ $this->updateCache( $context );
+
+ } catch ( Exception $e ) {
+
+ $this->purgeCache( $context );
+ wfDebug( $e->getMessage() );
+ $this->styleText = '/* LESS compile error: ' . $e->getMessage() . '*/';
+ }
+
+ }
+
+ /**
+ * @param mixed[] $options
+ */
+ protected function applyOptions( $options ) {
+ $mapConfigToLocalVar = array (
+ 'variables' => 'variables',
+ 'paths' => 'paths',
+ 'external styles' => 'extStyles',
+ 'cachetriggers' => 'cacheTriggers',
+ );
+
+ foreach ( $mapConfigToLocalVar as $config => $local ) {
+ if ( isset( $options[ $config ] ) ) {
+ $this->$local = $options[ $config ];
+ }
+ }
+ }
+
+ /**
+ * @param int $cacheStoreTime
+ *
+ * @return bool
+ */
+ protected function isCacheOutdated( $cacheStoreTime ) {
+
+ foreach ( $this->cacheTriggers as $triggerFile ) {
+ if ( $triggerFile !== null && $cacheStoreTime < filemtime( $triggerFile ) ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+}