From ca32f08966f1b51fcb19460f0996bb0c4048e6fe Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Sat, 3 Dec 2011 13:29:22 +0100 Subject: Update to MediaWiki 1.18.0 * also update ArchLinux skin to chagnes in MonoBook * Use only css to hide our menu bar when printing --- extensions/Gadgets/Gadgets_body.php | 586 ++++++++++++++++++++++++++++++++++++ 1 file changed, 586 insertions(+) create mode 100644 extensions/Gadgets/Gadgets_body.php (limited to 'extensions/Gadgets/Gadgets_body.php') diff --git a/extensions/Gadgets/Gadgets_body.php b/extensions/Gadgets/Gadgets_body.php new file mode 100644 index 00000000..a904d6e4 --- /dev/null +++ b/extensions/Gadgets/Gadgets_body.php @@ -0,0 +1,586 @@ +mTitle; + if( $title->getNamespace() == NS_MEDIAWIKI && $title->getText() == 'Gadgets-definition' ) { + Gadget::loadStructuredList( $text ); + } + return true; + } + + /** + * UserGetDefaultOptions hook handler + * @param $defaultOptions Array of default preference keys and values + */ + public static function userGetDefaultOptions( &$defaultOptions ) { + $gadgets = Gadget::loadStructuredList(); + if (!$gadgets) return true; + + foreach( $gadgets as $section => $thisSection ) { + foreach( $thisSection as $gadgetId => $gadget ) { + if ( $gadget->isOnByDefault() ) { + $defaultOptions['gadget-' . $gadgetId] = 1; + } + } + } + return true; + } + + /** + * GetPreferences hook handler. + * @param $user User + * @param $preferences Array: Preference descriptions + */ + public static function getPreferences( $user, &$preferences ) { + $gadgets = Gadget::loadStructuredList(); + if (!$gadgets) return true; + + $options = array(); + $default = array(); + foreach( $gadgets as $section => $thisSection ) { + $available = array(); + foreach( $thisSection as $gadget ) { + if ( $gadget->isAllowed( $user ) ) { + $gname = $gadget->getName(); + $available[$gadget->getDescription()] = $gname; + if ( $gadget->isEnabled( $user ) ) { + $default[] = $gname; + } + } + } + if ( $section !== '' ) { + $section = wfMsgExt( "gadget-section-$section", 'parseinline' ); + if ( count ( $available ) ) { + $options[$section] = $available; + } + } else { + $options = array_merge( $options, $available ); + } + } + + $preferences['gadgets-intro'] = + array( + 'type' => 'info', + 'label' => ' ', + 'default' => Xml::tags( 'tr', array(), + Xml::tags( 'td', array( 'colspan' => 2 ), + wfMsgExt( 'gadgets-prefstext', 'parse' ) ) ), + 'section' => 'gadgets', + 'raw' => 1, + 'rawrow' => 1, + ); + + $preferences['gadgets'] = + array( + 'type' => 'multiselect', + 'options' => $options, + 'section' => 'gadgets', + 'label' => ' ', + 'prefix' => 'gadget-', + 'default' => $default, + ); + + return true; + } + + /** + * ResourceLoaderRegisterModules hook handler. + * @param $resourceLoader ResourceLoader + */ + public static function registerModules( &$resourceLoader ) { + $gadgets = Gadget::loadList(); + if ( !$gadgets ) { + return true; + } + foreach ( $gadgets as $g ) { + $module = $g->getModule(); + if ( $module ) { + $resourceLoader->register( $g->getModuleName(), $module ); + } + } + return true; + } + + /** + * BeforePageDisplay hook handler. + * @param $out OutputPage + */ + public static function beforePageDisplay( $out ) { + global $wgUser; + + wfProfileIn( __METHOD__ ); + + $gadgets = Gadget::loadList(); + if ( !$gadgets ) { + wfProfileOut( __METHOD__ ); + return true; + } + + $lb = new LinkBatch(); + $lb->setCaller( __METHOD__ ); + $pages = array(); + + foreach ( $gadgets as $gadget ) { + if ( $gadget->isEnabled( $wgUser ) && $gadget->isAllowed( $wgUser ) ) { + if ( $gadget->hasModule() ) { + $out->addModules( $gadget->getModuleName() ); + } + foreach ( $gadget->getLegacyScripts() as $page ) { + $lb->add( NS_MEDIAWIKI, $page ); + $pages[] = $page; + } + } + } + + $lb->execute( __METHOD__ ); + + $done = array(); + foreach ( $pages as $page ) { + if ( isset( $done[$page] ) ) continue; + $done[$page] = true; + self::applyScript( $page, $out ); + } + wfProfileOut( __METHOD__ ); + + return true; + } + + /** + * Adds one legacy script to output. + * + * @param $page String: Unprefixed page title + * @param $out OutputPage + */ + private static function applyScript( $page, $out ) { + global $wgJsMimeType; + + # bug 22929: disable gadgets on sensitive pages. Scripts loaded through the + # ResourceLoader handle this in OutputPage::getModules() + # TODO: make this extension load everything via RL, then we don't need to worry + # about any of this. + if( $out->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS ) < ResourceLoaderModule::ORIGIN_USER_SITEWIDE ){ + return; + } + + $t = Title::makeTitleSafe( NS_MEDIAWIKI, $page ); + if ( !$t ) return; + + $u = $t->getLocalURL( 'action=raw&ctype=' . $wgJsMimeType ); + $out->addScriptFile( $u, $t->getLatestRevID() ); + } + + /** + * UnitTestsList hook handler + * @param $files Array: List of extension test files + */ + public static function unitTestsList( $files ) { + $files[] = dirname( __FILE__ ) . '/Gadgets_tests.php'; + return true; + } +} + +/** + * Wrapper for one gadget. + */ +class Gadget { + /** + * Increment this when changing class structure + */ + const GADGET_CLASS_VERSION = 5; + + private $version = self::GADGET_CLASS_VERSION, + $scripts = array(), + $styles = array(), + $dependencies = array(), + $name, + $definition, + $resourceLoaded = false, + $requiredRights = array(), + $onByDefault = false, + $category; + + /** + * Creates an instance of this class from definition in MediaWiki:Gadgets-definition + * @param $definition String: Gadget definition + * @return Mixed: Instance of Gadget class or false if $definition is invalid + */ + public static function newFromDefinition( $definition ) { + $m = array(); + if ( !preg_match( '/^\*+ *([a-zA-Z](?:[-_:.\w\d ]*[a-zA-Z0-9])?)(\s*\[.*?\])?\s*((\|[^|]*)+)\s*$/', $definition, $m ) ) { + return false; + } + //NOTE: the gadget name is used as part of the name of a form field, + // and must follow the rules defined in http://www.w3.org/TR/html4/types.html#type-cdata + // Also, title-normalization applies. + $gadget = new Gadget(); + $gadget->name = trim( str_replace(' ', '_', $m[1] ) ); + $gadget->definition = $definition; + $options = trim( $m[2], ' []' ); + foreach ( preg_split( '/\s*\|\s*/', $options, -1, PREG_SPLIT_NO_EMPTY ) as $option ) { + $arr = preg_split( '/\s*=\s*/', $option, 2 ); + $option = $arr[0]; + if ( isset( $arr[1] ) ) { + $params = explode( ',', $arr[1] ); + $params = array_map( 'trim', $params ); + } else { + $params = array(); + } + switch ( $option ) { + case 'ResourceLoader': + $gadget->resourceLoaded = true; + break; + case 'dependencies': + $gadget->dependencies = $params; + break; + case 'rights': + $gadget->requiredRights = $params; + break; + case 'default': + $gadget->onByDefault = true; + break; + } + } + foreach ( preg_split( '/\s*\|\s*/', $m[3], -1, PREG_SPLIT_NO_EMPTY ) as $page ) { + $page = "Gadget-$page"; + if ( preg_match( '/\.js/', $page ) ) { + $gadget->scripts[] = $page; + } elseif ( preg_match( '/\.css/', $page ) ) { + $gadget->styles[] = $page; + } + } + return $gadget; + } + + /** + * @return String: Gadget name + */ + public function getName() { + return $this->name; + } + + /** + * @return String: Gadget description parsed into HTML + */ + public function getDescription() { + return wfMessage( "gadget-{$this->getName()}" )->parse(); + } + + /** + * @return String: Wikitext of gadget description + */ + public function getRawDescription() { + return wfMessage( "gadget-{$this->getName()}" )->plain(); + } + + /** + * @return String: Name of category (aka section) our gadget belongs to. Empty string if none. + */ + public function getCategory() { + return $this->category; + } + + /** + * @return String: Name of ResourceLoader module for this gadget + */ + public function getModuleName() { + return "ext.gadget.{$this->name}"; + } + + /** + * Checks whether this is an instance of an older version of this class deserialized from cache + * @return Boolean + */ + public function isOutdated() { + return $this->version != self::GADGET_CLASS_VERSION; + } + + /** + * Checks whether this gadget is enabled for given user + * + * @param $user User: user to check against + * @return Boolean + */ + public function isEnabled( $user ) { + return (bool)$user->getOption( "gadget-{$this->name}", $this->onByDefault ); + } + + /** + * Checks whether given user has permissions to use this gadget + * + * @param $user User: user to check against + * @return Boolean + */ + public function isAllowed( $user ) { + return count( array_intersect( $this->requiredRights, $user->getRights() ) ) == count( $this->requiredRights ); + } + + /** + * @return Boolean: Whether this gadget is on by default for everyone (but can be disabled in preferences) + */ + public function isOnByDefault() { + return $this->onByDefault; + } + + /** + * @return Boolean: Whether all of this gadget's JS components support ResourceLoader + */ + public function supportsResourceLoader() { + return $this->resourceLoaded; + } + + /** + * @return Boolean: Whether this gadget has resources that can be loaded via ResourceLoader + */ + public function hasModule() { + return count( $this->styles ) + + ( $this->supportsResourceLoader() ? count( $this->scripts ) : 0 ) + > 0; + } + + /** + * @return String: Definition for this gadget from MediaWiki:gadgets-definition + */ + public function getDefinition() { + return $this->definition; + } + + /** + * @return Array: Array of pages with JS not prefixed with namespace + */ + public function getScripts() { + return $this->scripts; + } + + /** + * @return Array: Array of pages with CSS not prefixed with namespace + */ + public function getStyles() { + return $this->styles; + } + + /** + * @return Array: Array of all of this gadget's resources + */ + public function getScriptsAndStyles() { + return array_merge( $this->scripts, $this->styles ); + } + + /** + * Returns module for ResourceLoader, see getModuleName() for its name. + * If our gadget has no scripts or styles suitable for RL, false will be returned. + * @return Mixed: GadgetResourceLoaderModule or false + */ + public function getModule() { + $pages = array(); + foreach( $this->styles as $style ) { + $pages['MediaWiki:' . $style] = array( 'type' => 'style' ); + } + if ( $this->supportsResourceLoader() ) { + foreach ( $this->scripts as $script ) { + $pages['MediaWiki:' . $script] = array( 'type' => 'script' ); + } + } + if ( !count( $pages ) ) { + return null; + } + return new GadgetResourceLoaderModule( $pages, $this->dependencies ); + } + + /** + * Returns list of scripts that don't support ResourceLoader + * @return Array + */ + public function getLegacyScripts() { + if ( $this->supportsResourceLoader() ) { + return array(); + } + return $this->scripts; + } + + /** + * Returns names of resources this gadget depends on + * @return Array + */ + public function getDependencies() { + return $this->dependencies; + } + + /** + * Returns array of permissions required by this gadget + * @return Array + */ + public function getRequiredRights() { + return $this->requiredRights; + } + + /** + * Loads and returns a list of all gadgets + * @return Mixed: Array of gadgets or false + */ + public static function loadList() { + static $gadgets = null; + + if ( $gadgets !== null ) return $gadgets; + + wfProfileIn( __METHOD__ ); + $struct = self::loadStructuredList(); + if ( !$struct ) { + $gadgets = $struct; + wfProfileOut( __METHOD__ ); + return $gadgets; + } + + $gadgets = array(); + foreach ( $struct as $section => $entries ) { + $gadgets = array_merge( $gadgets, $entries ); + } + wfProfileOut( __METHOD__ ); + + return $gadgets; + } + + /** + * Checks whether gadget list from cache can be used. + * @return Boolean + */ + private static function isValidList( $gadgets ) { + if ( !is_array( $gadgets ) ) return false; + // Check if we have 1) array of gadgets 2) the gadgets are up to date + // One check is enough + foreach ( $gadgets as $section => $list ) { + foreach ( $list as $g ) { + if ( !( $g instanceof Gadget ) || $g->isOutdated() ) { + return false; + } else { + return true; + } + } + } + return true; // empty array + } + + /** + * Loads list of gadgets and returns it as associative array of sections with gadgets + * e.g. array( 'sectionnname1' => array( $gadget1, $gadget2), + * 'sectionnname2' => array( $gadget3 ) ); + * @param $forceNewText String: New text of MediaWiki:gadgets-sdefinition. If specified, will + * force a purge of cache and recreation of the gadget list. + * @return Mixed: Array or false + */ + public static function loadStructuredList( $forceNewText = null ) { + global $wgMemc; + + static $gadgets = null; + if ( $gadgets !== null && $forceNewText === null ) return $gadgets; + + wfProfileIn( __METHOD__ ); + $key = wfMemcKey( 'gadgets-definition', self::GADGET_CLASS_VERSION ); + + if ( $forceNewText === null ) { + //cached? + $gadgets = $wgMemc->get( $key ); + if ( self::isValidList( $gadgets ) ) { + wfProfileOut( __METHOD__ ); + return $gadgets; + } + + $g = wfMessage( "gadgets-definition" )->inContentLanguage(); + if ( !$g->exists() ) { + $gadgets = false; + wfProfileOut( __METHOD__ ); + return $gadgets; + } + $g = $g->plain(); + } else { + $g = $forceNewText; + } + + $g = preg_replace( '//s', '', $g ); + $g = preg_split( '/(\r\n|\r|\n)+/', $g ); + + $gadgets = array(); + $section = ''; + + foreach ( $g as $line ) { + $m = array(); + if ( preg_match( '/^==+ *([^*:\s|]+?)\s*==+\s*$/', $line, $m ) ) { + $section = $m[1]; + } + else { + $gadget = self::newFromDefinition( $line ); + if ( $gadget ) { + $gadgets[$section][$gadget->getName()] = $gadget; + $gadget->category = $section; + } + } + } + + //cache for a while. gets purged automatically when MediaWiki:Gadgets-definition is edited + $wgMemc->set( $key, $gadgets, 60*60*24 ); + $source = $forceNewText !== null ? 'input text' : 'MediaWiki:Gadgets-definition'; + wfDebug( __METHOD__ . ": $source parsed, cache entry $key updated\n"); + wfProfileOut( __METHOD__ ); + + return $gadgets; + } +} + +/** + * Class representing a list of resources for one gadget + */ +class GadgetResourceLoaderModule extends ResourceLoaderWikiModule { + private $pages, $dependencies; + + /** + * Creates an instance of this class + * @param $pages Array: Associative array of pages in ResourceLoaderWikiModule-compatible + * format, for example: + * array( + * 'MediaWiki:Gadget-foo.js' => array( 'type' => 'script' ), + * 'MediaWiki:Gadget-foo.css' => array( 'type' => 'style' ), + * ) + * @param $dependencies Array: Names of resources this module depends on + */ + public function __construct( $pages, $dependencies ) { + $this->pages = $pages; + $this->dependencies = $dependencies; + } + + /** + * Overrides the abstract function from ResourceLoaderWikiModule class + * @return Array: $pages passed to __construct() + */ + protected function getPages( ResourceLoaderContext $context ) { + return $this->pages; + } + + /** + * Overrides ResourceLoaderModule::getDependencies() + * @return Array: Names of resources this module depends on + */ + public function getDependencies() { + return $this->dependencies; + } +} -- cgit v1.2.3-54-g00ecf