Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
83.59% covered (warning)
83.59%
377 / 451
64.10% covered (warning)
64.10%
75 / 117
CRAP
0.00% covered (danger)
0.00%
0 / 1
ParserOptions
83.78% covered (warning)
83.78%
377 / 450
64.10% covered (warning)
64.10%
75 / 117
344.11
0.00% covered (danger)
0.00%
0 / 1
 getOption
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 lazyLoadOption
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 nullifyLazyOption
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLazyOptions
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 getCacheVaryingOptionsHash
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setOption
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 setOptionLegacy
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 getInterwikiMagic
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setInterwikiMagic
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAllowExternalImages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAllowExternalImagesFrom
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getEnableImageWhitelist
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAllowSpecialInclusion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setAllowSpecialInclusion
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getInterfaceMessage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setInterfaceMessage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isMessage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setIsMessage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTargetLanguage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setTargetLanguage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getMaxIncludeSize
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setMaxIncludeSize
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getMaxPPNodeCount
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setMaxPPNodeCount
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMaxPPExpandDepth
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getMaxTemplateDepth
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setMaxTemplateDepth
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getExpensiveParserFunctionLimit
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setExpensiveParserFunctionLimit
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRemoveComments
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setRemoveComments
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEnableLimitReport
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 enableLimitReport
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCleanSignatures
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setCleanSignatures
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getExternalLinkTarget
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setExternalLinkTarget
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDisableContentConversion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 disableContentConversion
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDisableTitleConversion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 disableTitleConversion
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getThumbSize
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setThumbSize
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getIsPreview
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 setIsPreview
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
3.33
 getIsSectionPreview
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setIsSectionPreview
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 getIsPrintable
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setIsPrintable
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPreSaveTransform
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setPreSaveTransform
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getUseParsoid
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setUseParsoid
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDateFormat
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 initDateFormat
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setDateFormat
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getUserLangObj
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUserLang
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setUserLang
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getMagicISBNLinks
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getMagicPMIDLinks
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getMagicRFCLinks
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSuppressTOC
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setSuppressTOC
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getSuppressSectionEditLinks
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setSuppressSectionEditLinks
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCollapsibleSections
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setCollapsibleSections
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setVariant
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getVariant
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAllowUnsafeRawHtml
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setAllowUnsafeRawHtml
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getWrapOutputClass
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setWrapOutputClass
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 getCurrentRevisionRecordCallback
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setCurrentRevisionRecordCallback
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTemplateCallback
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setTemplateCallback
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSpeculativeRevId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSpeculativePageId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 initSpeculativeRevId
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 initSpeculativePageId
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setSpeculativeRevIdCallback
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setSpeculativePageIdCallback
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getTimestamp
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 setTimestamp
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setRedirectTarget
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRedirectTarget
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addExtraKey
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getUserIdentity
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 newFromAnon
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 newFromUser
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 newFromUserAndLang
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 newFromContext
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 newCanonical
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 clearStaticCache
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
2.03
 getDefaults
100.00% covered (success)
100.00%
89 / 89
100.00% covered (success)
100.00%
1 / 1
2
 getDefaultTemplateCallback
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 getDefaultRevisionCallback
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 initialiseFromUser
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 matches
95.83% covered (success)
95.83%
23 / 24
0.00% covered (danger)
0.00%
0 / 1
8
 matchesForCacheKey
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 registerWatcher
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 optionUsed
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 allCacheVaryingOptions
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 allCacheVaryingOptionsWithPostproc
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 optionToString
84.21% covered (warning)
84.21%
16 / 19
0.00% covered (danger)
0.00%
0 / 1
10.39
 optionsHash
96.77% covered (success)
96.77%
30 / 31
0.00% covered (danger)
0.00%
0 / 1
8
 isSafeToCache
66.67% covered (warning)
66.67%
28 / 42
0.00% covered (danger)
0.00%
0 / 1
21.26
 setupFakeRevision
