diff options
Diffstat (limited to 'extensions/Variables/Variables.php')
-rw-r--r-- | extensions/Variables/Variables.php | 375 |
1 files changed, 375 insertions, 0 deletions
diff --git a/extensions/Variables/Variables.php b/extensions/Variables/Variables.php new file mode 100644 index 00000000..84d6c90c --- /dev/null +++ b/extensions/Variables/Variables.php @@ -0,0 +1,375 @@ +<?php + +/** + * 'Variables' introduces parser functions for defining page-scoped variables within + * wiki pages. + * + * Documentation: https://www.mediawiki.org/wiki/Extension:Variables + * Support: https://www.mediawiki.org/wiki/Extension_talk:Variables + * Source code: https://phabricator.wikimedia.org/diffusion/EVAR/ + * + * @license: ISC License + * @author: Rob Adams + * @author: Tom Hempel + * @author: Xiloynaha + * @author: Daniel Werner < danweetz@web.de > + * + * @file Variables.php + * @ingroup Variables + */ + +// Ensure that the script cannot be executed outside of MediaWiki. +if ( !defined( 'MEDIAWIKI' ) ) { + die( 'This is an extension to MediaWiki and cannot be run standalone.' ); +} + +// Display extension properties on MediaWiki. +$wgExtensionCredits['parserhook'][] = array( + 'path' => __FILE__, + 'name' => 'Variables', + 'descriptionmsg' => 'variables-desc', + 'version' => ExtVariables::VERSION, + 'author' => array( + 'Rob Adams', + 'Tom Hempel', + 'Xiloynaha', + '[https://www.mediawiki.org/wiki/User:Danwe Daniel Werner]', + '...' + ), + 'url' => 'https://www.mediawiki.org/wiki/Extension:Variables', + 'license-name' => 'ISC' +); + +// language files: +$wgMessagesDirs['Variables'] = __DIR__ . '/i18n'; +$wgExtensionMessagesFiles['VariablesMagic'] = ExtVariables::getDir() . '/Variables.i18n.magic.php'; + +// hooks registration: +$wgHooks['ParserFirstCallInit'][] = 'ExtVariables::init'; +$wgHooks['ParserClearState'][] = 'ExtVariables::onParserClearState'; +$wgHooks['InternalParseBeforeSanitize'][] = 'ExtVariables::onInternalParseBeforeSanitize'; + +// parser tests registration: +$wgParserTestFiles[] = ExtVariables::getDir() . '/tests/mwparsertests/Variables.txt'; + +// Include the settings file: +require_once ExtVariables::getDir() . '/Variables.settings.php'; + + +/** + * Extension class with basic extension information. This class serves as static + * class with the static parser functions but also als variables store instance + * as object assigned to a Parser object. + */ +class ExtVariables { + + /** + * Version of the 'Variables' extension. + * + * @since 1.4 + * + * @var string + */ + const VERSION = '2.2.0'; + + /** + * Internal store for variable values + * + * @private + * @var array + */ + public $mVariables = array(); + + /** + * Array with all names of variables requested by '#var_final'. Key of the values is the + * stripSateId of the strip-item placed where the final var should appear. + * + * @since 2.0 + * + * @private + * @var array + */ + public $mFinalizedVars = array(); + + /** + * Variables extensions own private StripState manager to manage '#final_var' placeholders + * and their replacement with the final var value or a defined default. + * + * @since 2.0 + * + * @private + * @var StripState + */ + public $mFinalizedVarsStripState; + + /** + * Sets up parser functions + * + * @since 1.4 + */ + public static function init( Parser &$parser ) { + + /* + * store for variables per parser object. This will solve several bugs related to + * 'ParserClearState' hook clearing all variables early in combination with certain + * other extensions. (since v2.0) + */ + $parser->mExtVariables = new self(); + + // Parser::SFH_OBJECT_ARGS available since MW 1.12 + self::initFunction( $parser, 'var', array( __CLASS__, 'pfObj_var' ), Parser::SFH_OBJECT_ARGS ); + self::initFunction( $parser, 'var_final' ); + self::initFunction( $parser, 'vardefine' ); + self::initFunction( $parser, 'vardefineecho' ); + self::initFunction( $parser, 'varexists' ); + + return true; + } + private static function initFunction( Parser &$parser, $name, $functionCallback = null, $flags = 0 ) { + if( $functionCallback === null ) { + // prefix parser functions with 'pf_' + $functionCallback = array( __CLASS__, 'pf_' . $name ); + } + global $egVariablesDisabledFunctions; + + // register function only if not disabled by configuration: + if( ! in_array( $name, $egVariablesDisabledFunctions ) ) { + $parser->setFunctionHook( $name, $functionCallback, $flags ); + } + } + + /** + * Returns the extensions base installation directory. + * + * @since 2.0 + * + * @return string + */ + public static function getDir() { + static $dir = null; + + if( $dir === null ) { + $dir = dirname( __FILE__ ); + } + return $dir; + } + + + #################### + # Parser Functions # + #################### + + static function pf_varexists( Parser &$parser, $varName = '', $exists=true, $noexists=false ) { + if( self::get( $parser )->varExists( $varName ) ) { + return $exists; + } else { + return $noexists; + } + } + + static function pf_vardefine( Parser &$parser, $varName = '', $value = '' ) { + self::get( $parser )->setVarValue( $varName, $value ); + return ''; + } + + static function pf_vardefineecho( Parser &$parser, $varName = '', $value = '' ) { + self::get( $parser )->setVarValue( $varName, $value ); + return $value; + } + + static function pfObj_var( Parser &$parser, $frame, $args) { + $varName = trim( $frame->expand( $args[0] ) ); // first argument expanded already but lets do this anyway + $varVal = self::get( $parser )->getVarValue( $varName, null ); + + // default applies if var doesn't exist but also in case it is an empty string! + if( $varVal === null || $varVal === '' ) { + // only expand argument when needed: + $defaultVal = isset( $args[1] ) ? trim( $frame->expand( $args[1] ) ) : ''; + return $defaultVal; + } + return $varVal; + } + + static function pf_var_final( Parser &$parser, $varName, $defaultVal = '' ) { + return self::get( $parser )->requestFinalizedVar( $parser, $varName, $defaultVal ); + } + + + ############## + # Used Hooks # + ############## + + /** + * Used for '#var_final' parser function to insert the final variable values. + * @see http://www.mediawiki.org/wiki/Manual:Hooks/InternalParseBeforeSanitize + * + * @since 2.0.1 + */ + static function onInternalParseBeforeSanitize( Parser &$parser, &$text ) { + $varStore = self::get( $parser ); + + // only do this if '#var_final' was used + if( $varStore->mFinalizedVarsStripState === null ) { + return true; + } + + /* + * all vars are final now, check whether requested vars can be inserted for '#final_var' or + * if the default has to be inserted. In any case, adjust the strip item value + */ + foreach( $varStore->mFinalizedVars as $stripStateId => $varName ) { + + $varVal = $varStore->getVarValue( $varName, '' ); + if( $varVal !== '' ) { + // replace strip item value with final variables value or registered default: + $varStore->stripStatePair( $stripStateId, $varVal ); + } + } + + /** + * Unstrip all '#var_final' strip-markers with their final '#var' or default values. + * This HAS to be done here and can't be done through the normal unstrip process of MW. + * This because the default value as well as the variables value stil have to be rendered properly since they + * may contain links or even category links. On the other hand, they can't be parsed with Parser::recursiveTagParse() + * since this would parse wiki templates and functions which are intended as normal text, kind of similar to + * returning a parser functions value with 'noparse' => true. + * Also, there is no way to expand the '#var_final' default value here, just if needed, since the output could be an + * entirely different, e.g. if variables are used. + * This method also takes care of recursive '#var_final' calls (within the default value) quite well. + */ + $text = $varStore->mFinalizedVarsStripState->unstripGeneral( $text ); + return true; + } + + /** + * This will clean up the variables store after parsing has finished. It will prevent strange things to happen + * for example during import of several pages or job queue is running for multiple pages. In these cases variables + * would become some kind of superglobals, being passed from one page to the other. + */ + static function onParserClearState( Parser &$parser ) { + /** + * MessageCaches Parser clone will mess things up if we don't reset the entire object. + * Only resetting the array would unset it in the original object as well! This instead + * will break the entire reference to the object + */ + $parser->mExtVariables = new self(); + return true; + } + + + ################## + # Private Helper # + ################## + + /** + * Takes care of setting a strip state pair + */ + protected function stripStatePair( $marker, $value ) { + $this->mFinalizedVarsStripState->addGeneral( $marker, $value ); + } + + + #################################### + # Public functions for interaction # + #################################### + # + # public non-parser functions, accessible for + # other extensions doing interactive stuff + # with 'Variables' (like Extension:Loops) + # + + /** + * Convenience function to return the 'Variables' extensions variables store connected + * to a certain Parser object. Each parser has its own store which will be reset after + * a parsing process [Parser::parse()] has finished. + * + * @param Parser &$parser + * + * @return ExtVariables by reference so we still have the right object after 'ParserClearState' + */ + public static function &get( Parser &$parser ) { + return $parser->mExtVariables; + } + + /** + * Defines a variable, accessible by getVarValue() or '#var' parser function. Name and + * value will be trimmed and converted to string. + * + * @param string $varName + * @param string $value will be converted to string if no string is given + */ + public function setVarValue( $varName, $value = '' ) { + $this->mVariables[ trim( $varName ) ] = trim( $value ); + } + + /** + * Returns a variables value or null if it doesn't exist. + * + * @param string $varName + * @param mixed $defaultVal + * + * @return string or mixed in case $defaultVal is being returned and not of type string + */ + public function getVarValue( $varName, $defaultVal = null ) { + $varName = trim( $varName ); + if ( $this->varExists( $varName ) ) { + return $this->mVariables[ $varName ]; + } else { + return $defaultVal; + } + } + + /** + * Checks whether a variable exists within the scope. + * + * @param string $varName + * + * @return boolean + */ + public function varExists( $varName ) { + $varName = trim( $varName ); + return array_key_exists( $varName, $this->mVariables ); + } + + /** + * Allows to unset a certain variable + * + * @param type $varName + */ + public function unsetVar( $varName ) { + unset( $this->mVariables[ $varName ] ); + } + + /** + * Allows to register the usage of '#var_final'. Meaning a variable can be set as well + * as a default value. The return value, a strip-item then can be inserted into any + * wikitext processed by the same parser. Later that strip-item will be replaced with + * the final var text. + * Note: It's not possible to use the returned strip-item within other stripped text + * since 'Variables' unstripping will happen before the general unstripping! + * + * @param Parser $parser + * @param string $varName + * @param string $defaultVal + * + * @return string strip-item + */ + function requestFinalizedVar( Parser &$parser, $varName, $defaultVal = '' ) { + if( $this->mFinalizedVarsStripState === null ) { + $this->mFinalizedVarsStripState = new StripState; + } + $id = count( $this->mFinalizedVars ); + /* + * strip-item which will be unstripped in self::onInternalParseBeforeSanitize() + * In case the requested final variable has a value in the end, this strip-item + * value will be replaced with that value before unstripping. + */ + $rnd = "{$parser->mUniqPrefix}-finalizedvar-{$id}-" . Parser::MARKER_SUFFIX; + + $this->stripStatePair( $rnd, trim( $defaultVal ) ); + $this->mFinalizedVars[ $rnd ] = trim( $varName ); + + return $rnd; + } + +} |