diff options
author | Luke Shumaker <LukeShu@sbcglobal.net> | 2014-01-28 09:50:25 -0500 |
---|---|---|
committer | Luke Shumaker <LukeShu@sbcglobal.net> | 2014-01-28 09:50:25 -0500 |
commit | 5744df39e15f85c6cc8a9faf8924d77e76d2b216 (patch) | |
tree | a8c8dd40a94d1fa0d5377566aa5548ae55a163da /includes/resourceloader | |
parent | 4bb2aeca1d198391ca856aa16c40b8559c68daec (diff) | |
parent | 224b22a051051f6c2e494c3a2fb4adb42898e2d1 (diff) |
Merge branch 'archwiki'
Conflicts:
extensions/FluxBBAuthPlugin.php
extensions/SyntaxHighlight_GeSHi/README
extensions/SyntaxHighlight_GeSHi/SyntaxHighlight_GeSHi.class.php
extensions/SyntaxHighlight_GeSHi/SyntaxHighlight_GeSHi.i18n.php
extensions/SyntaxHighlight_GeSHi/SyntaxHighlight_GeSHi.php
extensions/SyntaxHighlight_GeSHi/geshi/docs/CHANGES
extensions/SyntaxHighlight_GeSHi/geshi/docs/THANKS
extensions/SyntaxHighlight_GeSHi/geshi/docs/TODO
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/AbstractClass.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/AbstractClass_logo.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/AbstractMethod.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/AbstractPrivateClass.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/AbstractPrivateClass_logo.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/AbstractPrivateMethod.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/Class.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/Class_logo.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/Constant.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/Constructor.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/Destructor.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/Function.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/Global.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/I.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/Index.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/Interface.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/Interface_logo.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/L.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/Lminus.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/Lplus.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/Method.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/Page.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/Page_logo.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/PrivateClass.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/PrivateClass_logo.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/PrivateMethod.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/PrivateVariable.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/StaticMethod.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/StaticVariable.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/T.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/Tminus.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/Tplus.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/Variable.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/blank.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/class_folder.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/file.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/folder.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/function_folder.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/next_button.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/next_button_disabled.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/package.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/package_folder.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/previous_button.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/previous_button_disabled.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/private_class_logo.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/tutorial.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/tutorial_folder.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/api/media/images/up_button.png
extensions/SyntaxHighlight_GeSHi/geshi/docs/geshi-doc.html
extensions/SyntaxHighlight_GeSHi/geshi/docs/geshi-doc.txt
extensions/SyntaxHighlight_GeSHi/geshi/geshi.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/4cs.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/6502acme.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/6502kickass.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/6502tasm.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/68000devpac.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/abap.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/actionscript.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/actionscript3.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/ada.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/algol68.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/apache.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/applescript.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/apt_sources.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/asm.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/asp.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/autoconf.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/autohotkey.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/autoit.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/avisynth.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/awk.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/bascomavr.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/bash.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/basic4gl.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/bf.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/bibtex.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/blitzbasic.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/bnf.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/boo.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/c.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/c_loadrunner.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/c_mac.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/caddcl.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/cadlisp.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/cfdg.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/cfm.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/chaiscript.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/cil.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/clojure.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/cmake.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/cobol.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/coffeescript.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/cpp-qt.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/cpp.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/csharp.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/css.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/cuesheet.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/d.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/dcs.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/delphi.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/diff.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/div.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/dos.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/dot.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/e.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/ecmascript.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/eiffel.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/email.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/epc.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/erlang.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/euphoria.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/f1.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/falcon.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/fo.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/fortran.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/freebasic.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/fsharp.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/gambas.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/gdb.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/genero.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/genie.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/gettext.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/glsl.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/gml.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/gnuplot.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/go.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/groovy.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/gwbasic.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/haskell.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/hicest.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/hq9plus.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/html4strict.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/html5.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/icon.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/idl.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/ini.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/inno.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/intercal.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/io.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/j.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/java.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/java5.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/javascript.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/jquery.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/kixtart.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/klonec.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/klonecpp.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/latex.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/lb.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/lisp.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/llvm.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/locobasic.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/logtalk.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/lolcode.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/lotusformulas.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/lotusscript.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/lscript.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/lsl2.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/lua.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/m68k.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/magiksf.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/make.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/mapbasic.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/matlab.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/mirc.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/mmix.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/modula2.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/modula3.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/mpasm.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/mxml.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/mysql.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/newlisp.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/nsis.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/oberon2.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/objc.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/objeck.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/ocaml-brief.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/ocaml.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/oobas.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/oracle11.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/oracle8.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/oxygene.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/oz.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/pascal.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/pcre.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/per.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/perl.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/perl6.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/pf.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/php-brief.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/php.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/pic16.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/pike.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/pixelbender.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/pli.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/plsql.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/postgresql.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/povray.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/powerbuilder.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/powershell.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/proftpd.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/progress.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/prolog.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/properties.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/providex.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/purebasic.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/pycon.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/python.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/q.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/qbasic.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/rails.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/rebol.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/reg.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/robots.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/rpmspec.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/rsplus.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/ruby.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/sas.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/scala.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/scheme.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/scilab.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/sdlbasic.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/smalltalk.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/smarty.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/sql.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/systemverilog.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/tcl.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/teraterm.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/text.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/thinbasic.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/tsql.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/typoscript.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/unicon.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/uscript.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/vala.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/vb.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/vbnet.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/verilog.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/vhdl.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/vim.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/visualfoxpro.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/visualprolog.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/whitespace.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/whois.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/winbatch.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/xbasic.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/xml.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/xorg_conf.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/xpp.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/yaml.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/z80.php
extensions/SyntaxHighlight_GeSHi/geshi/geshi/zxbasic.php
Diffstat (limited to 'includes/resourceloader')
15 files changed, 774 insertions, 376 deletions
diff --git a/includes/resourceloader/ResourceLoader.php b/includes/resourceloader/ResourceLoader.php index 7b87f9d4..6380efcf 100644 --- a/includes/resourceloader/ResourceLoader.php +++ b/includes/resourceloader/ResourceLoader.php @@ -39,7 +39,7 @@ class ResourceLoader { /** Associative array mapping module name to info associative array */ protected $moduleInfos = array(); - + /** Associative array mapping framework ids to a list of names of test suite modules */ /** like array( 'qunit' => array( 'mediawiki.tests.qunit.suites', 'ext.foo.tests', .. ), .. ) */ protected $testModuleNames = array(); @@ -47,6 +47,9 @@ class ResourceLoader { /** array( 'source-id' => array( 'loadScript' => 'http://.../load.php' ) ) **/ protected $sources = array(); + /** @var bool */ + protected $hasErrors = false; + /* Protected Methods */ /** @@ -60,7 +63,7 @@ class ResourceLoader { * requests its own information. This sacrifice of modularity yields a substantial * performance improvement. * - * @param $modules Array: List of module names to preload information for + * @param array $modules List of module names to preload information for * @param $context ResourceLoaderContext: Context to load the information within */ public function preloadModuleInfo( array $modules, ResourceLoaderContext $context ) { @@ -127,8 +130,8 @@ class ResourceLoader { * If $data is empty, only contains whitespace or the filter was unknown, * $data is returned unmodified. * - * @param $filter String: Name of filter to run - * @param $data String: Text to filter, such as JavaScript or CSS text + * @param string $filter Name of filter to run + * @param string $data Text to filter, such as JavaScript or CSS text * @return String: Filtered data, or a comment containing an error message */ protected function filter( $filter, $data ) { @@ -150,6 +153,7 @@ class ResourceLoader { $cache = wfGetCache( CACHE_ANYTHING ); $cacheEntry = $cache->get( $key ); if ( is_string( $cacheEntry ) ) { + wfIncrStats( "rl-$filter-cache-hits" ); wfProfileOut( __METHOD__ ); return $cacheEntry; } @@ -157,6 +161,7 @@ class ResourceLoader { $result = ''; // Run the filter - we've already verified one of these will work try { + wfIncrStats( "rl-$filter-cache-misses" ); switch ( $filter ) { case 'minify-js': $result = JavaScriptMinifier::minify( $data, @@ -173,10 +178,12 @@ class ResourceLoader { // Save filtered text to Memcached $cache->set( $key, $result ); - } catch ( Exception $exception ) { - // Return exception as a comment - $result = $this->makeComment( $exception->__toString() ); + } catch ( Exception $e ) { + MWExceptionHandler::logException( $e ); + wfDebugLog( 'resourceloader', __METHOD__ . ": minification failed: $e" ); $this->hasErrors = true; + // Return exception as a comment + $result = self::formatException( $e ); } wfProfileOut( __METHOD__ ); @@ -201,7 +208,7 @@ class ResourceLoader { $this->addSource( $wgResourceLoaderSources ); // Register core modules - $this->register( include( "$IP/resources/Resources.php" ) ); + $this->register( include "$IP/resources/Resources.php" ); // Register extension modules wfRunHooks( 'ResourceLoaderRegisterModules', array( &$this ) ); $this->register( $wgResourceModules ); @@ -210,7 +217,6 @@ class ResourceLoader { $this->registerTestModules(); } - wfProfileOut( __METHOD__ ); } @@ -218,7 +224,7 @@ class ResourceLoader { * Registers a module with the ResourceLoader system. * * @param $name Mixed: Name of module as a string or List of name/object pairs as an array - * @param $info array Module info array. For backwards compatibility with 1.17alpha, + * @param array $info Module info array. For backwards compatibility with 1.17alpha, * this may also be a ResourceLoaderModule object. Optional when using * multiple-registration calling style. * @throws MWException: If a duplicate module registration is attempted @@ -235,6 +241,7 @@ class ResourceLoader { foreach ( $registrations as $name => $info ) { // Disallow duplicate registrations if ( isset( $this->moduleInfos[$name] ) ) { + wfProfileOut( __METHOD__ ); // A module has already been registered by this name throw new MWException( 'ResourceLoader duplicate registration error. ' . @@ -244,6 +251,7 @@ class ResourceLoader { // Check $name for validity if ( !self::isValidModuleName( $name ) ) { + wfProfileOut( __METHOD__ ); throw new MWException( "ResourceLoader module name '$name' is invalid, see ResourceLoader::isValidModuleName()" ); } @@ -252,6 +260,7 @@ class ResourceLoader { // Old calling convention // Validate the input if ( !( $info instanceof ResourceLoaderModule ) ) { + wfProfileOut( __METHOD__ ); throw new MWException( 'ResourceLoader invalid module error. ' . 'Instances of ResourceLoaderModule expected.' ); } @@ -274,24 +283,28 @@ class ResourceLoader { global $IP, $wgEnableJavaScriptTest; if ( $wgEnableJavaScriptTest !== true ) { - throw new MWException( 'Attempt to register JavaScript test modules but <tt>$wgEnableJavaScriptTest</tt> is false. Edit your <tt>LocalSettings.php</tt> to enable it.' ); + throw new MWException( 'Attempt to register JavaScript test modules but <code>$wgEnableJavaScriptTest</code> is false. Edit your <code>LocalSettings.php</code> to enable it.' ); } wfProfileIn( __METHOD__ ); // Get core test suites $testModules = array(); - $testModules['qunit'] = include( "$IP/tests/qunit/QUnitTestResources.php" ); + $testModules['qunit'] = include "$IP/tests/qunit/QUnitTestResources.php"; // Get other test suites (e.g. from extensions) wfRunHooks( 'ResourceLoaderTestModules', array( &$testModules, &$this ) ); // Add the testrunner (which configures QUnit) to the dependencies. // Since it must be ready before any of the test suites are executed. - foreach( $testModules['qunit'] as $moduleName => $moduleProps ) { - $testModules['qunit'][$moduleName]['dependencies'][] = 'mediawiki.tests.qunit.testrunner'; + foreach ( $testModules['qunit'] as &$module ) { + // Make sure all test modules are top-loading so that when QUnit starts + // on document-ready, it will run once and finish. If some tests arrive + // later (possibly after QUnit has already finished) they will be ignored. + $module['position'] = 'top'; + $module['dependencies'][] = 'mediawiki.tests.qunit.testrunner'; } - foreach( $testModules as $id => $names ) { + foreach ( $testModules as $id => $names ) { // Register test modules $this->register( $testModules[$id] ); @@ -309,9 +322,10 @@ class ResourceLoader { * 'loadScript': URL (either fully-qualified or protocol-relative) of load.php for this source * * @param $id Mixed: source ID (string), or array( id1 => props1, id2 => props2, ... ) - * @param $properties Array: source properties + * @param array $properties source properties + * @throws MWException */ - public function addSource( $id, $properties = null) { + public function addSource( $id, $properties = null ) { // Allow multiple sources to be registered in one call if ( is_array( $id ) ) { foreach ( $id as $key => $value ) { @@ -346,18 +360,18 @@ class ResourceLoader { public function getModuleNames() { return array_keys( $this->moduleInfos ); } - - /** + + /** * Get a list of test module names for one (or all) frameworks. * If the given framework id is unknkown, or if the in-object variable is not an array, * then it will return an empty array. * - * @param $framework String: Optional. Get only the test module names for one + * @param string $framework Optional. Get only the test module names for one * particular framework. * @return Array */ public function getTestModuleNames( $framework = 'all' ) { - /// @TODO: api siteinfo prop testmodulenames modulenames + /// @todo api siteinfo prop testmodulenames modulenames if ( $framework == 'all' ) { return $this->testModuleNames; } elseif ( isset( $this->testModuleNames[$framework] ) && is_array( $this->testModuleNames[$framework] ) ) { @@ -370,7 +384,7 @@ class ResourceLoader { /** * Get the ResourceLoaderModule object for a given module name. * - * @param $name String: Module name + * @param string $name Module name * @return ResourceLoaderModule if module has been registered, null otherwise */ public function getModule( $name ) { @@ -381,6 +395,7 @@ class ResourceLoader { } // Construct the requested object $info = $this->moduleInfos[$name]; + /** @var ResourceLoaderModule $object */ if ( isset( $info['object'] ) ) { // Object given in info array $object = $info['object']; @@ -435,7 +450,6 @@ class ResourceLoader { wfProfileIn( __METHOD__ ); $errors = ''; - $this->hasErrors = false; // Split requested modules into two groups, modules and missing $modules = array(); @@ -446,11 +460,14 @@ class ResourceLoader { // Do not allow private modules to be loaded from the web. // This is a security issue, see bug 34907. if ( $module->getGroup() === 'private' ) { - $errors .= $this->makeComment( "Cannot show private module \"$name\"" ); + wfDebugLog( 'resourceloader', __METHOD__ . ": request for private module '$name' denied" ); $this->hasErrors = true; + // Add exception to the output as a comment + $errors .= self::makeComment( "Cannot show private module \"$name\"" ); + continue; } - $modules[$name] = $this->getModule( $name ); + $modules[$name] = $module; } else { $missing[] = $name; } @@ -459,13 +476,15 @@ class ResourceLoader { // Preload information needed to the mtime calculation below try { $this->preloadModuleInfo( array_keys( $modules ), $context ); - } catch( Exception $e ) { - // Add exception to the output as a comment - $errors .= $this->makeComment( $e->__toString() ); + } catch ( Exception $e ) { + MWExceptionHandler::logException( $e ); + wfDebugLog( 'resourceloader', __METHOD__ . ": preloading module info failed: $e" ); $this->hasErrors = true; + // Add exception to the output as a comment + $errors .= self::formatException( $e ); } - wfProfileIn( __METHOD__.'-getModifiedTime' ); + wfProfileIn( __METHOD__ . '-getModifiedTime' ); // To send Last-Modified and support If-Modified-Since, we need to detect // the last modified time @@ -478,13 +497,15 @@ class ResourceLoader { // Calculate maximum modified time $mtime = max( $mtime, $module->getModifiedTime( $context ) ); } catch ( Exception $e ) { - // Add exception to the output as a comment - $errors .= $this->makeComment( $e->__toString() ); + MWExceptionHandler::logException( $e ); + wfDebugLog( 'resourceloader', __METHOD__ . ": calculating maximum modified time failed: $e" ); $this->hasErrors = true; + // Add exception to the output as a comment + $errors .= self::formatException( $e ); } } - wfProfileOut( __METHOD__.'-getModifiedTime' ); + wfProfileOut( __METHOD__ . '-getModifiedTime' ); // If there's an If-Modified-Since header, respond with a 304 appropriately if ( $this->tryRespondLastModified( $context, $mtime ) ) { @@ -501,7 +522,7 @@ class ResourceLoader { // Capture any PHP warnings from the output buffer and append them to the // response in a comment if we're in debug mode. if ( $context->getDebug() && strlen( $warnings = ob_get_contents() ) ) { - $response = $this->makeComment( $warnings ) . $response; + $response = self::makeComment( $warnings ) . $response; $this->hasErrors = true; } @@ -530,8 +551,8 @@ class ResourceLoader { /** * Send content type and last modified headers to the client. * @param $context ResourceLoaderContext - * @param $mtime string TS_MW timestamp to use for last-modified - * @param $error bool Whether there are commented-out errors in the response + * @param string $mtime TS_MW timestamp to use for last-modified + * @param bool $errors Whether there are commented-out errors in the response * @return void */ protected function sendResponseHeaders( ResourceLoaderContext $context, $mtime, $errors ) { @@ -540,16 +561,17 @@ class ResourceLoader { // to propagate to clients quickly // If there were errors, we also need a shorter expiry time so we can recover quickly if ( is_null( $context->getVersion() ) || $errors ) { - $maxage = $wgResourceLoaderMaxage['unversioned']['client']; + $maxage = $wgResourceLoaderMaxage['unversioned']['client']; $smaxage = $wgResourceLoaderMaxage['unversioned']['server']; // If a version was specified we can use a longer expiry time since changing // version numbers causes cache misses } else { - $maxage = $wgResourceLoaderMaxage['versioned']['client']; + $maxage = $wgResourceLoaderMaxage['versioned']['client']; $smaxage = $wgResourceLoaderMaxage['versioned']['server']; } if ( $context->getOnly() === 'styles' ) { header( 'Content-Type: text/css; charset=utf-8' ); + header( 'Access-Control-Allow-Origin: *' ); } else { header( 'Content-Type: text/javascript; charset=utf-8' ); } @@ -569,8 +591,8 @@ class ResourceLoader { * If there's an If-Modified-Since header, respond with a 304 appropriately * and clear out the output buffer. If the client cache is too old then do nothing. * @param $context ResourceLoaderContext - * @param $mtime string The TS_MW timestamp to check the header against - * @return bool True iff 304 header sent and output handled + * @param string $mtime The TS_MW timestamp to check the header against + * @return bool True if 304 header sent and output handled */ protected function tryRespondLastModified( ResourceLoaderContext $context, $mtime ) { // If there's an If-Modified-Since header, respond with a 304 appropriately @@ -590,13 +612,7 @@ class ResourceLoader { // See also http://bugs.php.net/bug.php?id=51579 // To work around this, we tear down all output buffering before // sending the 304. - // On some setups, ob_get_level() doesn't seem to go down to zero - // no matter how often we call ob_get_clean(), so instead of doing - // the more intuitive while ( ob_get_level() > 0 ) ob_get_clean(); - // we have to be safe here and avoid an infinite loop. - for ( $i = 0; $i < ob_get_level(); $i++ ) { - ob_end_clean(); - } + wfResetOutputBuffers( /* $resetGzipEncoding = */ true ); header( 'HTTP/1.0 304 Not Modified' ); header( 'Status: 304 Not Modified' ); @@ -628,7 +644,7 @@ class ResourceLoader { if ( !$good ) { try { // RL always hits the DB on file cache miss... wfGetDB( DB_SLAVE ); - } catch( DBConnectionError $e ) { // ...check if we need to fallback to cache + } catch ( DBConnectionError $e ) { // ...check if we need to fallback to cache $good = $fileCache->isCacheGood(); // cache existence check } } @@ -657,22 +673,45 @@ class ResourceLoader { return false; // cache miss } - protected function makeComment( $text ) { + /** + * Generate a CSS or JS comment block. Only use this for public data, + * not error message details. + * + * @param $text string + * @return string + */ + public static function makeComment( $text ) { $encText = str_replace( '*/', '* /', $text ); return "/*\n$encText\n*/\n"; } /** + * Handle exception display + * + * @param Exception $e to be shown to the user + * @return string sanitized text that can be returned to the user + */ + public static function formatException( $e ) { + global $wgShowExceptionDetails; + + if ( $wgShowExceptionDetails ) { + return self::makeComment( $e->__toString() ); + } else { + return self::makeComment( wfMessage( 'internalerror' )->text() ); + } + } + + /** * Generates code for a response * * @param $context ResourceLoaderContext: Context in which to generate a response - * @param $modules Array: List of module objects keyed by module name - * @param $missing Array: List of unavailable modules (optional) + * @param array $modules List of module objects keyed by module name + * @param array $missing List of unavailable modules (optional) * @return String: Response data */ public function makeModuleResponse( ResourceLoaderContext $context, - array $modules, $missing = array() ) - { + array $modules, $missing = array() + ) { $out = ''; $exceptions = ''; if ( $modules === array() && $missing === array() ) { @@ -685,9 +724,11 @@ class ResourceLoader { try { $blobs = MessageBlobStore::get( $this, $modules, $context->getLanguage() ); } catch ( Exception $e ) { - // Add exception to the output as a comment - $exceptions .= $this->makeComment( $e->__toString() ); + MWExceptionHandler::logException( $e ); + wfDebugLog( 'resourceloader', __METHOD__ . ": pre-fetching blobs from MessageBlobStore failed: $e" ); $this->hasErrors = true; + // Add exception to the output as a comment + $exceptions .= self::formatException( $e ); } } else { $blobs = array(); @@ -791,9 +832,11 @@ class ResourceLoader { break; } } catch ( Exception $e ) { - // Add exception to the output as a comment - $exceptions .= $this->makeComment( $e->__toString() ); + MWExceptionHandler::logException( $e ); + wfDebugLog( 'resourceloader', __METHOD__ . ": generating module package failed: $e" ); $this->hasErrors = true; + // Add exception to the output as a comment + $exceptions .= self::formatException( $e ); // Register module as missing $missing[] = $name; @@ -834,7 +877,7 @@ class ResourceLoader { * Returns JS code to call to mw.loader.implement for a module with * given properties. * - * @param $name string Module name + * @param string $name Module name * @param $scripts Mixed: List of URLs to JavaScript files or String of JavaScript code * @param $styles Mixed: Array of CSS strings keyed by media type, or an array of lists of URLs to * CSS files keyed by media type @@ -842,6 +885,7 @@ class ResourceLoader { * associative array mapping message key to value, or a JSON-encoded message blob containing * the same data, wrapped in an XmlJsCode object. * + * @throws MWException * @return string */ public static function makeLoaderImplementScript( $name, $scripts, $styles, $messages ) { @@ -862,7 +906,9 @@ class ResourceLoader { // output javascript "[]" instead of "{}". This fixes that. (object)$styles, (object)$messages - ) ); + ), + ResourceLoader::inDebugMode() + ); } /** @@ -881,7 +927,7 @@ class ResourceLoader { * Combines an associative array mapping media type to CSS into a * single stylesheet with "@media" blocks. * - * @param $styles Array: Array keyed by media type containing (arrays of) CSS strings. + * @param array $stylePairs Array keyed by media type containing (arrays of) CSS strings. * * @return Array */ @@ -891,7 +937,7 @@ class ResourceLoader { // ResourceLoaderFileModule::getStyle can return the styles // as a string or an array of strings. This is to allow separation in // the front-end. - $styles = (array) $styles; + $styles = (array)$styles; foreach ( $styles as $style ) { $style = trim( $style ); // Don't output an empty "@media print { }" block (bug 40498) @@ -902,7 +948,7 @@ class ResourceLoader { if ( $media === '' || $media == 'all' ) { $out[] = $style; - } else if ( is_string( $media ) ) { + } elseif ( is_string( $media ) ) { $out[] = "@media $media {\n" . str_replace( "\n", "\n\t", "\t" . $style ) . "}"; } // else: skip @@ -941,12 +987,12 @@ class ResourceLoader { * which will have values corresponding to $name, $version, $dependencies * and $group as supplied. * - * @param $name String: Module name + * @param string $name Module name * @param $version Integer: Module version number as a timestamp - * @param $dependencies Array: List of module names on which this module depends - * @param $group String: Group which the module is in. - * @param $source String: Source of the module, or 'local' if not foreign. - * @param $script String: JavaScript code + * @param array $dependencies List of module names on which this module depends + * @param string $group Group which the module is in. + * @param string $source Source of the module, or 'local' if not foreign. + * @param string $script JavaScript code * * @return string */ @@ -974,21 +1020,21 @@ class ResourceLoader { * ) ): * Registers modules with the given names and parameters. * - * @param $name String: Module name + * @param string $name Module name * @param $version Integer: Module version number as a timestamp - * @param $dependencies Array: List of module names on which this module depends - * @param $group String: group which the module is in. - * @param $source String: source of the module, or 'local' if not foreign + * @param array $dependencies List of module names on which this module depends + * @param string $group group which the module is in. + * @param string $source source of the module, or 'local' if not foreign * * @return string */ public static function makeLoaderRegisterScript( $name, $version = null, - $dependencies = null, $group = null, $source = null ) - { + $dependencies = null, $group = null, $source = null + ) { if ( is_array( $name ) ) { return Xml::encodeJsCall( 'mw.loader.register', array( $name ) ); } else { - $version = (int) $version > 1 ? (int) $version : 1; + $version = (int)$version > 1 ? (int)$version : 1; return Xml::encodeJsCall( 'mw.loader.register', array( $name, $version, $dependencies, $group, $source ) ); } @@ -1004,8 +1050,8 @@ class ResourceLoader { * - ResourceLoader::makeLoaderSourcesScript( array( $id1 => $props1, $id2 => $props2, ... ) ); * Register sources with the given IDs and properties. * - * @param $id String: source ID - * @param $properties Array: source properties (see addSource()) + * @param string $id source ID + * @param array $properties source properties (see addSource()) * * @return string */ @@ -1021,7 +1067,7 @@ class ResourceLoader { * Returns JS code which runs given JS code if the client-side framework is * present. * - * @param $script String: JavaScript code + * @param string $script JavaScript code * * @return string */ @@ -1033,12 +1079,12 @@ class ResourceLoader { * Returns JS code which will set the MediaWiki configuration array to * the given value. * - * @param $configuration Array: List of configuration values keyed by variable name + * @param array $configuration List of configuration values keyed by variable name * * @return string */ public static function makeConfigSetScript( array $configuration ) { - return Xml::encodeJsCall( 'mw.config.set', array( $configuration ) ); + return Xml::encodeJsCall( 'mw.config.set', array( $configuration ), ResourceLoader::inDebugMode() ); } /** @@ -1046,7 +1092,7 @@ class ResourceLoader { * * For example, array( 'foo.bar', 'foo.baz', 'bar.baz', 'bar.quux' ) * becomes 'foo.bar,baz|bar.baz,quux' - * @param $modules array of module names (strings) + * @param array $modules of module names (strings) * @return string Packed query string */ public static function makePackedModulesString( $modules ) { @@ -1084,16 +1130,16 @@ class ResourceLoader { /** * Build a load.php URL - * @param $modules array of module names (strings) - * @param $lang string Language code - * @param $skin string Skin name - * @param $user string|null User name. If null, the &user= parameter is omitted - * @param $version string|null Versioning timestamp - * @param $debug bool Whether the request should be in debug mode - * @param $only string|null &only= parameter - * @param $printable bool Printable mode - * @param $handheld bool Handheld mode - * @param $extraQuery array Extra query parameters to add + * @param array $modules of module names (strings) + * @param string $lang Language code + * @param string $skin Skin name + * @param string|null $user User name. If null, the &user= parameter is omitted + * @param string|null $version Versioning timestamp + * @param bool $debug Whether the request should be in debug mode + * @param string|null $only &only= parameter + * @param bool $printable Printable mode + * @param bool $handheld Handheld mode + * @param array $extraQuery Extra query parameters to add * @return string URL to load.php. May be protocol-relative (if $wgLoadScript is procol-relative) */ public static function makeLoaderURL( $modules, $lang, $skin, $user = null, $version = null, $debug = false, $only = null, @@ -1111,6 +1157,18 @@ class ResourceLoader { /** * Build a query array (array representation of query string) for load.php. Helper * function for makeLoaderURL(). + * + * @param array $modules + * @param string $lang + * @param string $skin + * @param string $user + * @param string $version + * @param bool $debug + * @param string $only + * @param bool $printable + * @param bool $handheld + * @param array $extraQuery + * * @return array */ public static function makeLoaderQuery( $modules, $lang, $skin, $user = null, $version = null, $debug = false, $only = null, @@ -1149,10 +1207,54 @@ class ResourceLoader { * Module names may not contain pipes (|), commas (,) or exclamation marks (!) and can be * at most 255 bytes. * - * @param $moduleName string Module name to check + * @param string $moduleName Module name to check * @return bool Whether $moduleName is a valid module name */ public static function isValidModuleName( $moduleName ) { return !preg_match( '/[|,!]/', $moduleName ) && strlen( $moduleName ) <= 255; } + + /** + * Returns LESS compiler set up for use with MediaWiki + * + * @since 1.22 + * @return lessc + */ + public static function getLessCompiler() { + global $wgResourceLoaderLESSFunctions, $wgResourceLoaderLESSImportPaths; + + // When called from the installer, it is possible that a required PHP extension + // is missing (at least for now; see bug 47564). If this is the case, throw an + // exception (caught by the installer) to prevent a fatal error later on. + if ( !function_exists( 'ctype_digit' ) ) { + throw new MWException( 'lessc requires the Ctype extension' ); + } + + $less = new lessc(); + $less->setPreserveComments( true ); + $less->setVariables( self::getLESSVars() ); + $less->setImportDir( $wgResourceLoaderLESSImportPaths ); + foreach ( $wgResourceLoaderLESSFunctions as $name => $func ) { + $less->registerFunction( $name, $func ); + } + return $less; + } + + /** + * Get global LESS variables. + * + * $since 1.22 + * @return array: Map of variable names to string CSS values. + */ + public static function getLESSVars() { + global $wgResourceLoaderLESSVars; + + static $lessVars = null; + if ( $lessVars === null ) { + $lessVars = $wgResourceLoaderLESSVars; + // Sort by key to ensure consistent hashing for cache lookups. + ksort( $lessVars ); + } + return $lessVars; + } } diff --git a/includes/resourceloader/ResourceLoaderContext.php b/includes/resourceloader/ResourceLoaderContext.php index 0e96c6c8..22ff6a7e 100644 --- a/includes/resourceloader/ResourceLoaderContext.php +++ b/includes/resourceloader/ResourceLoaderContext.php @@ -58,14 +58,14 @@ class ResourceLoaderContext { // Interpret request // List of modules $modules = $request->getVal( 'modules' ); - $this->modules = $modules ? self::expandModuleNames( $modules ) : array(); + $this->modules = $modules ? self::expandModuleNames( $modules ) : array(); // Various parameters - $this->skin = $request->getVal( 'skin' ); - $this->user = $request->getVal( 'user' ); - $this->debug = $request->getFuzzyBool( 'debug', $wgResourceLoaderDebug ); - $this->only = $request->getVal( 'only' ); - $this->version = $request->getVal( 'version' ); - $this->raw = $request->getFuzzyBool( 'raw' ); + $this->skin = $request->getVal( 'skin' ); + $this->user = $request->getVal( 'user' ); + $this->debug = $request->getFuzzyBool( 'debug', $wgResourceLoaderDebug ); + $this->only = $request->getVal( 'only' ); + $this->version = $request->getVal( 'version' ); + $this->raw = $request->getFuzzyBool( 'raw' ); $skinnames = Skin::getSkinNames(); // If no skin is specified, or we don't recognize the skin, use the default skin @@ -78,7 +78,7 @@ class ResourceLoaderContext { * Expand a string of the form jquery.foo,bar|jquery.ui.baz,quux to * an array of module names like array( 'jquery.foo', 'jquery.bar', * 'jquery.ui.baz', 'jquery.ui.quux' ) - * @param $modules String Packed module name list + * @param string $modules Packed module name list * @return array of module names */ public static function expandModuleNames( $modules ) { @@ -96,7 +96,7 @@ class ResourceLoaderContext { $pos = strrpos( $group, '.' ); if ( $pos === false ) { // Prefixless modules, i.e. without dots - $retval = explode( ',', $group ); + $retval = array_merge( $retval, explode( ',', $group ) ); } else { // We have a prefix and a bunch of suffixes $prefix = substr( $group, 0, $pos ); // 'foo' @@ -145,7 +145,7 @@ class ResourceLoaderContext { public function getLanguage() { if ( $this->language === null ) { global $wgLang; - $this->language = $this->request->getVal( 'lang' ); + $this->language = $this->request->getVal( 'lang' ); if ( !$this->language ) { $this->language = $wgLang->getCode(); } diff --git a/includes/resourceloader/ResourceLoaderFileModule.php b/includes/resourceloader/ResourceLoaderFileModule.php index 8b9b7277..9ed181ed 100644 --- a/includes/resourceloader/ResourceLoaderFileModule.php +++ b/includes/resourceloader/ResourceLoaderFileModule.php @@ -113,6 +113,14 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { protected $debugRaw = true; /** Boolean: Whether mw.loader.state() call should be omitted */ protected $raw = false; + protected $targets = array( 'desktop' ); + + /** + * Boolean: Whether getStyleURLsForDebug should return raw file paths, + * or return load.php urls + */ + protected $hasGeneratedStyles = false; + /** * Array: Cache for mtime * @par Usage: @@ -135,57 +143,58 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { /** * Constructs a new module from an options array. * - * @param $options Array: List of options; if not given or empty, an empty module will be + * @param array $options List of options; if not given or empty, an empty module will be * constructed - * @param $localBasePath String: Base path to prepend to all local paths in $options. Defaults + * @param string $localBasePath Base path to prepend to all local paths in $options. Defaults * to $IP - * @param $remoteBasePath String: Base path to prepend to all remote paths in $options. Defaults + * @param string $remoteBasePath Base path to prepend to all remote paths in $options. Defaults * to $wgScriptPath * * Below is a description for the $options array: + * @throws MWException * @par Construction options: * @code - * array( - * // Base path to prepend to all local paths in $options. Defaults to $IP - * 'localBasePath' => [base path], - * // Base path to prepend to all remote paths in $options. Defaults to $wgScriptPath - * 'remoteBasePath' => [base path], - * // Equivalent of remoteBasePath, but relative to $wgExtensionAssetsPath - * 'remoteExtPath' => [base path], - * // Scripts to always include - * 'scripts' => [file path string or array of file path strings], - * // Scripts to include in specific language contexts - * 'languageScripts' => array( - * [language code] => [file path string or array of file path strings], - * ), - * // Scripts to include in specific skin contexts - * 'skinScripts' => array( - * [skin name] => [file path string or array of file path strings], - * ), - * // Scripts to include in debug contexts - * 'debugScripts' => [file path string or array of file path strings], - * // Scripts to include in the startup module - * 'loaderScripts' => [file path string or array of file path strings], - * // Modules which must be loaded before this module - * 'dependencies' => [modile name string or array of module name strings], - * // Styles to always load - * 'styles' => [file path string or array of file path strings], - * // Styles to include in specific skin contexts - * 'skinStyles' => array( - * [skin name] => [file path string or array of file path strings], - * ), - * // Messages to always load - * 'messages' => [array of message key strings], - * // Group which this module should be loaded together with - * 'group' => [group name string], - * // Position on the page to load this module at - * 'position' => ['bottom' (default) or 'top'] - * ) + * array( + * // Base path to prepend to all local paths in $options. Defaults to $IP + * 'localBasePath' => [base path], + * // Base path to prepend to all remote paths in $options. Defaults to $wgScriptPath + * 'remoteBasePath' => [base path], + * // Equivalent of remoteBasePath, but relative to $wgExtensionAssetsPath + * 'remoteExtPath' => [base path], + * // Scripts to always include + * 'scripts' => [file path string or array of file path strings], + * // Scripts to include in specific language contexts + * 'languageScripts' => array( + * [language code] => [file path string or array of file path strings], + * ), + * // Scripts to include in specific skin contexts + * 'skinScripts' => array( + * [skin name] => [file path string or array of file path strings], + * ), + * // Scripts to include in debug contexts + * 'debugScripts' => [file path string or array of file path strings], + * // Scripts to include in the startup module + * 'loaderScripts' => [file path string or array of file path strings], + * // Modules which must be loaded before this module + * 'dependencies' => [module name string or array of module name strings], + * // Styles to always load + * 'styles' => [file path string or array of file path strings], + * // Styles to include in specific skin contexts + * 'skinStyles' => array( + * [skin name] => [file path string or array of file path strings], + * ), + * // Messages to always load + * 'messages' => [array of message key strings], + * // Group which this module should be loaded together with + * 'group' => [group name string], + * // Position on the page to load this module at + * 'position' => ['bottom' (default) or 'top'] + * ) * @endcode */ public function __construct( $options = array(), $localBasePath = null, - $remoteBasePath = null ) - { + $remoteBasePath = null + ) { global $IP, $wgScriptPath, $wgResourceBasePath; $this->localBasePath = $localBasePath === null ? $IP : $localBasePath; if ( $remoteBasePath !== null ) { @@ -206,7 +215,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { case 'debugScripts': case 'loaderScripts': case 'styles': - $this->{$member} = (array) $option; + $this->{$member} = (array)$option; break; // Collated lists of file paths case 'languageScripts': @@ -225,25 +234,26 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { "'$key' given, string expected." ); } - $this->{$member}[$key] = (array) $value; + $this->{$member}[$key] = (array)$value; } break; // Lists of strings case 'dependencies': case 'messages': - $this->{$member} = (array) $option; + case 'targets': + $this->{$member} = (array)$option; break; // Single strings case 'group': case 'position': case 'localBasePath': case 'remoteBasePath': - $this->{$member} = (string) $option; + $this->{$member} = (string)$option; break; // Single booleans case 'debugRaw': case 'raw': - $this->{$member} = (bool) $option; + $this->{$member} = (bool)$option; break; } } @@ -255,8 +265,8 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { /** * Gets all scripts for a given context concatenated together. * - * @param $context ResourceLoaderContext: Context in which to generate script - * @return String: JavaScript code for $context + * @param ResourceLoaderContext $context Context in which to generate script + * @return string: JavaScript code for $context */ public function getScript( ResourceLoaderContext $context ) { $files = $this->getScriptFiles( $context ); @@ -264,7 +274,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { } /** - * @param $context ResourceLoaderContext + * @param ResourceLoaderContext $context * @return array */ public function getScriptURLsForDebug( ResourceLoaderContext $context ) { @@ -285,7 +295,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { /** * Gets loader script. * - * @return String: JavaScript code to be added to startup module + * @return string: JavaScript code to be added to startup module */ public function getLoaderScript() { if ( count( $this->loaderScripts ) == 0 ) { @@ -297,8 +307,8 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { /** * Gets all styles for a given context concatenated together. * - * @param $context ResourceLoaderContext: Context in which to generate styles - * @return String: CSS code for $context + * @param ResourceLoaderContext $context Context in which to generate styles + * @return string: CSS code for $context */ public function getStyles( ResourceLoaderContext $context ) { $styles = $this->readStyleFiles( @@ -320,16 +330,23 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { ); } } catch ( Exception $e ) { - wfDebug( __METHOD__ . " failed to update DB: $e\n" ); + wfDebugLog( 'resourceloader', __METHOD__ . ": failed to update DB: $e" ); } return $styles; } /** - * @param $context ResourceLoaderContext + * @param ResourceLoaderContext $context * @return array */ public function getStyleURLsForDebug( ResourceLoaderContext $context ) { + if ( $this->hasGeneratedStyles ) { + // Do the default behaviour of returning a url back to load.php + // but with only=styles. + return parent::getStyleURLsForDebug( $context ); + } + // Our module consists entirely of real css files, + // in debug mode we can load those directly. $urls = array(); foreach ( $this->getStyleFiles( $context ) as $mediaType => $list ) { $urls[$mediaType] = array(); @@ -343,7 +360,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { /** * Gets list of message keys used by this module. * - * @return Array: List of message keys + * @return array: List of message keys */ public function getMessages() { return $this->messages; @@ -352,7 +369,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { /** * Gets the name of the group this module should be loaded in. * - * @return String: Group name + * @return string: Group name */ public function getGroup() { return $this->group; @@ -368,7 +385,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { /** * Gets list of names of modules this module depends on. * - * @return Array: List of module names + * @return array: List of module names */ public function getDependencies() { return $this->dependencies; @@ -390,9 +407,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { * calculations on files relevant to the given language, skin and debug * mode. * - * @param $context ResourceLoaderContext: Context in which to calculate + * @param ResourceLoaderContext $context Context in which to calculate * the modified time - * @return Integer: UNIX timestamp + * @return int: UNIX timestamp * @see ResourceLoaderModule::getFileDependencies */ public function getModifiedTime( ResourceLoaderContext $context ) { @@ -437,9 +454,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { return $this->modifiedTime[$context->getHash()] = 1; } - wfProfileIn( __METHOD__.'-filemtime' ); + wfProfileIn( __METHOD__ . '-filemtime' ); $filesMtime = max( array_map( array( __CLASS__, 'safeFilemtime' ), $files ) ); - wfProfileOut( __METHOD__.'-filemtime' ); + wfProfileOut( __METHOD__ . '-filemtime' ); $this->modifiedTime[$context->getHash()] = max( $filesMtime, $this->getMsgBlobMtime( $context->getLanguage() ) ); @@ -451,7 +468,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { /* Protected Methods */ /** - * @param $path string + * @param string $path * @return string */ protected function getLocalPath( $path ) { @@ -459,7 +476,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { } /** - * @param $path string + * @param string $path * @return string */ protected function getRemotePath( $path ) { @@ -467,17 +484,28 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { } /** + * Infer the stylesheet language from a stylesheet file path. + * + * @since 1.22 + * @param string $path + * @return string: the stylesheet language name + */ + public function getStyleSheetLang( $path ) { + return preg_match( '/\.less$/i', $path ) ? 'less' : 'css'; + } + + /** * Collates file paths by option (where provided). * - * @param $list Array: List of file paths in any combination of index/path + * @param array $list List of file paths in any combination of index/path * or path/options pairs - * @param $option String: option name - * @param $default Mixed: default value if the option isn't set - * @return Array: List of file paths, collated by $option + * @param string $option option name + * @param mixed $default default value if the option isn't set + * @return array: List of file paths, collated by $option */ protected static function collateFilePathListByOption( array $list, $option, $default ) { $collatedFiles = array(); - foreach ( (array) $list as $key => $value ) { + foreach ( (array)$list as $key => $value ) { if ( is_int( $key ) ) { // File name as the value if ( !isset( $collatedFiles[$default] ) ) { @@ -499,10 +527,10 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { /** * Gets a list of element that match a key, optionally using a fallback key. * - * @param $list Array: List of lists to select from - * @param $key String: Key to look for in $map - * @param $fallback String: Key to look for in $list if $key doesn't exist - * @return Array: List of elements from $map which matched $key or $fallback, + * @param array $list List of lists to select from + * @param string $key Key to look for in $map + * @param string $fallback Key to look for in $list if $key doesn't exist + * @return array: List of elements from $map which matched $key or $fallback, * or an empty list in case of no match */ protected static function tryForKey( array $list, $key, $fallback = null ) { @@ -520,8 +548,8 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { /** * Gets a list of file paths for all scripts in this module, in order of propper execution. * - * @param $context ResourceLoaderContext: Context - * @return Array: List of file paths + * @param ResourceLoaderContext $context + * @return array: List of file paths */ protected function getScriptFiles( ResourceLoaderContext $context ) { $files = array_merge( @@ -532,14 +560,15 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { if ( $context->getDebug() ) { $files = array_merge( $files, $this->debugScripts ); } - return $files; + + return array_unique( $files ); } /** * Gets a list of file paths for all styles in this module, in order of propper inclusion. * - * @param $context ResourceLoaderContext: Context - * @return Array: List of file paths + * @param ResourceLoaderContext $context + * @return array: List of file paths */ protected function getStyleFiles( ResourceLoaderContext $context ) { return array_merge_recursive( @@ -551,10 +580,28 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { } /** + * Returns all style files used by this module + * @return array + */ + public function getAllStyleFiles() { + $files = array(); + foreach( (array)$this->styles as $key => $value ) { + if ( is_array( $value ) ) { + $path = $key; + } else { + $path = $value; + } + $files[] = $this->getLocalPath( $path ); + } + return $files; + } + + /** * Gets the contents of a list of JavaScript files. * - * @param $scripts Array: List of file paths to scripts to read, remap and concetenate - * @return String: Concatenated and remapped JavaScript data from $scripts + * @param array $scripts List of file paths to scripts to read, remap and concetenate + * @throws MWException + * @return string: Concatenated and remapped JavaScript data from $scripts */ protected function readScriptFiles( array $scripts ) { global $wgResourceLoaderValidateStaticJS; @@ -565,7 +612,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { foreach ( array_unique( $scripts ) as $fileName ) { $localPath = $this->getLocalPath( $fileName ); if ( !file_exists( $localPath ) ) { - throw new MWException( __METHOD__.": script file not found: \"$localPath\"" ); + throw new MWException( __METHOD__ . ": script file not found: \"$localPath\"" ); } $contents = file_get_contents( $localPath ); if ( $wgResourceLoaderValidateStaticJS ) { @@ -582,12 +629,12 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { /** * Gets the contents of a list of CSS files. * - * @param $styles Array: List of media type/list of file paths pairs, to read, remap and + * @param array $styles List of media type/list of file paths pairs, to read, remap and * concetenate * - * @param $flip bool + * @param bool $flip * - * @return Array: List of concatenated and remapped CSS data from $styles, + * @return array: List of concatenated and remapped CSS data from $styles, * keyed by media type */ protected function readStyleFiles( array $styles, $flip ) { @@ -613,18 +660,27 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { * * This method can be used as a callback for array_map() * - * @param $path String: File path of style file to read - * @param $flip bool + * @param string $path File path of style file to read + * @param bool $flip * - * @return String: CSS data in script file + * @return string: CSS data in script file * @throws MWException if the file doesn't exist */ protected function readStyleFile( $path, $flip ) { $localPath = $this->getLocalPath( $path ); if ( !file_exists( $localPath ) ) { - throw new MWException( __METHOD__.": style file not found: \"$localPath\"" ); + $msg = __METHOD__ . ": style file not found: \"$localPath\""; + wfDebugLog( 'resourceloader', $msg ); + throw new MWException( $msg ); + } + + if ( $this->getStyleSheetLang( $path ) === 'less' ) { + $style = $this->compileLESSFile( $localPath ); + $this->hasGeneratedStyles = true; + } else { + $style = file_get_contents( $localPath ); } - $style = file_get_contents( $localPath ); + if ( $flip ) { $style = CSSJanus::transform( $style, true, false ); } @@ -646,28 +702,75 @@ class ResourceLoaderFileModule extends ResourceLoaderModule { } /** - * Safe version of filemtime(), which doesn't throw a PHP warning if the file doesn't exist - * but returns 1 instead. - * @param $filename string File name - * @return int UNIX timestamp, or 1 if the file doesn't exist - */ - protected static function safeFilemtime( $filename ) { - if ( file_exists( $filename ) ) { - return filemtime( $filename ); - } else { - // We only ever map this function on an array if we're gonna call max() after, - // so return our standard minimum timestamps here. This is 1, not 0, because - // wfTimestamp(0) == NOW - return 1; - } - } - - /** * Get whether CSS for this module should be flipped - * @param $context ResourceLoaderContext + * @param ResourceLoaderContext $context * @return bool */ public function getFlip( $context ) { return $context->getDirection() === 'rtl'; } + + /** + * Get target(s) for the module, eg ['desktop'] or ['desktop', 'mobile'] + * + * @return array of strings + */ + public function getTargets() { + return $this->targets; + } + + /** + * Generate a cache key for a LESS file. + * + * The cache key varies on the file name and the names and values of global + * LESS variables. + * + * @since 1.22 + * @param string $fileName File name of root LESS file. + * @return string: Cache key + */ + protected static function getLESSCacheKey( $fileName ) { + $vars = json_encode( ResourceLoader::getLESSVars() ); + $hash = md5( $fileName . $vars ); + return wfMemcKey( 'resourceloader', 'less', $hash ); + } + + /** + * Compile a LESS file into CSS. + * + * If invalid, returns replacement CSS source consisting of the compilation + * error message encoded as a comment. To save work, we cache a result object + * which comprises the compiled CSS and the names & mtimes of the files + * that were processed. lessphp compares the cached & current mtimes and + * recompiles as necessary. + * + * @since 1.22 + * @param string $fileName File path of LESS source + * @return string: CSS source + */ + protected function compileLESSFile( $fileName ) { + $key = self::getLESSCacheKey( $fileName ); + $cache = wfGetCache( CACHE_ANYTHING ); + + // The input to lessc. Either an associative array representing the + // cached results of a previous compilation, or the string file name if + // no cache result exists. + $source = $cache->get( $key ); + if ( !is_array( $source ) || !isset( $source['root'] ) ) { + $source = $fileName; + } + + $compiler = ResourceLoader::getLessCompiler(); + $result = null; + + $result = $compiler->cachedCompile( $source ); + + if ( !is_array( $result ) ) { + throw new MWException( 'LESS compiler result has type ' . gettype( $result ) . '; array expected.' ); + } + + $this->localFileRefs += array_keys( $result['files'] ); + $cache->set( $key, $result ); + return $result['compiled']; + } } diff --git a/includes/resourceloader/ResourceLoaderLESSFunctions.php b/includes/resourceloader/ResourceLoaderLESSFunctions.php new file mode 100644 index 00000000..c7570f64 --- /dev/null +++ b/includes/resourceloader/ResourceLoaderLESSFunctions.php @@ -0,0 +1,67 @@ +<?php +/** + * PHP-provided functions for LESS; see docs for $wgResourceLoaderLESSFunctions + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +class ResourceLoaderLESSFunctions { + /** + * Check if an image file reference is suitable for embedding. + * An image is embeddable if it (a) exists, (b) has a suitable MIME-type, + * (c) does not exceed IE<9 size limit of 32kb. This is a LESS predicate + * function; it returns a LESS boolean value and can thus be used as a + * mixin guard. + * + * @par Example: + * @code + * .background-image(@url) when(embeddable(@url)) { + * background-image: url(@url) !ie; + * } + * @endcode + */ + public static function embeddable( $frame, $less ) { + $base = pathinfo( $less->parser->sourceName, PATHINFO_DIRNAME ); + $url = $frame[2][0]; + $file = realpath( $base . '/' . $url ); + return $less->toBool( $file + && strpos( $url, '//' ) === false + && filesize( $file ) < CSSMin::EMBED_SIZE_LIMIT + && CSSMin::getMimeType( $file ) !== false ); + } + + /** + * Convert an image URI to a base64-encoded data URI. + * + * @par Example: + * @code + * .fancy-button { + * background-image: embed('../images/button-bg.png'); + * } + * @endcode + */ + public static function embed( $frame, $less ) { + $base = pathinfo( $less->parser->sourceName, PATHINFO_DIRNAME ); + $url = $frame[2][0]; + $file = realpath( $base . '/' . $url ); + + $data = CSSMin::encodeImageAsDataURI( $file ); + $less->addParsedFile( $file ); + return 'url(' . $data . ')'; + } +} diff --git a/includes/resourceloader/ResourceLoaderLanguageDataModule.php b/includes/resourceloader/ResourceLoaderLanguageDataModule.php index c916c4a5..fa0fbf85 100644 --- a/includes/resourceloader/ResourceLoaderLanguageDataModule.php +++ b/includes/resourceloader/ResourceLoaderLanguageDataModule.php @@ -28,6 +28,7 @@ class ResourceLoaderLanguageDataModule extends ResourceLoaderModule { protected $language; + protected $targets = array( 'desktop', 'mobile' ); /** * Get the grammar forms for the site content language. * @@ -47,33 +48,47 @@ class ResourceLoaderLanguageDataModule extends ResourceLoaderModule { } /** + * Get the digit groupin Pattern for the site content language. + * + * @return array + */ + protected function getDigitGroupingPattern() { + return $this->language->digitGroupingPattern(); + } + + /** * Get the digit transform table for the content language - * Seperator transform table also required here to convert - * the . and , sign to appropriate forms in content language. * * @return array */ protected function getDigitTransformTable() { - $digitTransformTable = $this->language->digitTransformTable(); - $separatorTransformTable = $this->language->separatorTransformTable(); - if ( $digitTransformTable ) { - array_merge( $digitTransformTable, (array)$separatorTransformTable ); - } else { - return $separatorTransformTable; - } - return $digitTransformTable; + return $this->language->digitTransformTable(); } /** - * Get all the dynamic data for the content language to an array + * Get seperator transform table required for converting + * the . and , sign to appropriate forms in site content language. + * + * @return array + */ + protected function getSeparatorTransformTable() { + return $this->language->separatorTransformTable(); + } + + /** + * Get all the dynamic data for the content language to an array. + * + * NOTE: Before calling this you HAVE to make sure $this->language is set. * * @return array */ protected function getData() { return array( 'digitTransformTable' => $this->getDigitTransformTable(), + 'separatorTransformTable' => $this->getSeparatorTransformTable(), 'grammarForms' => $this->getSiteLangGrammarForms(), 'pluralRules' => $this->getPluralRules(), + 'digitGroupingPattern' => $this->getDigitGroupingPattern(), ); } @@ -91,26 +106,20 @@ class ResourceLoaderLanguageDataModule extends ResourceLoaderModule { /** * @param $context ResourceLoaderContext - * @return array|int|Mixed + * @return int: UNIX timestamp */ public function getModifiedTime( ResourceLoaderContext $context ) { - $this->language = Language::factory( $context ->getLanguage() ); - $cache = wfGetCache( CACHE_ANYTHING ); - $key = wfMemcKey( 'resourceloader', 'langdatamodule', 'changeinfo' ); + return max( 1, $this->getHashMtime( $context ) ); + } - $data = $this->getData(); - $hash = md5( serialize( $data ) ); + /** + * @param $context ResourceLoaderContext + * @return string: Hash + */ + public function getModifiedHash( ResourceLoaderContext $context ) { + $this->language = Language::factory( $context->getLanguage() ); - $result = $cache->get( $key ); - if ( is_array( $result ) && $result['hash'] === $hash ) { - return $result['timestamp']; - } - $timestamp = wfTimestamp(); - $cache->set( $key, array( - 'hash' => $hash, - 'timestamp' => $timestamp, - ) ); - return $timestamp; + return md5( serialize( $this->getData() ) ); } /** diff --git a/includes/resourceloader/ResourceLoaderModule.php b/includes/resourceloader/ResourceLoaderModule.php index 9c49c45f..11264fc8 100644 --- a/includes/resourceloader/ResourceLoaderModule.php +++ b/includes/resourceloader/ResourceLoaderModule.php @@ -58,6 +58,7 @@ abstract class ResourceLoaderModule { /* Protected Members */ protected $name = null; + protected $targets = array( 'desktop' ); // In-object cache for file dependencies protected $fileDeps = array(); @@ -70,17 +71,17 @@ abstract class ResourceLoaderModule { * Get this module's name. This is set when the module is registered * with ResourceLoader::register() * - * @return Mixed: Name (string) or null if no name was set + * @return mixed: Name (string) or null if no name was set */ public function getName() { return $this->name; } /** - * Set this module's name. This is called by ResourceLodaer::register() + * Set this module's name. This is called by ResourceLoader::register() * when registering the module. Other code should not call this. * - * @param $name String: Name + * @param string $name Name */ public function setName( $name ) { $this->name = $name; @@ -90,25 +91,25 @@ abstract class ResourceLoaderModule { * Get this module's origin. This is set when the module is registered * with ResourceLoader::register() * - * @return Int ResourceLoaderModule class constant, the subclass default - * if not set manuall + * @return int: ResourceLoaderModule class constant, the subclass default + * if not set manually */ public function getOrigin() { return $this->origin; } /** - * Set this module's origin. This is called by ResourceLodaer::register() + * Set this module's origin. This is called by ResourceLoader::register() * when registering the module. Other code should not call this. * - * @param $origin Int origin + * @param int $origin origin */ public function setOrigin( $origin ) { $this->origin = $origin; } /** - * @param $context ResourceLoaderContext + * @param ResourceLoaderContext $context * @return bool */ public function getFlip( $context ) { @@ -121,8 +122,8 @@ abstract class ResourceLoaderModule { * Get all JS for this module for a given language and skin. * Includes all relevant JS except loader scripts. * - * @param $context ResourceLoaderContext: Context object - * @return String: JavaScript code + * @param ResourceLoaderContext $context + * @return string: JavaScript code */ public function getScript( ResourceLoaderContext $context ) { // Stub, override expected @@ -140,8 +141,8 @@ abstract class ResourceLoaderModule { * #2 is important to prevent an infinite loop, therefore this function * MUST return either an only= URL or a non-load.php URL. * - * @param $context ResourceLoaderContext: Context object - * @return Array of URLs + * @param ResourceLoaderContext $context + * @return array: Array of URLs */ public function getScriptURLsForDebug( ResourceLoaderContext $context ) { $url = ResourceLoader::makeLoaderURL( @@ -171,8 +172,8 @@ abstract class ResourceLoaderModule { /** * Get all CSS for this module for a given skin. * - * @param $context ResourceLoaderContext: Context object - * @return Array: List of CSS strings or array of CSS strings keyed by media type. + * @param ResourceLoaderContext $context + * @return array: List of CSS strings or array of CSS strings keyed by media type. * like array( 'screen' => '.foo { width: 0 }' ); * or array( 'screen' => array( '.foo { width: 0 }' ) ); */ @@ -187,8 +188,8 @@ abstract class ResourceLoaderModule { * the module, but file-based modules will want to override this to * load the files directly. See also getScriptURLsForDebug() * - * @param $context ResourceLoaderContext: Context object - * @return Array: array( mediaType => array( URL1, URL2, ... ), ... ) + * @param ResourceLoaderContext $context + * @return array: array( mediaType => array( URL1, URL2, ... ), ... ) */ public function getStyleURLsForDebug( ResourceLoaderContext $context ) { $url = ResourceLoader::makeLoaderURL( @@ -210,7 +211,7 @@ abstract class ResourceLoaderModule { * * To get a JSON blob with messages, use MessageBlobStore::get() * - * @return Array: List of message keys. Keys may occur more than once + * @return array: List of message keys. Keys may occur more than once */ public function getMessages() { // Stub, override expected @@ -220,7 +221,7 @@ abstract class ResourceLoaderModule { /** * Get the group this module is in. * - * @return String: Group name + * @return string: Group name */ public function getGroup() { // Stub, override expected @@ -230,7 +231,7 @@ abstract class ResourceLoaderModule { /** * Get the origin of this module. Should only be overridden for foreign modules. * - * @return String: Origin name, 'local' for local modules + * @return string: Origin name, 'local' for local modules */ public function getSource() { // Stub, override expected @@ -262,7 +263,7 @@ abstract class ResourceLoaderModule { /** * Get the loader JS for this module, if set. * - * @return Mixed: JavaScript loader code as a string or boolean false if no custom loader set + * @return mixed: JavaScript loader code as a string or boolean false if no custom loader set */ public function getLoaderScript() { // Stub, override expected @@ -273,16 +274,11 @@ abstract class ResourceLoaderModule { * Get a list of modules this module depends on. * * Dependency information is taken into account when loading a module - * on the client side. When adding a module on the server side, - * dependency information is NOT taken into account and YOU are - * responsible for adding dependent modules as well. If you don't do - * this, the client side loader will send a second request back to the - * server to fetch the missing modules, which kind of defeats the - * purpose of the resource loader. + * on the client side. * * To add dependencies dynamically on the client side, use a custom * loader script, see getLoaderScript() - * @return Array: List of module names as strings + * @return array: List of module names as strings */ public function getDependencies() { // Stub, override expected @@ -290,11 +286,20 @@ abstract class ResourceLoaderModule { } /** + * Get target(s) for the module, eg ['desktop'] or ['desktop', 'mobile'] + * + * @return array: Array of strings + */ + public function getTargets() { + return $this->targets; + } + + /** * Get the files this module depends on indirectly for a given skin. * Currently these are only image files referenced by the module's CSS. * - * @param $skin String: Skin name - * @return Array: List of files + * @param string $skin Skin name + * @return array: List of files */ public function getFileDependencies( $skin ) { // Try in-object cache first @@ -309,7 +314,7 @@ abstract class ResourceLoaderModule { ), __METHOD__ ); if ( !is_null( $deps ) ) { - $this->fileDeps[$skin] = (array) FormatJson::decode( $deps, true ); + $this->fileDeps[$skin] = (array)FormatJson::decode( $deps, true ); } else { $this->fileDeps[$skin] = array(); } @@ -319,8 +324,8 @@ abstract class ResourceLoaderModule { /** * Set preloaded file dependency information. Used so we can load this * information for all modules at once. - * @param $skin String: Skin name - * @param $deps Array: Array of file names + * @param string $skin Skin name + * @param array $deps Array of file names */ public function setFileDependencies( $skin, $deps ) { $this->fileDeps[$skin] = $deps; @@ -329,13 +334,14 @@ abstract class ResourceLoaderModule { /** * Get the last modification timestamp of the message blob for this * module in a given language. - * @param $lang String: Language code - * @return Integer: UNIX timestamp, or 0 if the module doesn't have messages + * @param string $lang Language code + * @return int: UNIX timestamp, or 0 if the module doesn't have messages */ public function getMsgBlobMtime( $lang ) { if ( !isset( $this->msgBlobMtime[$lang] ) ) { - if ( !count( $this->getMessages() ) ) + if ( !count( $this->getMessages() ) ) { return 0; + } $dbr = wfGetDB( DB_SLAVE ); $msgBlobMtime = $dbr->selectField( 'msg_resource', 'mr_timestamp', array( @@ -356,7 +362,7 @@ abstract class ResourceLoaderModule { /** * Set a preloaded message blob last modification timestamp. Used so we * can load this information for all modules at once. - * @param $lang String: Language code + * @param string $lang Language code * @param $mtime Integer: UNIX timestamp or 0 if there is no such blob */ public function setMsgBlobMtime( $lang, $mtime ) { @@ -375,9 +381,13 @@ abstract class ResourceLoaderModule { * NOTE: The mtime of the module's messages is NOT automatically included. * If you want this to happen, you'll need to call getMsgBlobMtime() * yourself and take its result into consideration. - * - * @param $context ResourceLoaderContext: Context object - * @return Integer: UNIX timestamp + * + * NOTE: The mtime of the module's hash is NOT automatically included. + * If your module provides a getModifiedHash() method, you'll need to call getHashMtime() + * yourself and take its result into consideration. + * + * @param ResourceLoaderContext $context Context object + * @return integer UNIX timestamp */ public function getModifiedTime( ResourceLoaderContext $context ) { // 0 would mean now @@ -385,19 +395,59 @@ abstract class ResourceLoaderModule { } /** + * Helper method for calculating when the module's hash (if it has one) changed. + * + * @param ResourceLoaderContext $context + * @return integer: UNIX timestamp or 0 if there is no hash provided + */ + public function getHashMtime( ResourceLoaderContext $context ) { + $hash = $this->getModifiedHash( $context ); + if ( !is_string( $hash ) ) { + return 0; + } + + $cache = wfGetCache( CACHE_ANYTHING ); + $key = wfMemcKey( 'resourceloader', 'modulemodifiedhash', $this->getName() ); + + $data = $cache->get( $key ); + if ( is_array( $data ) && $data['hash'] === $hash ) { + // Hash is still the same, re-use the timestamp of when we first saw this hash. + return $data['timestamp']; + } + + $timestamp = wfTimestamp(); + $cache->set( $key, array( + 'hash' => $hash, + 'timestamp' => $timestamp, + ) ); + + return $timestamp; + } + + /** + * Get the last modification timestamp of the message blob for this + * module in a given language. + * + * @param ResourceLoaderContext $context + * @return string|null: Hash + */ + public function getModifiedHash( ResourceLoaderContext $context ) { + return null; + } + + /** * Check whether this module is known to be empty. If a child class * has an easy and cheap way to determine that this module is * definitely going to be empty, it should override this method to * return true in that case. Callers may optimize the request for this * module away if this function returns true. - * @param $context ResourceLoaderContext: Context object - * @return Boolean + * @param ResourceLoaderContext $context + * @return bool */ public function isKnownEmpty( ResourceLoaderContext $context ) { return false; } - /** @var JSParser lazy-initialized; use self::javaScriptParser() */ private static $jsParser; private static $parseCacheVersion = 1; @@ -408,7 +458,7 @@ abstract class ResourceLoaderModule { * * @param string $fileName * @param string $contents - * @return string JS with the original, or a replacement error + * @return string: JS with the original, or a replacement error */ protected function validateScriptFile( $fileName, $contents ) { global $wgResourceLoaderValidateJS; @@ -426,10 +476,10 @@ abstract class ResourceLoaderModule { try { $parser->parse( $contents, $fileName, 1 ); $result = $contents; - } catch (Exception $e) { + } catch ( Exception $e ) { // We'll save this to cache to avoid having to validate broken JS over and over... $err = $e->getMessage(); - $result = "throw new Error(" . Xml::encodeJsVar("JavaScript parse error: $err") . ");"; + $result = "throw new Error(" . Xml::encodeJsVar( "JavaScript parse error: $err" ) . ");"; } $cache->set( $key, $result ); @@ -449,4 +499,20 @@ abstract class ResourceLoaderModule { return self::$jsParser; } + /** + * Safe version of filemtime(), which doesn't throw a PHP warning if the file doesn't exist + * but returns 1 instead. + * @param string $filename File name + * @return int UNIX timestamp, or 1 if the file doesn't exist + */ + protected static function safeFilemtime( $filename ) { + if ( file_exists( $filename ) ) { + return filemtime( $filename ); + } else { + // We only ever map this function on an array if we're gonna call max() after, + // so return our standard minimum timestamps here. This is 1, not 0, because + // wfTimestamp(0) == NOW + return 1; + } + } } diff --git a/includes/resourceloader/ResourceLoaderNoscriptModule.php b/includes/resourceloader/ResourceLoaderNoscriptModule.php index 8e81c8d9..bd026f3f 100644 --- a/includes/resourceloader/ResourceLoaderNoscriptModule.php +++ b/includes/resourceloader/ResourceLoaderNoscriptModule.php @@ -45,7 +45,7 @@ class ResourceLoaderNoscriptModule extends ResourceLoaderWikiModule { /** * Gets group name - * + * * @return String: Name of group */ public function getGroup() { diff --git a/includes/resourceloader/ResourceLoaderSiteModule.php b/includes/resourceloader/ResourceLoaderSiteModule.php index 03fe1fe5..05754d37 100644 --- a/includes/resourceloader/ResourceLoaderSiteModule.php +++ b/includes/resourceloader/ResourceLoaderSiteModule.php @@ -37,20 +37,19 @@ class ResourceLoaderSiteModule extends ResourceLoaderWikiModule { * @return Array: List of pages */ protected function getPages( ResourceLoaderContext $context ) { - global $wgHandheldStyle; + global $wgUseSiteJs, $wgUseSiteCss; - $pages = array( - 'MediaWiki:Common.js' => array( 'type' => 'script' ), - 'MediaWiki:Common.css' => array( 'type' => 'style' ), - 'MediaWiki:' . ucfirst( $context->getSkin() ) . '.js' => array( 'type' => 'script' ), - 'MediaWiki:' . ucfirst( $context->getSkin() ) . '.css' => array( 'type' => 'style' ), - 'MediaWiki:Print.css' => array( 'type' => 'style', 'media' => 'print' ), - ); - if ( $wgHandheldStyle ) { - $pages['MediaWiki:Handheld.css'] = array( - 'type' => 'style', - 'media' => 'handheld' ); + $pages = array(); + if ( $wgUseSiteJs ) { + $pages['MediaWiki:Common.js'] = array( 'type' => 'script' ); + $pages['MediaWiki:' . ucfirst( $context->getSkin() ) . '.js'] = array( 'type' => 'script' ); } + if ( $wgUseSiteCss ) { + $pages['MediaWiki:Common.css'] = array( 'type' => 'style' ); + $pages['MediaWiki:' . ucfirst( $context->getSkin() ) . '.css'] = array( 'type' => 'style' ); + + } + $pages['MediaWiki:Print.css'] = array( 'type' => 'style', 'media' => 'print' ); return $pages; } @@ -58,7 +57,7 @@ class ResourceLoaderSiteModule extends ResourceLoaderWikiModule { /** * Gets group name - * + * * @return String: Name of group */ public function getGroup() { diff --git a/includes/resourceloader/ResourceLoaderStartUpModule.php b/includes/resourceloader/ResourceLoaderStartUpModule.php index 20ee83f9..20f6e0ba 100644 --- a/includes/resourceloader/ResourceLoaderStartUpModule.php +++ b/includes/resourceloader/ResourceLoaderStartUpModule.php @@ -27,6 +27,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule { /* Protected Members */ protected $modifiedTime = array(); + protected $targets = array( 'desktop', 'mobile' ); /* Protected Methods */ @@ -51,7 +52,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule { */ $namespaceIds = $wgContLang->getNamespaceIds(); $caseSensitiveNamespaces = array(); - foreach( MWNamespace::getCanonicalNamespaces() as $index => $name ) { + foreach ( MWNamespace::getCanonicalNamespaces() as $index => $name ) { $namespaceIds[$wgContLang->lc( $name )] = $index; if ( !MWNamespace::isCapitalized( $index ) ) { $caseSensitiveNamespaces[] = $index; @@ -83,7 +84,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule { 'wgFormattedNamespaces' => $wgContLang->getFormattedNamespaces(), 'wgNamespaceIds' => $namespaceIds, 'wgSiteName' => $wgSitename, - 'wgFileExtensions' => array_values( $wgFileExtensions ), + 'wgFileExtensions' => array_values( array_unique( $wgFileExtensions ) ), 'wgDBname' => $wgDBname, // This sucks, it is only needed on Special:Upload, but I could // not find a way to add vars only for a certain module @@ -94,6 +95,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule { 'wgCookiePrefix' => $wgCookiePrefix, 'wgResourceLoaderMaxQueryLength' => $wgResourceLoaderMaxQueryLength, 'wgCaseSensitiveNamespaces' => $caseSensitiveNamespaces, + 'wgLegalTitleChars' => Title::convertByteClassToUnicodeClass( Title::legalChars() ), ); wfRunHooks( 'ResourceLoaderGetConfigVars', array( &$vars ) ); @@ -114,6 +116,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule { $out = ''; $registrations = array(); $resourceLoader = $context->getResourceLoader(); + $target = $context->getRequest()->getVal( 'target', 'desktop' ); // Register sources $out .= ResourceLoader::makeLoaderSourcesScript( $resourceLoader->getSources() ); @@ -121,6 +124,10 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule { // Register modules foreach ( $resourceLoader->getModuleNames() as $name ) { $module = $resourceLoader->getModule( $name ); + $moduleTargets = $module->getTargets(); + if ( !in_array( $target, $moduleTargets ) ) { + continue; + } $deps = $module->getDependencies(); $group = $module->getGroup(); $source = $module->getSource(); @@ -130,33 +137,33 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule { $version = wfTimestamp( TS_ISO_8601_BASIC, $module->getModifiedTime( $context ) ); $out .= ResourceLoader::makeCustomLoaderScript( $name, $version, $deps, $group, $source, $loader ); + continue; } + // Automatically register module + // getModifiedTime() is supposed to return a UNIX timestamp, but it doesn't always + // seem to do that, and custom implementations might forget. Coerce it to TS_UNIX + $moduleMtime = wfTimestamp( TS_UNIX, $module->getModifiedTime( $context ) ); + $mtime = max( $moduleMtime, wfTimestamp( TS_UNIX, $wgCacheEpoch ) ); + // Modules without dependencies, a group or a foreign source pass two arguments (name, timestamp) to + // mw.loader.register() + if ( !count( $deps ) && $group === null && $source === 'local' ) { + $registrations[] = array( $name, $mtime ); + } + // Modules with dependencies but no group or foreign source pass three arguments + // (name, timestamp, dependencies) to mw.loader.register() + elseif ( $group === null && $source === 'local' ) { + $registrations[] = array( $name, $mtime, $deps ); + } + // Modules with a group but no foreign source pass four arguments (name, timestamp, dependencies, group) + // to mw.loader.register() + elseif ( $source === 'local' ) { + $registrations[] = array( $name, $mtime, $deps, $group ); + } + // Modules with a foreign source pass five arguments (name, timestamp, dependencies, group, source) + // to mw.loader.register() else { - // getModifiedTime() is supposed to return a UNIX timestamp, but it doesn't always - // seem to do that, and custom implementations might forget. Coerce it to TS_UNIX - $moduleMtime = wfTimestamp( TS_UNIX, $module->getModifiedTime( $context ) ); - $mtime = max( $moduleMtime, wfTimestamp( TS_UNIX, $wgCacheEpoch ) ); - // Modules without dependencies, a group or a foreign source pass two arguments (name, timestamp) to - // mw.loader.register() - if ( !count( $deps ) && $group === null && $source === 'local' ) { - $registrations[] = array( $name, $mtime ); - } - // Modules with dependencies but no group or foreign source pass three arguments - // (name, timestamp, dependencies) to mw.loader.register() - elseif ( $group === null && $source === 'local' ) { - $registrations[] = array( $name, $mtime, $deps ); - } - // Modules with a group but no foreign source pass four arguments (name, timestamp, dependencies, group) - // to mw.loader.register() - elseif ( $source === 'local' ) { - $registrations[] = array( $name, $mtime, $deps, $group ); - } - // Modules with a foreign source pass five arguments (name, timestamp, dependencies, group, source) - // to mw.loader.register() - else { - $registrations[] = array( $name, $mtime, $deps, $group, $source ); - } + $registrations[] = array( $name, $mtime, $deps, $group, $source ); } } $out .= ResourceLoader::makeLoaderRegisterScript( $registrations ); @@ -179,7 +186,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule { * @return string */ public function getScript( ResourceLoaderContext $context ) { - global $IP, $wgLoadScript, $wgLegacyJavaScriptGlobals; + global $IP, $wgLegacyJavaScriptGlobals; $out = file_get_contents( "$IP/resources/startup.js" ); if ( $context->getOnly() === 'scripts' ) { @@ -219,7 +226,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule { "};\n"; // Conditional script injection - $scriptTag = Html::linkedScript( $wgLoadScript . '?' . wfArrayToCGI( $query ) ); + $scriptTag = Html::linkedScript( wfAppendQuery( wfScript( 'load' ), $query ) ); $out .= "if ( isCompatible() ) {\n" . "\t" . Xml::encodeJsCall( 'document.write', array( $scriptTag ) ) . "}\n" . diff --git a/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php b/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php index 0e95d964..bda86539 100644 --- a/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php +++ b/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php @@ -48,7 +48,7 @@ class ResourceLoaderUserCSSPrefsModule extends ResourceLoaderModule { global $wgUser; return $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $wgUser->getTouched() ); } - + /** * @param $context ResourceLoaderContext * @return array @@ -56,43 +56,44 @@ class ResourceLoaderUserCSSPrefsModule extends ResourceLoaderModule { public function getStyles( ResourceLoaderContext $context ) { global $wgAllowUserCssPrefs, $wgUser; - if ( $wgAllowUserCssPrefs ) { - $options = $wgUser->getOptions(); + if ( !$wgAllowUserCssPrefs ) { + return array(); + } - // Build CSS rules - $rules = array(); + $options = $wgUser->getOptions(); - // Underline: 2 = browser default, 1 = always, 0 = never - if ( $options['underline'] < 2 ) { - $rules[] = "a { text-decoration: " . - ( $options['underline'] ? 'underline' : 'none' ) . "; }"; - } else { - # The scripts of these languages are very hard to read with underlines - $rules[] = 'a:lang(ar), a:lang(ckb), a:lang(fa),a:lang(kk-arab), ' . - 'a:lang(mzn), a:lang(ps), a:lang(ur) { text-decoration: none; }'; - } - if ( $options['justify'] ) { - $rules[] = "#article, #bodyContent, #mw_content { text-align: justify; }\n"; - } - if ( !$options['showtoc'] ) { - $rules[] = "#toc { display: none; }\n"; - } - if ( !$options['editsection'] ) { - $rules[] = ".editsection { display: none; }\n"; - } - if ( $options['editfont'] !== 'default' ) { - // Double-check that $options['editfont'] consists of safe characters only - if ( preg_match( '/^[a-zA-Z0-9_, -]+$/', $options['editfont'] ) ) { - $rules[] = "textarea { font-family: {$options['editfont']}; }\n"; - } - } - $style = implode( "\n", $rules ); - if ( $this->getFlip( $context ) ) { - $style = CSSJanus::transform( $style, true, false ); + // Build CSS rules + $rules = array(); + + // Underline: 2 = browser default, 1 = always, 0 = never + if ( $options['underline'] < 2 ) { + $rules[] = "a { text-decoration: " . + ( $options['underline'] ? 'underline' : 'none' ) . "; }"; + } else { + # The scripts of these languages are very hard to read with underlines + $rules[] = 'a:lang(ar), a:lang(ckb), a:lang(kk-arab), ' . + 'a:lang(mzn), a:lang(ps), a:lang(ur) { text-decoration: none; }'; + } + if ( $options['justify'] ) { + $rules[] = "#article, #bodyContent, #mw_content { text-align: justify; }\n"; + } + if ( !$options['showtoc'] ) { + $rules[] = "#toc { display: none; }\n"; + } + if ( !$options['editsection'] ) { + $rules[] = ".mw-editsection { display: none; }\n"; + } + if ( $options['editfont'] !== 'default' ) { + // Double-check that $options['editfont'] consists of safe characters only + if ( preg_match( '/^[a-zA-Z0-9_, -]+$/', $options['editfont'] ) ) { + $rules[] = "textarea { font-family: {$options['editfont']}; }\n"; } - return array( 'all' => $style ); } - return array(); + $style = implode( "\n", $rules ); + if ( $this->getFlip( $context ) ) { + $style = CSSJanus::transform( $style, true, false ); + } + return array( 'all' => $style ); } /** diff --git a/includes/resourceloader/ResourceLoaderUserGroupsModule.php b/includes/resourceloader/ResourceLoaderUserGroupsModule.php index 1316f423..9064263f 100644 --- a/includes/resourceloader/ResourceLoaderUserGroupsModule.php +++ b/includes/resourceloader/ResourceLoaderUserGroupsModule.php @@ -33,12 +33,15 @@ class ResourceLoaderUserGroupsModule extends ResourceLoaderWikiModule { * @return array */ protected function getPages( ResourceLoaderContext $context ) { - global $wgUser; + global $wgUser, $wgUseSiteJs, $wgUseSiteCss; $userName = $context->getUser(); if ( $userName === null ) { return array(); } + if ( !$wgUseSiteJs && !$wgUseSiteCss ) { + return array(); + } // Use $wgUser is possible; allows to skip a lot of code if ( is_object( $wgUser ) && $wgUser->getName() == $userName ) { @@ -51,12 +54,16 @@ class ResourceLoaderUserGroupsModule extends ResourceLoaderWikiModule { } $pages = array(); - foreach( $user->getEffectiveGroups() as $group ) { + foreach ( $user->getEffectiveGroups() as $group ) { if ( in_array( $group, array( '*', 'user' ) ) ) { continue; } - $pages["MediaWiki:Group-$group.js"] = array( 'type' => 'script' ); - $pages["MediaWiki:Group-$group.css"] = array( 'type' => 'style' ); + if ( $wgUseSiteJs ) { + $pages["MediaWiki:Group-$group.js"] = array( 'type' => 'script' ); + } + if ( $wgUseSiteCss ) { + $pages["MediaWiki:Group-$group.css"] = array( 'type' => 'style' ); + } } return $pages; } diff --git a/includes/resourceloader/ResourceLoaderUserModule.php b/includes/resourceloader/ResourceLoaderUserModule.php index 177302c5..7a04e473 100644 --- a/includes/resourceloader/ResourceLoaderUserModule.php +++ b/includes/resourceloader/ResourceLoaderUserModule.php @@ -35,11 +35,15 @@ class ResourceLoaderUserModule extends ResourceLoaderWikiModule { * @return array */ protected function getPages( ResourceLoaderContext $context ) { + global $wgAllowUserJs, $wgAllowUserCss; $username = $context->getUser(); if ( $username === null ) { return array(); } + if ( !$wgAllowUserJs && !$wgAllowUserCss ) { + return array(); + } // Get the normalized title of the user's user page $userpageTitle = Title::makeTitleSafe( NS_USER, $username ); @@ -50,14 +54,15 @@ class ResourceLoaderUserModule extends ResourceLoaderWikiModule { $userpage = $userpageTitle->getPrefixedDBkey(); // Needed so $excludepages works - $pages = array( - "$userpage/common.js" => array( 'type' => 'script' ), - "$userpage/" . $context->getSkin() . '.js' => - array( 'type' => 'script' ), - "$userpage/common.css" => array( 'type' => 'style' ), - "$userpage/" . $context->getSkin() . '.css' => - array( 'type' => 'style' ), - ); + $pages = array(); + if ( $wgAllowUserJs ) { + $pages["$userpage/common.js"] = array( 'type' => 'script' ); + $pages["$userpage/" . $context->getSkin() . '.js'] = array( 'type' => 'script' ); + } + if ( $wgAllowUserCss ) { + $pages["$userpage/common.css"] = array( 'type' => 'style' ); + $pages["$userpage/" . $context->getSkin() . '.css'] = array( 'type' => 'style' ); + } // Hack for bug 26283: if we're on a preview page for a CSS/JS page, // we need to exclude that page from this module. In that case, the excludepage diff --git a/includes/resourceloader/ResourceLoaderUserOptionsModule.php b/includes/resourceloader/ResourceLoaderUserOptionsModule.php index 4624cbce..0b7e1964 100644 --- a/includes/resourceloader/ResourceLoaderUserOptionsModule.php +++ b/includes/resourceloader/ResourceLoaderUserOptionsModule.php @@ -56,7 +56,9 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule { public function getScript( ResourceLoaderContext $context ) { global $wgUser; return Xml::encodeJsCall( 'mw.user.options.set', - array( $wgUser->getOptions() ) ); + array( $wgUser->getOptions() ), + ResourceLoader::inDebugMode() + ); } /** diff --git a/includes/resourceloader/ResourceLoaderUserTokensModule.php b/includes/resourceloader/ResourceLoaderUserTokensModule.php index 62d096a6..92ebbe93 100644 --- a/includes/resourceloader/ResourceLoaderUserTokensModule.php +++ b/includes/resourceloader/ResourceLoaderUserTokensModule.php @@ -35,15 +35,15 @@ class ResourceLoaderUserTokensModule extends ResourceLoaderModule { /** * Fetch the tokens for the current user. * - * @param $context ResourceLoaderContext: Context object - * @return Array: List of tokens keyed by token type + * @return array: List of tokens keyed by token type */ - protected function contextUserTokens( ResourceLoaderContext $context ) { + protected function contextUserTokens() { global $wgUser; return array( 'editToken' => $wgUser->getEditToken(), - 'watchToken' => ApiQueryInfo::getWatchToken(null, null), + 'patrolToken' => ApiQueryRecentChanges::getPatrolToken( null, null ), + 'watchToken' => ApiQueryInfo::getWatchToken( null, null ), ); } @@ -53,7 +53,9 @@ class ResourceLoaderUserTokensModule extends ResourceLoaderModule { */ public function getScript( ResourceLoaderContext $context ) { return Xml::encodeJsCall( 'mw.user.tokens.set', - array( $this->contextUserTokens( $context ) ) ); + array( $this->contextUserTokens() ), + ResourceLoader::inDebugMode() + ); } /** diff --git a/includes/resourceloader/ResourceLoaderWikiModule.php b/includes/resourceloader/ResourceLoaderWikiModule.php index ee8dd1e5..3f10ae53 100644 --- a/includes/resourceloader/ResourceLoaderWikiModule.php +++ b/includes/resourceloader/ResourceLoaderWikiModule.php @@ -42,7 +42,20 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule { /* Abstract Protected Methods */ /** + * Subclasses should return an associative array of resources in the module. + * Keys should be the title of a page in the MediaWiki or User namespace. + * + * Values should be a nested array of options. The supported keys are 'type' and + * (CSS only) 'media'. + * + * For scripts, 'type' should be 'script'. + * + * For stylesheets, 'type' should be 'style'. + * There is an optional media key, the value of which can be the + * medium ('screen', 'print', etc.) of the stylesheet. + * * @param $context ResourceLoaderContext + * @return array */ abstract protected function getPages( ResourceLoaderContext $context ); @@ -75,7 +88,22 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule { if ( !$revision ) { return null; } - return $revision->getRawText(); + + $content = $revision->getContent( Revision::RAW ); + + if ( !$content ) { + wfDebugLog( 'resourceloader', __METHOD__ . ': failed to load content of JS/CSS page!' ); + return null; + } + + $model = $content->getModel(); + + if ( $model !== CONTENT_MODEL_CSS && $model !== CONTENT_MODEL_JAVASCRIPT ) { + wfDebugLog( 'resourceloader', __METHOD__ . ': bad content model $model for JS/CSS page!' ); + return null; + } + + return $content->getNativeData(); //NOTE: this is safe, we know it's JS or CSS } /* Methods */ @@ -98,7 +126,7 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule { if ( strval( $script ) !== '' ) { $script = $this->validateScriptFile( $titleText, $script ); if ( strpos( $titleText, '*/' ) === false ) { - $scripts .= "/* $titleText */\n"; + $scripts .= "/* $titleText */\n"; } $scripts .= $script . "\n"; } @@ -119,7 +147,7 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule { continue; } $title = Title::newFromText( $titleText ); - if ( !$title || $title->isRedirect() ) { + if ( !$title || $title->isRedirect() ) { continue; } $media = isset( $options['media'] ) ? $options['media'] : 'all'; @@ -135,7 +163,7 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule { $styles[$media] = array(); } if ( strpos( $titleText, '*/' ) === false ) { - $style = "/* $titleText */\n" . $style; + $style = "/* $titleText */\n" . $style; } $styles[$media][] = $style; } |