82.98% covered (warning)
82.98%
39 / 47
0.00% covered (danger)
0.00%
0 / 1
8.32
 getRenderReason
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setRenderReason
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPostproc
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 enablePostproc
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 shouldIncludePostproc
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 clearPostproc
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Options for the PHP parser
4 *
5 * @license GPL-2.0-or-later
6 * @file
7 * @ingroup Parser
8 */
9
10namespace MediaWiki\Parser;
11
12use BackedEnum;
13use InvalidArgumentException;
14use LogicException;
15use MediaWiki\Content\Content;
16use MediaWiki\Context\IContextSource;
17use MediaWiki\Context\RequestContext;
18use MediaWiki\HookContainer\HookRunner;
19use MediaWiki\Language\Language;
20use MediaWiki\Logger\LoggerFactory;
21use MediaWiki\MainConfigNames;
22use MediaWiki\MediaWikiServices;
23use MediaWiki\Page\PageIdentity;
24use MediaWiki\Page\PageIdentityValue;
25use MediaWiki\Page\PageRecord;
26use MediaWiki\Revision\MutableRevisionRecord;
27use MediaWiki\Skin\Skin;
28use MediaWiki\StubObject\StubObject;
29use MediaWiki\Title\Title;
30use MediaWiki\User\UserIdentity;
31use MediaWiki\User\UserIdentityValue;
32use MediaWiki\Utils\MWTimestamp;
33use ReflectionClass;
34use UnitEnum;
35use Wikimedia\IPUtils;
36use Wikimedia\Parsoid\Core\LinkTarget as ParsoidLinkTarget;
37use Wikimedia\ScopedCallback;
38use Wikimedia\Timestamp\TimestampFormat as TS;
39
40/**
41 * @brief Set options of the Parser
42 *
43 * How to add an option in core:
44 *  1. Add it to one of the arrays in ParserOptions::getDefaults() (see documentation there)
45 *  2. If necessary:
46 *    a. add an entry to ParserOptions::$initialCacheVaryingOptionsHash: this splits the cache on that option
47 *    b. add an entry to ParserOptions::$callbacks: this is used for pseudo-options that shouldn't be considered
48 *      to compute cacheability
49 *    c. add an entry to ParserOptions::$initialLazyOptions: this ensures that the option is only loaded on read.
50 *      The cacheability of these options is also determined by their usage during the parse and their presence
51 *      in the $initialCacheVaryingOptionsHash or $callbacks arrays.
52 *    Setting options that are not from (2a) or (2b) will make the parse uncacheable if they are actually used
53 *    during the parse in the (current default article parse) setup where option usage is recorded to determine
54 *    cacheability (see ::isSafeToCache() below for details.)
55 *  3. Add a getter and setter in the section for that.
56 *     These should be typically be wrappers around getOption/setOption in order
57 *     to ensure that $this->optionUsed() gets invoked.
58 *
59 * How to add an option in an extension:
60 *  1. Use the 'ParserOptionsRegister' hook to register it.
61 *     This callback will provide $defaults (step 1), $cacheVaryingOptionsHash (2a)
62 *     and $lazyOptions (2c) arrays which serve the same purpose as
63 *     for options defined in core.  It is not possible to add a pseudo-option
64 *     (2b) from an extension at this time.
65 *  2. Where necessary, use $popt->getOption() and $popt->setOption()
66 *     to access it.
67 *
68 * @ingroup Parser
69 */
70class ParserOptions {
71
72    /**
73     * Default values for all options that are relevant for caching.
74     * @see self::getDefaults()
75     * @var array|null
76     */
77    private static $defaults = null;
78
79    /**
80     * Lazy-loaded options
81     * @var callable[]|null
82     */
83    private static $lazyOptions = null;
84
85    /**
86     * Initial lazy-loaded options (before hook)
87     * @var callable[]
88     */
89    private static $initialLazyOptions = [
90        'dateformat' => [ self::class, 'initDateFormat' ],
91        'speculativeRevId' => [ self::class, 'initSpeculativeRevId' ],
92        'speculativePageId' => [ self::class, 'initSpeculativePageId' ],
93    ];
94
95    /**
96     * Specify options that are included in the cache key. This array is initially a copy of
97     * $initialCacheVaryingOptionsHash, and is then modified by the ParserOptionsRegister hook.
98     * If an option in this array has a value that differs from its defaults, said value is
99     * added to the cache key and splits the cache accordingly.
100     * @var array|null
101     */
102    private static $cacheVaryingOptionsHash = null;
103
104    /**
105     * Initial set of $cacheVaryingOptionsHash options (before hook)
106     * @var array
107     */
108    private static $initialCacheVaryingOptionsHash = [
109        'dateformat' => true,
110        'thumbsize' => true,
111        'printable' => true,
112        'userlang' => true,
113        'useParsoid' => true,
114        'collapsibleSections' => true,
115        'postproc' => true,
116        'skin' => true,
117        'injectTOC' => true,
118        'enableSectionEditLinks' => true,
119        'variant' => true,
120    ];
121
122    /**
123     * List of "postprocessing options", excluded from most ParserOption
124     * operations by default.
125     * @var list<string>
126     * @internal
127     */
128    public static array $postprocOptions = [
129        'skin',
130        'allowTOC',
131        'injectTOC',
132        'includeDebugInfo',
133        'enableSectionEditLinks',
134        'wrapperDivClass',
135        'deduplicateStyles',
136        'unwrap',
137        'absoluteURLs',
138        'variant',
139    ];
140
141    /**
142     * Specify pseudo-options that are actually callbacks.
143     * These must be ignored when checking for cacheability.
144     * @var array
145     */
146    private static $callbacks = [
147        'currentRevisionRecordCallback' => true,
148        'templateCallback' => true,
149        'speculativeRevIdCallback' => true,
150        'speculativePageIdCallback' => true,
151    ];
152
153    /**
154     * Current values for all options that are relevant for caching.
155     * @var array
156     */
157    private $options;
158
159    /**
160     * Timestamp used for {{CURRENTDAY}} etc.
161     * @var string|null
162     * @note Caching based on parse time is handled externally
163     */
164    private $mTimestamp;
165
166    /**
167     * Stored user object
168     * @var UserIdentity
169     * @todo Track this for caching somehow without fragmenting the cache
170     */
171    private $mUser;
172
173    /**
174     * Function to be called when an option is accessed.
175     * @var callable|null
176     * @note Used for collecting used options, does not affect caching
177     */
178    private $onAccessCallback = null;
179
180    /**
181     * If the page being parsed is a redirect, this should hold the redirect
182     * target.
183     * @var Title|null
184     * @todo Track this for caching somehow
185     */
186    private $redirectTarget = null;
187
188    /**
189     * Appended to the options hash
190     * @var string
191     */
192    private $mExtraKey = '';
193
194    /**
195     * The reason for rendering the content.
196     * @var string
197     */
198    private $renderReason = 'unknown';
199
200    /**
201     * Fetch an option and track that is was accessed
202     * @since 1.30
203     * @param string $name Option name
204     * @return mixed
205     */
206    public function getOption( $name ) {
207        if ( !array_key_exists( $name, $this->options ) ) {
208            throw new InvalidArgumentException( "Unknown parser option $name" );
209        }
210
211        $this->lazyLoadOption( $name );
212        $this->optionUsed( $name );
213        return $this->options[$name];
214    }
215
216    /**
217     * @param string $name Lazy load option without tracking usage
218     */
219    private function lazyLoadOption( $name ) {
220        $lazyOptions = self::getLazyOptions();
221        if ( isset( $lazyOptions[$name] ) && $this->options[$name] === null ) {
222            $this->options[$name] = $lazyOptions[$name]( $this, $name );
223        }
224    }
225
226    /**
227     * Resets lazy loaded options to null in the provided $options array
228     * @param array $options
229     * @return array
230     */
231    private function nullifyLazyOption( array $options ): array {
232        return array_fill_keys( array_keys( self::getLazyOptions() ), null ) + $options;
233    }
234
235    /**
236     * Get lazy-loaded options.
237     *
238     * This array should be initialised by the constructor. The return type
239     * hint is used as an assertion to ensure this has happened and to coerce
240     * the type for static analysis.
241     *
242     * @internal Public for testing only
243     *
244     * @return array
245     */
246    public static function getLazyOptions(): array {
247        // Trigger a call to the 'ParserOptionsRegister' hook if it hasn't
248        // already been called.
249        if ( self::$lazyOptions === null ) {
250            self::getDefaults();
251        }
252        return self::$lazyOptions;
253    }
254
255    /**
256     * Get cache varying options, with the name of the option in the key, and a
257     * boolean in the value which indicates whether the cache is indeed varied.
258     *
259     * @see self::allCacheVaryingOptions()
260     *
261     * @return array
262     */
263    private static function getCacheVaryingOptionsHash(): array {
264        // Trigger a call to the 'ParserOptionsRegister' hook if it hasn't
265        // already been called.
266        if ( self::$cacheVaryingOptionsHash === null ) {
267            self::getDefaults();
268        }
269        return self::$cacheVaryingOptionsHash;
270    }
271
272    /**
273     * Set an option, generically
274     * @since 1.30
275     * @param string $name Option name
276     * @param mixed $value New value. Passing null will set null, unlike many
277     *  of the existing accessors which ignore null for historical reasons.
278     * @return mixed Old value
279     */
280    public function setOption( $name, $value ) {
281        if ( !array_key_exists( $name, $this->options ) ) {
282            throw new InvalidArgumentException( "Unknown parser option $name" );
283        }
284        $old = $this->options[$name];
285        $this->options[$name] = $value;
286        return $old;
287    }
288
289    /**
290     * Legacy implementation
291     * @since 1.30 For implementing legacy setters only. Don't use this in new code.
292     * @deprecated since 1.30
293     * @param string $name Option name
294     * @param mixed $value New value. Passing null does not set the value.
295     * @return mixed Old value
296     */
297    protected function setOptionLegacy( $name, $value ) {
298        if ( !array_key_exists( $name, $this->options ) ) {
299            throw new InvalidArgumentException( "Unknown parser option $name" );
300        }