summaryrefslogtreecommitdiff
path: root/extensions/Variables/Variables.php
blob: 84d6c90cb72cdaf08cc49566a9c729054b9a5a8b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
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;
	}

}