Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
87.28% |
295 / 338 |
|
65.09% |
69 / 106 |
CRAP | |
0.00% |
0 / 1 |
ParserOptions | |
87.28% |
295 / 338 |
|
65.09% |
69 / 106 |
201.20 | |
0.00% |
0 / 1 |
getOption | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
lazyLoadOption | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
nullifyLazyOption | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getLazyOptions | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
2.15 | |||
getCacheVaryingOptionsHash | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
setOption | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
setOptionLegacy | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
2.15 | |||
getInterwikiMagic | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setInterwikiMagic | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getAllowExternalImages | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getAllowExternalImagesFrom | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getEnableImageWhitelist | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getAllowSpecialInclusion | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setAllowSpecialInclusion | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getInterfaceMessage | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setInterfaceMessage | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getTargetLanguage | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setTargetLanguage | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getMaxIncludeSize | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setMaxIncludeSize | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getMaxPPNodeCount | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setMaxPPNodeCount | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getMaxPPExpandDepth | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getMaxTemplateDepth | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setMaxTemplateDepth | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getExpensiveParserFunctionLimit | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setExpensiveParserFunctionLimit | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getRemoveComments | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setRemoveComments | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getEnableLimitReport | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
enableLimitReport | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getCleanSignatures | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setCleanSignatures | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getExternalLinkTarget | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setExternalLinkTarget | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getDisableContentConversion | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
disableContentConversion | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getDisableTitleConversion | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
disableTitleConversion | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getThumbSize | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setThumbSize | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getIsPreview | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setIsPreview | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getIsSectionPreview | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setIsSectionPreview | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getIsPrintable | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setIsPrintable | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getPreSaveTransform | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setPreSaveTransform | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getUseParsoid | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setUseParsoid | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getDateFormat | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
initDateFormat | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
setDateFormat | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getUserLangObj | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getUserLang | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setUserLang | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
2.15 | |||
getMagicISBNLinks | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getMagicPMIDLinks | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getMagicRFCLinks | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSuppressTOC | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setSuppressTOC | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getSuppressSectionEditLinks | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setSuppressSectionEditLinks | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getCollapsibleSections | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setCollapsibleSections | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getAllowUnsafeRawHtml | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setAllowUnsafeRawHtml | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getWrapOutputClass | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setWrapOutputClass | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
getCurrentRevisionRecordCallback | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setCurrentRevisionRecordCallback | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getTemplateCallback | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setTemplateCallback | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSpeculativeRevId | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSpeculativePageId | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
initSpeculativeRevId | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
initSpeculativePageId | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
setSpeculativeRevIdCallback | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
setSpeculativePageIdCallback | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getTimestamp | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
setTimestamp | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setRedirectTarget | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getRedirectTarget | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
addExtraKey | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getUserIdentity | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
newFromAnon | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
newFromUser | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
newFromUserAndLang | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
newFromContext | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
newCanonical | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
4 | |||
clearStaticCache | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
3.07 | |||
getDefaults | |
100.00% |
71 / 71 |
|
100.00% |
1 / 1 |
2 | |||
initialiseFromUser | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
matches | |
95.83% |
23 / 24 |
|
0.00% |
0 / 1 |
8 | |||
matchesForCacheKey | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
3 | |||
registerWatcher | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
optionUsed | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
allCacheVaryingOptions | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
optionToString | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
6 | |||
optionsHash | |
96.67% |
29 / 30 |
|
0.00% |
0 / 1 |
7 | |||
isSafeToCache | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
5 | |||
setupFakeRevision | |
96.67% |
29 / 30 |
|
0.00% |
0 / 1 |
3 | |||
getRenderReason | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setRenderReason | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | /** |
3 | * Options for the PHP parser |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License along |
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | * http://www.gnu.org/copyleft/gpl.html |
19 | * |
20 | * @file |
21 | * @ingroup Parser |
22 | */ |
23 | |
24 | use MediaWiki\Context\IContextSource; |
25 | use MediaWiki\HookContainer\HookRunner; |
26 | use MediaWiki\MainConfigNames; |
27 | use MediaWiki\MediaWikiServices; |
28 | use MediaWiki\Parser\Parser; |
29 | use MediaWiki\Revision\MutableRevisionRecord; |
30 | use MediaWiki\Revision\SlotRecord; |
31 | use MediaWiki\StubObject\StubObject; |
32 | use MediaWiki\Title\Title; |
33 | use MediaWiki\User\UserIdentity; |
34 | use MediaWiki\Utils\MWTimestamp; |
35 | use Wikimedia\ScopedCallback; |
36 | |
37 | /** |
38 | * @brief Set options of the Parser |
39 | * |
40 | * How to add an option in core: |
41 | * 1. Add it to one of the arrays in ParserOptions::setDefaults() |
42 | * 2. If necessary, add an entry to ParserOptions::$inCacheKey |
43 | * 3. Add a getter and setter in the section for that. |
44 | * |
45 | * How to add an option in an extension: |
46 | * 1. Use the 'ParserOptionsRegister' hook to register it. |
47 | * 2. Where necessary, use $popt->getOption() and $popt->setOption() |
48 | * to access it. |
49 | * |
50 | * @ingroup Parser |
51 | */ |
52 | class ParserOptions { |
53 | |
54 | /** |
55 | * Default values for all options that are relevant for caching. |
56 | * @see self::getDefaults() |
57 | * @var array|null |
58 | */ |
59 | private static $defaults = null; |
60 | |
61 | /** |
62 | * Lazy-loaded options |
63 | * @var callable[]|null |
64 | */ |
65 | private static $lazyOptions = null; |
66 | |
67 | /** |
68 | * Initial lazy-loaded options (before hook) |
69 | * @var callable[] |
70 | */ |
71 | private static $initialLazyOptions = [ |
72 | 'dateformat' => [ __CLASS__, 'initDateFormat' ], |
73 | 'speculativeRevId' => [ __CLASS__, 'initSpeculativeRevId' ], |
74 | 'speculativePageId' => [ __CLASS__, 'initSpeculativePageId' ], |
75 | ]; |
76 | |
77 | /** |
78 | * Specify options that are included in the cache key |
79 | * @var array|null |
80 | */ |
81 | private static $cacheVaryingOptionsHash = null; |
82 | |
83 | /** |
84 | * Initial inCacheKey options (before hook) |
85 | * @var array |
86 | */ |
87 | private static $initialCacheVaryingOptionsHash = [ |
88 | 'dateformat' => true, |
89 | 'thumbsize' => true, |
90 | 'printable' => true, |
91 | 'userlang' => true, |
92 | 'useParsoid' => true, |
93 | 'suppressSectionEditLinks' => true, |
94 | 'collapsibleSections' => true, |
95 | ]; |
96 | |
97 | /** |
98 | * Specify pseudo-options that are actually callbacks. |
99 | * These must be ignored when checking for cacheability. |
100 | * @var array |
101 | */ |
102 | private static $callbacks = [ |
103 | 'currentRevisionRecordCallback' => true, |
104 | 'templateCallback' => true, |
105 | 'speculativeRevIdCallback' => true, |
106 | 'speculativePageIdCallback' => true, |
107 | ]; |
108 | |
109 | /** |
110 | * Current values for all options that are relevant for caching. |
111 | * @var array |
112 | */ |
113 | private $options; |
114 | |
115 | /** |
116 | * Timestamp used for {{CURRENTDAY}} etc. |
117 | * @var string|null |
118 | * @note Caching based on parse time is handled externally |
119 | */ |
120 | private $mTimestamp; |
121 | |
122 | /** |
123 | * Stored user object |
124 | * @var UserIdentity |
125 | * @todo Track this for caching somehow without fragmenting the cache |
126 | */ |
127 | private $mUser; |
128 | |
129 | /** |
130 | * Function to be called when an option is accessed. |
131 | * @var callable|null |
132 | * @note Used for collecting used options, does not affect caching |
133 | */ |
134 | private $onAccessCallback = null; |
135 | |
136 | /** |
137 | * If the page being parsed is a redirect, this should hold the redirect |
138 | * target. |
139 | * @var Title|null |
140 | * @todo Track this for caching somehow |
141 | */ |
142 | private $redirectTarget = null; |
143 | |
144 | /** |
145 | * Appended to the options hash |
146 | */ |
147 | private $mExtraKey = ''; |
148 | |
149 | /** |
150 | * The reason for rendering the content. |
151 | * @var string |
152 | */ |
153 | private $renderReason = 'unknown'; |
154 | |
155 | /** |
156 | * Fetch an option and track that is was accessed |
157 | * @since 1.30 |
158 | * @param string $name Option name |
159 | * @return mixed |
160 | */ |
161 | public function getOption( $name ) { |
162 | if ( !array_key_exists( $name, $this->options ) ) { |
163 | throw new InvalidArgumentException( "Unknown parser option $name" ); |
164 | } |
165 | |
166 | $this->lazyLoadOption( $name ); |
167 | $this->optionUsed( $name ); |
168 | return $this->options[$name]; |
169 | } |
170 | |
171 | /** |
172 | * @param string $name Lazy load option without tracking usage |
173 | */ |
174 | private function lazyLoadOption( $name ) { |
175 | $lazyOptions = self::getLazyOptions(); |
176 | if ( isset( $lazyOptions[$name] ) && $this->options[$name] === null ) { |
177 | $this->options[$name] = call_user_func( $lazyOptions[$name], $this, $name ); |
178 | } |
179 | } |
180 | |
181 | /** |
182 | * Resets lazy loaded options to null in the provided $options array |
183 | * @param array $options |
184 | * @return array |
185 | */ |
186 | private function nullifyLazyOption( array $options ): array { |
187 | return array_fill_keys( array_keys( self::getLazyOptions() ), null ) + $options; |
188 | } |
189 | |
190 | /** |
191 | * Get lazy-loaded options. |
192 | * |
193 | * This array should be initialised by the constructor. The return type |
194 | * hint is used as an assertion to ensure this has happened and to coerce |
195 | * the type for static analysis. |
196 | * |
197 | * @internal Public for testing only |
198 | * |
199 | * @return array |
200 | */ |
201 | public static function getLazyOptions(): array { |
202 | // Trigger a call to the 'ParserOptionsRegister' hook if it hasn't |
203 | // already been called. |
204 | if ( self::$lazyOptions === null ) { |
205 | self::getDefaults(); |
206 | } |
207 | return self::$lazyOptions; |
208 | } |
209 | |
210 | /** |
211 | * Get cache varying options, with the name of the option in the key, and a |
212 | * boolean in the value which indicates whether the cache is indeed varied. |
213 | * |
214 | * @see self::allCacheVaryingOptions() |
215 | * |
216 | * @return array |
217 | */ |
218 | private static function getCacheVaryingOptionsHash(): array { |
219 | // Trigger a call to the 'ParserOptionsRegister' hook if it hasn't |
220 | // already been called. |
221 | if ( self::$cacheVaryingOptionsHash === null ) { |
222 | self::getDefaults(); |
223 | } |
224 | return self::$cacheVaryingOptionsHash; |
225 | } |
226 | |
227 | /** |
228 | * Set an option, generically |
229 | * @since 1.30 |
230 | * @param string $name Option name |
231 | * @param mixed $value New value. Passing null will set null, unlike many |
232 | * of the existing accessors which ignore null for historical reasons. |
233 | * @return mixed Old value |
234 | */ |
235 | public function setOption( $name, $value ) { |
236 | if ( !array_key_exists( $name, $this->options ) ) { |
237 | throw new InvalidArgumentException( "Unknown parser option $name" ); |
238 | } |
239 | $old = $this->options[$name]; |
240 | $this->options[$name] = $value; |
241 | return $old; |
242 | } |
243 | |
244 | /** |
245 | * Legacy implementation |
246 | * @since 1.30 For implementing legacy setters only. Don't use this in new code. |
247 | * @deprecated since 1.30 |
248 | * @param string $name Option name |
249 | * @param mixed $value New value. Passing null does not set the value. |
250 | * @return mixed Old value |
251 | */ |
252 | protected function setOptionLegacy( $name, $value ) { |
253 | if ( !array_key_exists( $name, $this->options ) ) { |
254 | throw new InvalidArgumentException( "Unknown parser option $name" ); |
255 | } |
256 | return wfSetVar( $this->options[$name], $value ); |
257 | } |
258 | |
259 | /** |
260 | * Whether to extract interlanguage links |
261 | * |
262 | * When true, interlanguage links will be returned by |
263 | * ParserOutput::getLanguageLinks() instead of generating link HTML. |
264 | * |
265 | * @return bool |
266 | */ |
267 | public function getInterwikiMagic() { |
268 | return $this->getOption( 'interwikiMagic' ); |
269 | } |
270 | |
271 | /** |
272 | * Specify whether to extract interlanguage links |
273 | * @param bool|null $x New value (null is no change) |
274 | * @return bool Old value |
275 | */ |
276 | public function setInterwikiMagic( $x ) { |
277 | return $this->setOptionLegacy( 'interwikiMagic', $x ); |
278 | } |
279 | |
280 | /** |
281 | * Allow all external images inline? |
282 | * @return bool |
283 | */ |
284 | public function getAllowExternalImages() { |
285 | return $this->getOption( 'allowExternalImages' ); |
286 | } |
287 | |
288 | /** |
289 | * External images to allow |
290 | * |
291 | * When self::getAllowExternalImages() is false |
292 | * |
293 | * @return string|string[] URLs to allow |
294 | */ |
295 | public function getAllowExternalImagesFrom() { |
296 | return $this->getOption( 'allowExternalImagesFrom' ); |
297 | } |
298 | |
299 | /** |
300 | * Use the on-wiki external image whitelist? |
301 | * @return bool |
302 | */ |
303 | public function getEnableImageWhitelist() { |
304 | return $this->getOption( 'enableImageWhitelist' ); |
305 | } |
306 | |
307 | /** |
308 | * Allow inclusion of special pages? |
309 | * @return bool |
310 | */ |
311 | public function getAllowSpecialInclusion() { |
312 | return $this->getOption( 'allowSpecialInclusion' ); |
313 | } |
314 | |
315 | /** |
316 | * Allow inclusion of special pages? |
317 | * @param bool|null $x New value (null is no change) |
318 | * @return bool Old value |
319 | */ |
320 | public function setAllowSpecialInclusion( $x ) { |
321 | return $this->setOptionLegacy( 'allowSpecialInclusion', $x ); |
322 | } |
323 | |
324 | /** |
325 | * Parsing an interface message? |
326 | * @return bool |
327 | */ |
328 | public function getInterfaceMessage() { |
329 | return $this->getOption( 'interfaceMessage' ); |
330 | } |
331 | |
332 | /** |
333 | * Parsing an interface message? |
334 | * @param bool|null $x New value (null is no change) |
335 | * @return bool Old value |
336 | */ |
337 | public function setInterfaceMessage( $x ) { |
338 | return $this->setOptionLegacy( 'interfaceMessage', $x ); |
339 | } |
340 | |
341 | /** |
342 | * Target language for the parse |
343 | * @return Language|null |
344 | */ |
345 | public function getTargetLanguage() { |
346 | return $this->getOption( 'targetLanguage' ); |
347 | } |
348 | |
349 | /** |
350 | * Target language for the parse |
351 | * @param Language|null $x New value |
352 | * @return Language|null Old value |
353 | */ |
354 | public function setTargetLanguage( $x ) { |
355 | return $this->setOption( 'targetLanguage', $x ); |
356 | } |
357 | |
358 | /** |
359 | * Maximum size of template expansions, in bytes |
360 | * @return int |
361 | */ |
362 | public function getMaxIncludeSize() { |
363 | return $this->getOption( 'maxIncludeSize' ); |
364 | } |
365 | |
366 | /** |
367 | * Maximum size of template expansions, in bytes |
368 | * @param int|null $x New value (null is no change) |
369 | * @return int Old value |
370 | */ |
371 | public function setMaxIncludeSize( $x ) { |
372 | return $this->setOptionLegacy( 'maxIncludeSize', $x ); |
373 | } |
374 | |
375 | /** |
376 | * Maximum number of nodes touched by PPFrame::expand() |
377 | * @return int |
378 | */ |
379 | public function getMaxPPNodeCount() { |
380 | return $this->getOption( 'maxPPNodeCount' ); |
381 | } |
382 | |
383 | /** |
384 | * Maximum number of nodes touched by PPFrame::expand() |
385 | * @param int|null $x New value (null is no change) |
386 | * @return int Old value |
387 | */ |
388 | public function setMaxPPNodeCount( $x ) { |
389 | return $this->setOptionLegacy( 'maxPPNodeCount', $x ); |
390 | } |
391 | |
392 | /** |
393 | * Maximum recursion depth in PPFrame::expand() |
394 | * @return int |
395 | */ |
396 | public function getMaxPPExpandDepth() { |
397 | return $this->getOption( 'maxPPExpandDepth' ); |
398 | } |
399 | |
400 | /** |
401 | * Maximum recursion depth for templates within templates |
402 | * @return int |
403 | * @internal Only used by Parser (T318826) |
404 | */ |
405 | public function getMaxTemplateDepth() { |
406 | return $this->getOption( 'maxTemplateDepth' ); |
407 | } |
408 | |
409 | /** |
410 | * Maximum recursion depth for templates within templates |
411 | * @param int|null $x New value (null is no change) |
412 | * @return int Old value |
413 | * @internal Only used by ParserTestRunner (T318826) |
414 | */ |
415 | public function setMaxTemplateDepth( $x ) { |
416 | return $this->setOptionLegacy( 'maxTemplateDepth', $x ); |
417 | } |
418 | |
419 | /** |
420 | * Maximum number of calls per parse to expensive parser functions |
421 | * @since 1.20 |
422 | * @return int |
423 | */ |
424 | public function getExpensiveParserFunctionLimit() { |
425 | return $this->getOption( 'expensiveParserFunctionLimit' ); |
426 | } |
427 | |
428 | /** |
429 | * Maximum number of calls per parse to expensive parser functions |
430 | * @since 1.20 |
431 | * @param int|null $x New value (null is no change) |
432 | * @return int Old value |
433 | */ |
434 | public function setExpensiveParserFunctionLimit( $x ) { |
435 | return $this->setOptionLegacy( 'expensiveParserFunctionLimit', $x ); |
436 | } |
437 | |
438 | /** |
439 | * Remove HTML comments |
440 | * @warning Only applies to preprocess operations |
441 | * @return bool |
442 | */ |
443 | public function getRemoveComments() { |
444 | return $this->getOption( 'removeComments' ); |
445 | } |
446 | |
447 | /** |
448 | * Remove HTML comments |
449 | * @warning Only applies to preprocess operations |
450 | * @param bool|null $x New value (null is no change) |
451 | * @return bool Old value |
452 | */ |
453 | public function setRemoveComments( $x ) { |
454 | return $this->setOptionLegacy( 'removeComments', $x ); |
455 | } |
456 | |
457 | /** |
458 | * @deprecated since 1.38. This does nothing now, to control limit reporting |
459 | * please provide 'includeDebugInfo' option to ParserOutput::getText. |
460 | * |
461 | * Enable limit report in an HTML comment on output |
462 | * @return bool |
463 | */ |
464 | public function getEnableLimitReport() { |
465 | return false; |
466 | } |
467 | |
468 | /** |
469 | * @deprecated since 1.38. This does nothing now, to control limit reporting |
470 | * please provide 'includeDebugInfo' option to ParserOutput::getText. |
471 | * |
472 | * Enable limit report in an HTML comment on output |
473 | * @param bool|null $x New value (null is no change) |
474 | * @return bool Old value |
475 | */ |
476 | public function enableLimitReport( $x = true ) { |
477 | return false; |
478 | } |
479 | |
480 | /** |
481 | * Clean up signature texts? |
482 | * @see Parser::cleanSig |
483 | * @return bool |
484 | */ |
485 | public function getCleanSignatures() { |
486 | return $this->getOption( 'cleanSignatures' ); |
487 | } |
488 | |
489 | /** |
490 | * Clean up signature texts? |
491 | * @see Parser::cleanSig |
492 | * @param bool|null $x New value (null is no change) |
493 | * @return bool Old value |
494 | */ |
495 | public function setCleanSignatures( $x ) { |
496 | return $this->setOptionLegacy( 'cleanSignatures', $x ); |
497 | } |
498 | |
499 | /** |
500 | * Target attribute for external links |
501 | * @return string|false |
502 | * @internal Only set by installer (T317647) |
503 | */ |
504 | public function getExternalLinkTarget() { |
505 | return $this->getOption( 'externalLinkTarget' ); |
506 | } |
507 | |
508 | /** |
509 | * Target attribute for external links |
510 | * @param string|false|null $x New value (null is no change) |
511 | * @return string Old value |
512 | * @internal Only used by installer (T317647) |
513 | */ |
514 | public function setExternalLinkTarget( $x ) { |
515 | return $this->setOptionLegacy( 'externalLinkTarget', $x ); |
516 | } |
517 | |
518 | /** |
519 | * Whether content conversion should be disabled |
520 | * @return bool |
521 | */ |
522 | public function getDisableContentConversion() { |
523 | return $this->getOption( 'disableContentConversion' ); |
524 | } |
525 | |
526 | /** |
527 | * Whether content conversion should be disabled |
528 | * @param bool|null $x New value (null is no change) |
529 | * @return bool Old value |
530 | */ |
531 | public function disableContentConversion( $x = true ) { |
532 | return $this->setOptionLegacy( 'disableContentConversion', $x ); |
533 | } |
534 | |
535 | /** |
536 | * Whether title conversion should be disabled |
537 | * @return bool |
538 | */ |
539 | public function getDisableTitleConversion() { |
540 | return $this->getOption( 'disableTitleConversion' ); |
541 | } |
542 | |
543 | /** |
544 | * Whether title conversion should be disabled |
545 | * @param bool|null $x New value (null is no change) |
546 | * @return bool Old value |
547 | */ |
548 | public function disableTitleConversion( $x = true ) { |
549 | return $this->setOptionLegacy( 'disableTitleConversion', $x ); |
550 | } |
551 | |
552 | /** |
553 | * Thumb size preferred by the user. |
554 | * @return int |
555 | */ |
556 | public function getThumbSize() { |
557 | return $this->getOption( 'thumbsize' ); |
558 | } |
559 | |
560 | /** |
561 | * Thumb size preferred by the user. |
562 | * @param int|null $x New value (null is no change) |
563 | * @return int Old value |
564 | */ |
565 | public function setThumbSize( $x ) { |
566 | return $this->setOptionLegacy( 'thumbsize', $x ); |
567 | } |
568 | |
569 | /** |
570 | * Parsing the page for a "preview" operation? |
571 | * @return bool |
572 | */ |
573 | public function getIsPreview() { |
574 | return $this->getOption( 'isPreview' ); |
575 | } |
576 | |
577 | /** |
578 | * Parsing the page for a "preview" operation? |
579 | * @param bool|null $x New value (null is no change) |
580 | * @return bool Old value |
581 | */ |
582 | public function setIsPreview( $x ) { |
583 | return $this->setOptionLegacy( 'isPreview', $x ); |
584 | } |
585 | |
586 | /** |
587 | * Parsing the page for a "preview" operation on a single section? |
588 | * @return bool |
589 | */ |
590 | public function getIsSectionPreview() { |
591 | return $this->getOption( 'isSectionPreview' ); |
592 | } |
593 | |
594 | /** |
595 | * Parsing the page for a "preview" operation on a single section? |
596 | * @param bool|null $x New value (null is no change) |
597 | * @return bool Old value |
598 | */ |
599 | public function setIsSectionPreview( $x ) { |
600 | return $this->setOptionLegacy( 'isSectionPreview', $x ); |
601 | } |
602 | |
603 | /** |
604 | * Parsing the printable version of the page? |
605 | * @return bool |
606 | */ |
607 | public function getIsPrintable() { |
608 | return $this->getOption( 'printable' ); |
609 | } |
610 | |
611 | /** |
612 | * Parsing the printable version of the page? |
613 | * @param bool|null $x New value (null is no change) |
614 | * @return bool Old value |
615 | */ |
616 | public function setIsPrintable( $x ) { |
617 | return $this->setOptionLegacy( 'printable', $x ); |
618 | } |
619 | |
620 | /** |
621 | * Transform wiki markup when saving the page? |
622 | * @return bool |
623 | */ |
624 | public function getPreSaveTransform() { |
625 | return $this->getOption( 'preSaveTransform' ); |
626 | } |
627 | |
628 | /** |
629 | * Transform wiki markup when saving the page? |
630 | * @param bool|null $x New value (null is no change) |
631 | * @return bool Old value |
632 | */ |
633 | public function setPreSaveTransform( $x ) { |
634 | return $this->setOptionLegacy( 'preSaveTransform', $x ); |
635 | } |
636 | |
637 | /** |
638 | * Parsoid-format HTML output, or legacy wikitext parser HTML? |
639 | * @see T300191 |
640 | * @unstable |
641 | * @since 1.41 |
642 | * @return bool |
643 | */ |
644 | public function getUseParsoid(): bool { |
645 | return $this->getOption( 'useParsoid' ); |
646 | } |
647 | |
648 | /** |
649 | * Request Parsoid-format HTML output. |
650 | * @see T300191 |
651 | * @unstable |
652 | * @since 1.41 |
653 | */ |
654 | public function setUseParsoid() { |
655 | $this->setOption( 'useParsoid', true ); |
656 | } |
657 | |
658 | /** |
659 | * Date format index |
660 | * @return string |
661 | */ |
662 | public function getDateFormat() { |
663 | return $this->getOption( 'dateformat' ); |
664 | } |
665 | |
666 | /** |
667 | * Lazy initializer for dateFormat |
668 | * @param ParserOptions $popt |
669 | * @return string |
670 | */ |
671 | private static function initDateFormat( ParserOptions $popt ) { |
672 | $userFactory = MediaWikiServices::getInstance()->getUserFactory(); |
673 | return $userFactory->newFromUserIdentity( $popt->getUserIdentity() )->getDatePreference(); |
674 | } |
675 | |
676 | /** |
677 | * Date format index |
678 | * @param string|null $x New value (null is no change) |
679 | * @return string Old value |
680 | */ |
681 | public function setDateFormat( $x ) { |
682 | return $this->setOptionLegacy( 'dateformat', $x ); |
683 | } |
684 | |
685 | /** |
686 | * Get the user language used by the parser for this page and split the parser cache. |
687 | * |
688 | * @warning Calling this causes the parser cache to be fragmented by user language! |
689 | * To avoid cache fragmentation, output should not depend on the user language. |
690 | * Use Parser::getTargetLanguage() instead! |
691 | * |
692 | * @note This function will trigger a cache fragmentation by recording the |
693 | * 'userlang' option, see optionUsed(). This is done to avoid cache pollution |
694 | * when the page is rendered based on the language of the user. |
695 | * |
696 | * @note When saving, this will return the default language instead of the user's. |
697 | * {{int: }} uses this which used to produce inconsistent link tables (T16404). |
698 | * |
699 | * @return Language |
700 | * @since 1.19 |
701 | */ |
702 | public function getUserLangObj() { |
703 | return $this->getOption( 'userlang' ); |
704 | } |
705 | |
706 | /** |
707 | * Same as getUserLangObj() but returns a string instead. |
708 | * |
709 | * @warning Calling this causes the parser cache to be fragmented by user language! |
710 | * To avoid cache fragmentation, output should not depend on the user language. |
711 | * Use Parser::getTargetLanguage() instead! |
712 | * |
713 | * @see getUserLangObj() |
714 | * |
715 | * @return string Language code |
716 | * @since 1.17 |
717 | */ |
718 | public function getUserLang() { |
719 | return $this->getUserLangObj()->getCode(); |
720 | } |
721 | |
722 | /** |
723 | * Set the user language used by the parser for this page and split the parser cache. |
724 | * @param string|Language $x New value |
725 | * @return Language Old value |
726 | */ |
727 | public function setUserLang( $x ) { |
728 | if ( is_string( $x ) ) { |
729 | $x = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( $x ); |
730 | } |
731 | |
732 | return $this->setOptionLegacy( 'userlang', $x ); |
733 | } |
734 | |
735 | /** |
736 | * Are magic ISBN links enabled? |
737 | * @since 1.28 |
738 | * @return bool |
739 | */ |
740 | public function getMagicISBNLinks() { |
741 | return $this->getOption( 'magicISBNLinks' ); |
742 | } |
743 | |
744 | /** |
745 | * Are magic PMID links enabled? |
746 | * @since 1.28 |
747 | * @return bool |
748 | */ |
749 | public function getMagicPMIDLinks() { |
750 | return $this->getOption( 'magicPMIDLinks' ); |
751 | } |
752 | |
753 | /** |
754 | * Are magic RFC links enabled? |
755 | * @since 1.28 |
756 | * @return bool |
757 | */ |
758 | public function getMagicRFCLinks() { |
759 | return $this->getOption( 'magicRFCLinks' ); |
760 | } |
761 | |
762 | /** |
763 | * Should the table of contents be suppressed? |
764 | * Used when parsing "code" pages (like JavaScript) as wikitext |
765 | * for backlink support and categories, but where we don't want |
766 | * other metadata generated (like the table of contents). |
767 | * @see T307691 |
768 | * @since 1.39 |
769 | * @return bool |
770 | */ |
771 | public function getSuppressTOC() { |
772 | return $this->getOption( 'suppressTOC' ); |
773 | } |
774 | |
775 | /** |
776 | * Suppress generation of the table of contents. |
777 | * Used when parsing "code" pages (like JavaScript) as wikitext |
778 | * for backlink support and categories, but where we don't want |
779 | * other metadata generated (like the table of contents). |
780 | * @see T307691 |
781 | * @since 1.39 |
782 | * @deprecated since 1.42; just clear the metadata in the final |
783 | * parser output |
784 | */ |
785 | public function setSuppressTOC() { |
786 | wfDeprecated( __METHOD__, '1.42' ); |
787 | $this->setOption( 'suppressTOC', true ); |
788 | } |
789 | |
790 | /** |
791 | * Should section edit links be suppressed? |
792 | * Used when parsing wikitext which will be presented in a |
793 | * non-interactive context: previews, UX text, etc. |
794 | * @since 1.42 |
795 | * @return bool |
796 | */ |
797 | public function getSuppressSectionEditLinks() { |
798 | return $this->getOption( 'suppressSectionEditLinks' ); |
799 | } |
800 | |
801 | /** |
802 | * Suppress section edit links in the output. |
803 | * Used when parsing wikitext which will be presented in a |
804 | * non-interactive context: previews, UX text, etc. |
805 | * @since 1.42 |
806 | */ |
807 | public function setSuppressSectionEditLinks() { |
808 | $this->setOption( 'suppressSectionEditLinks', true ); |
809 | } |
810 | |
811 | /** |
812 | * Should section contents be wrapped in <div> to make them |
813 | * collapsible? |
814 | * @since 1.42 |
815 | */ |
816 | public function getCollapsibleSections(): bool { |
817 | return $this->getOption( 'collapsibleSections' ); |
818 | } |
819 | |
820 | /** |
821 | * Wrap section contents in a <div> to allow client-side code |
822 | * to collapse them. |
823 | * @since 1.42 |
824 | */ |
825 | public function setCollapsibleSections(): void { |
826 | $this->setOption( 'collapsibleSections', true ); |
827 | } |
828 | |
829 | /** |
830 | * If the wiki is configured to allow raw html ($wgRawHtml = true) |
831 | * is it allowed in the specific case of parsing this page. |
832 | * |
833 | * This is meant to disable unsafe parser tags in cases where |
834 | * a malicious user may control the input to the parser. |
835 | * |
836 | * @note This is expected to be true for normal pages even if the |
837 | * wiki has $wgRawHtml disabled in general. The setting only |
838 | * signifies that raw html would be unsafe in the current context |
839 | * provided that raw html is allowed at all. |
840 | * @since 1.29 |
841 | * @return bool |
842 | */ |
843 | public function getAllowUnsafeRawHtml() { |
844 | return $this->getOption( 'allowUnsafeRawHtml' ); |
845 | } |
846 | |
847 | /** |
848 | * If the wiki is configured to allow raw html ($wgRawHtml = true) |
849 | * is it allowed in the specific case of parsing this page. |
850 | * @see self::getAllowUnsafeRawHtml() |
851 | * @since 1.29 |
852 | * @param bool|null $x Value to set or null to get current value |
853 | * @return bool Current value for allowUnsafeRawHtml |
854 | */ |
855 | public function setAllowUnsafeRawHtml( $x ) { |
856 | return $this->setOptionLegacy( 'allowUnsafeRawHtml', $x ); |
857 | } |
858 | |
859 | /** |
860 | * Class to use to wrap output from Parser::parse() |
861 | * @since 1.30 |
862 | * @return string|false |
863 | */ |
864 | public function getWrapOutputClass() { |
865 | return $this->getOption( 'wrapclass' ); |
866 | } |
867 | |
868 | /** |
869 | * CSS class to use to wrap output from Parser::parse() |
870 | * @since 1.30 |
871 | * @param string $className Class name to use for wrapping. |
872 | * Passing false to indicate "no wrapping" was deprecated in MediaWiki 1.31. |
873 | * @return string|false Current value |
874 | */ |
875 | public function setWrapOutputClass( $className ) { |
876 | if ( $className === true ) { // DWIM, they probably want the default class name |
877 | $className = 'mw-parser-output'; |
878 | } |
879 | if ( $className === false ) { |
880 | wfDeprecated( __METHOD__ . '( false )', '1.31' ); |
881 | } |
882 | return $this->setOption( 'wrapclass', $className ); |
883 | } |
884 | |
885 | /** |
886 | * Callback for current revision fetching; first argument to call_user_func(). |
887 | * @internal |
888 | * @since 1.35 |
889 | * @return callable |
890 | */ |
891 | public function getCurrentRevisionRecordCallback() { |
892 | return $this->getOption( 'currentRevisionRecordCallback' ); |
893 | } |
894 | |
895 | /** |
896 | * Callback for current revision fetching; first argument to call_user_func(). |
897 | * @internal |
898 | * @since 1.35 |
899 | * @param callable|null $x New value |
900 | * @return callable Old value |
901 | */ |
902 | public function setCurrentRevisionRecordCallback( $x ) { |
903 | return $this->setOption( 'currentRevisionRecordCallback', $x ); |
904 | } |
905 | |
906 | /** |
907 | * Callback for template fetching; first argument to call_user_func(). |
908 | * @return callable |
909 | */ |
910 | public function getTemplateCallback() { |
911 | return $this->getOption( 'templateCallback' ); |
912 | } |
913 | |
914 | /** |
915 | * Callback for template fetching; first argument to call_user_func(). |
916 | * @param callable|null $x New value (null is no change) |
917 | * @return callable Old value |
918 | */ |
919 | public function setTemplateCallback( $x ) { |
920 | return $this->setOptionLegacy( 'templateCallback', $x ); |
921 | } |
922 | |
923 | /** |
924 | * A guess for {{REVISIONID}}, calculated using the callback provided via |
925 | * setSpeculativeRevIdCallback(). For consistency, the value will be calculated upon the |
926 | * first call of this method, and re-used for subsequent calls. |
927 | * |
928 | * If no callback was defined via setSpeculativeRevIdCallback(), this method will return false. |
929 | * |
930 | * @since 1.32 |
931 | * @return int|false |
932 | */ |
933 | public function getSpeculativeRevId() { |
934 | return $this->getOption( 'speculativeRevId' ); |
935 | } |
936 | |
937 | /** |
938 | * A guess for {{PAGEID}}, calculated using the callback provided via |
939 | * setSpeculativeRevPageCallback(). For consistency, the value will be calculated upon the |
940 | * first call of this method, and re-used for subsequent calls. |
941 | * |
942 | * If no callback was defined via setSpeculativePageIdCallback(), this method will return false. |
943 | * |
944 | * @since 1.34 |
945 | * @return int|false |
946 | */ |
947 | public function getSpeculativePageId() { |
948 | return $this->getOption( 'speculativePageId' ); |
949 | } |
950 | |
951 | /** |
952 | * Callback registered with ParserOptions::$lazyOptions, triggered by getSpeculativeRevId(). |
953 | * |
954 | * @param ParserOptions $popt |
955 | * @return int|false |
956 | */ |
957 | private static function initSpeculativeRevId( ParserOptions $popt ) { |
958 | $cb = $popt->getOption( 'speculativeRevIdCallback' ); |
959 | $id = $cb ? $cb() : null; |
960 | |
961 | // returning null would result in this being re-called every access |
962 | return $id ?? false; |
963 | } |
964 | |
965 | /** |
966 | * Callback registered with ParserOptions::$lazyOptions, triggered by getSpeculativePageId(). |
967 | * |
968 | * @param ParserOptions $popt |
969 | * @return int|false |
970 | */ |
971 | private static function initSpeculativePageId( ParserOptions $popt ) { |
972 | $cb = $popt->getOption( 'speculativePageIdCallback' ); |
973 | $id = $cb ? $cb() : null; |
974 | |
975 | // returning null would result in this being re-called every access |
976 | return $id ?? false; |
977 | } |
978 | |
979 | /** |
980 | * Callback to generate a guess for {{REVISIONID}} |
981 | * @param callable|null $x New value |
982 | * @return callable|null Old value |
983 | * @since 1.28 |
984 | */ |
985 | public function setSpeculativeRevIdCallback( $x ) { |
986 | $this->setOption( 'speculativeRevId', null ); // reset |
987 | return $this->setOption( 'speculativeRevIdCallback', $x ); |
988 | } |
989 | |
990 | /** |
991 | * Callback to generate a guess for {{PAGEID}} |
992 | * @param callable|null $x New value |
993 | * @return callable|null Old value |
994 | * @since 1.34 |
995 | */ |
996 | public function setSpeculativePageIdCallback( $x ) { |
997 | $this->setOption( 'speculativePageId', null ); // reset |
998 | return $this->setOption( 'speculativePageIdCallback', $x ); |
999 | } |
1000 | |
1001 | /** |
1002 | * Timestamp used for {{CURRENTDAY}} etc. |
1003 | * @return string TS_MW timestamp |
1004 | */ |
1005 | public function getTimestamp() { |
1006 | if ( !isset( $this->mTimestamp ) ) { |
1007 | $this->mTimestamp = wfTimestampNow(); |
1008 | } |
1009 | return $this->mTimestamp; |
1010 | } |
1011 | |
1012 | /** |
1013 | * Timestamp used for {{CURRENTDAY}} etc. |
1014 | * @param string|null $x New value (null is no change) |
1015 | * @return string Old value |
1016 | */ |
1017 | public function setTimestamp( $x ) { |
1018 | return wfSetVar( $this->mTimestamp, $x ); |
1019 | } |
1020 | |
1021 | /** |
1022 | * Note that setting or changing this does not *make* the page a redirect |
1023 | * or change its target, it merely records the information for reference |
1024 | * during the parse. |
1025 | * |
1026 | * @since 1.24 |
1027 | * @param Title|null $title |
1028 | */ |
1029 | public function setRedirectTarget( $title ) { |
1030 | $this->redirectTarget = $title; |
1031 | } |
1032 | |
1033 | /** |
1034 | * Get the previously-set redirect target. |
1035 | * |
1036 | * @since 1.24 |
1037 | * @return Title|null |
1038 | */ |
1039 | public function getRedirectTarget() { |
1040 | return $this->redirectTarget; |
1041 | } |
1042 | |
1043 | /** |
1044 | * Extra key that should be present in the parser cache key. |
1045 | * @warning Consider registering your additional options with the |
1046 | * ParserOptionsRegister hook instead of using this method. |
1047 | * @param string $key |
1048 | */ |
1049 | public function addExtraKey( $key ) { |
1050 | $this->mExtraKey .= '!' . $key; |
1051 | } |
1052 | |
1053 | /** |
1054 | * Get the identity of the user for whom the parse is made. |
1055 | * @since 1.36 |
1056 | * @return UserIdentity |
1057 | */ |
1058 | public function getUserIdentity(): UserIdentity { |
1059 | return $this->mUser; |
1060 | } |
1061 | |
1062 | /** |
1063 | * @param UserIdentity $user |
1064 | * @param Language|null $lang |
1065 | */ |
1066 | public function __construct( UserIdentity $user, $lang = null ) { |
1067 | if ( $lang === null ) { |
1068 | global $wgLang; |
1069 | StubObject::unstub( $wgLang ); |
1070 | $lang = $wgLang; |
1071 | } |
1072 | $this->initialiseFromUser( $user, $lang ); |
1073 | } |
1074 | |
1075 | /** |
1076 | * Get a ParserOptions object for an anonymous user |
1077 | * @since 1.27 |
1078 | * @return ParserOptions |
1079 | */ |
1080 | public static function newFromAnon() { |
1081 | return new ParserOptions( MediaWikiServices::getInstance()->getUserFactory()->newAnonymous(), |
1082 | MediaWikiServices::getInstance()->getContentLanguage() ); |
1083 | } |
1084 | |
1085 | /** |
1086 | * Get a ParserOptions object from a given user. |
1087 | * Language will be taken from $wgLang. |
1088 | * |
1089 | * @param UserIdentity $user |
1090 | * @return ParserOptions |
1091 | */ |
1092 | public static function newFromUser( $user ) { |
1093 | return new ParserOptions( $user ); |
1094 | } |
1095 | |
1096 | /** |
1097 | * Get a ParserOptions object from a given user and language |
1098 | * |
1099 | * @param UserIdentity $user |
1100 | * @param Language $lang |
1101 | * @return ParserOptions |
1102 | */ |
1103 | public static function newFromUserAndLang( UserIdentity $user, Language $lang ) { |
1104 | return new ParserOptions( $user, $lang ); |
1105 | } |
1106 | |
1107 | /** |
1108 | * Get a ParserOptions object from a IContextSource object |
1109 | * |
1110 | * @param IContextSource $context |
1111 | * @return ParserOptions |
1112 | */ |
1113 | public static function newFromContext( IContextSource $context ) { |
1114 | return new ParserOptions( $context->getUser(), $context->getLanguage() ); |
1115 | } |
1116 | |
1117 | /** |
1118 | * Creates a "canonical" ParserOptions object |
1119 | * |
1120 | * For historical reasons, certain options have default values that are |
1121 | * different from the canonical values used for caching. |
1122 | * |
1123 | * @since 1.30 |
1124 | * @since 1.32 Added string and IContextSource as options for the first parameter |
1125 | * @since 1.36 UserIdentity is also allowed |
1126 | * @deprecated since 1.38. Use ::newFromContext, ::newFromAnon or ::newFromUserAndLang instead. |
1127 | * Canonical ParserOptions are now exactly the same as non-canonical. |
1128 | * @param IContextSource|string|UserIdentity $context |
1129 | * - If an IContextSource, the options are initialized based on the source's UserIdentity and Language. |
1130 | * - If the string 'canonical', the options are initialized with an anonymous user and |
1131 | * the content language. |
1132 | * - If a UserIdentity, the options are initialized for that UserIdentity |
1133 | * 'userlang' is taken from the $userLang parameter, defaulting to $wgLang if that is null. |
1134 | * @param Language|StubObject|null $userLang (see above) |
1135 | * @return ParserOptions |
1136 | */ |
1137 | public static function newCanonical( $context, $userLang = null ) { |
1138 | if ( $context instanceof IContextSource ) { |
1139 | $ret = self::newFromContext( $context ); |
1140 | } elseif ( $context === 'canonical' ) { |
1141 | $ret = self::newFromAnon(); |
1142 | } elseif ( $context instanceof UserIdentity ) { |
1143 | $ret = new self( $context, $userLang ); |
1144 | } else { |
1145 | throw new InvalidArgumentException( |
1146 | '$context must be an IContextSource, the string "canonical", or a UserIdentity' |
1147 | ); |
1148 | } |
1149 | return $ret; |
1150 | } |
1151 | |
1152 | /** |
1153 | * Reset static caches |
1154 | * @internal For testing |
1155 | */ |
1156 | public static function clearStaticCache() { |
1157 | if ( !defined( 'MW_PHPUNIT_TEST' ) && !defined( 'MW_PARSER_TEST' ) ) { |
1158 | throw new LogicException( __METHOD__ . ' is just for testing' ); |
1159 | } |
1160 | self::$defaults = null; |
1161 | self::$lazyOptions = null; |
1162 | self::$cacheVaryingOptionsHash = null; |
1163 | } |
1164 | |
1165 | /** |
1166 | * Get default option values |
1167 | * @warning If you change the default for an existing option, all existing |
1168 | * parser cache entries will be invalid. To avoid bugs, you'll need to handle |
1169 | * that somehow (e.g. with the RejectParserCacheValue hook) because |
1170 | * MediaWiki won't do it for you. |
1171 | * @return array |
1172 | */ |
1173 | private static function getDefaults() { |
1174 | $services = MediaWikiServices::getInstance(); |
1175 | $mainConfig = $services->getMainConfig(); |
1176 | $interwikiMagic = $mainConfig->get( MainConfigNames::InterwikiMagic ); |
1177 | $allowExternalImages = $mainConfig->get( MainConfigNames::AllowExternalImages ); |
1178 | $allowExternalImagesFrom = $mainConfig->get( MainConfigNames::AllowExternalImagesFrom ); |
1179 | $enableImageWhitelist = $mainConfig->get( MainConfigNames::EnableImageWhitelist ); |
1180 | $allowSpecialInclusion = $mainConfig->get( MainConfigNames::AllowSpecialInclusion ); |
1181 | $maxArticleSize = $mainConfig->get( MainConfigNames::MaxArticleSize ); |
1182 | $maxPPNodeCount = $mainConfig->get( MainConfigNames::MaxPPNodeCount ); |
1183 | $maxTemplateDepth = $mainConfig->get( MainConfigNames::MaxTemplateDepth ); |
1184 | $maxPPExpandDepth = $mainConfig->get( MainConfigNames::MaxPPExpandDepth ); |
1185 | $cleanSignatures = $mainConfig->get( MainConfigNames::CleanSignatures ); |
1186 | $externalLinkTarget = $mainConfig->get( MainConfigNames::ExternalLinkTarget ); |
1187 | $expensiveParserFunctionLimit = $mainConfig->get( MainConfigNames::ExpensiveParserFunctionLimit ); |
1188 | $enableMagicLinks = $mainConfig->get( MainConfigNames::EnableMagicLinks ); |
1189 | $languageConverterFactory = $services->getLanguageConverterFactory(); |
1190 | $userOptionsLookup = $services->getUserOptionsLookup(); |
1191 | $contentLanguage = $services->getContentLanguage(); |
1192 | |
1193 | if ( self::$defaults === null ) { |
1194 | // *UPDATE* ParserOptions::matches() if any of this changes as needed |
1195 | self::$defaults = [ |
1196 | 'dateformat' => null, |
1197 | 'interfaceMessage' => false, |
1198 | 'targetLanguage' => null, |
1199 | 'removeComments' => true, |
1200 | 'suppressTOC' => false, |
1201 | 'suppressSectionEditLinks' => false, |
1202 | 'collapsibleSections' => false, |
1203 | 'enableLimitReport' => false, |
1204 | 'preSaveTransform' => true, |
1205 | 'isPreview' => false, |
1206 | 'isSectionPreview' => false, |
1207 | 'printable' => false, |
1208 | 'allowUnsafeRawHtml' => true, |
1209 | 'wrapclass' => 'mw-parser-output', |
1210 | 'currentRevisionRecordCallback' => [ Parser::class, 'statelessFetchRevisionRecord' ], |
1211 | 'templateCallback' => [ Parser::class, 'statelessFetchTemplate' ], |
1212 | 'speculativeRevIdCallback' => null, |
1213 | 'speculativeRevId' => null, |
1214 | 'speculativePageIdCallback' => null, |
1215 | 'speculativePageId' => null, |
1216 | 'useParsoid' => false, |
1217 | ]; |
1218 | |
1219 | self::$cacheVaryingOptionsHash = self::$initialCacheVaryingOptionsHash; |
1220 | self::$lazyOptions = self::$initialLazyOptions; |
1221 | |
1222 | ( new HookRunner( $services->getHookContainer() ) )->onParserOptionsRegister( |
1223 | self::$defaults, |
1224 | self::$cacheVaryingOptionsHash, |
1225 | self::$lazyOptions |
1226 | ); |
1227 | |
1228 | ksort( self::$cacheVaryingOptionsHash ); |
1229 | } |
1230 | |
1231 | // Unit tests depend on being able to modify the globals at will |
1232 | return self::$defaults + [ |
1233 | 'interwikiMagic' => $interwikiMagic, |
1234 | 'allowExternalImages' => $allowExternalImages, |
1235 | 'allowExternalImagesFrom' => $allowExternalImagesFrom, |
1236 | 'enableImageWhitelist' => $enableImageWhitelist, |
1237 | 'allowSpecialInclusion' => $allowSpecialInclusion, |
1238 | 'maxIncludeSize' => $maxArticleSize * 1024, |
1239 | 'maxPPNodeCount' => $maxPPNodeCount, |
1240 | 'maxPPExpandDepth' => $maxPPExpandDepth, |
1241 | 'maxTemplateDepth' => $maxTemplateDepth, |
1242 | 'expensiveParserFunctionLimit' => $expensiveParserFunctionLimit, |
1243 | 'externalLinkTarget' => $externalLinkTarget, |
1244 | 'cleanSignatures' => $cleanSignatures, |
1245 | 'disableContentConversion' => $languageConverterFactory->isConversionDisabled(), |
1246 | 'disableTitleConversion' => $languageConverterFactory->isLinkConversionDisabled(), |
1247 | // FIXME: The fallback to false for enableMagicLinks is a band-aid to allow |
1248 | // the phpunit entrypoint patch (I82045c207738d152d5b0006f353637cfaa40bb66) |
1249 | // to be merged. |
1250 | // It is possible that a test somewhere is globally resetting $wgEnableMagicLinks |
1251 | // to null, or that ParserOptions is somehow similarly getting reset in such a way |
1252 | // that $enableMagicLinks ends up as null rather than an array. This workaround |
1253 | // seems harmless, but would be nice to eventually fix the underlying issue. |
1254 | 'magicISBNLinks' => $enableMagicLinks['ISBN'] ?? false, |
1255 | 'magicPMIDLinks' => $enableMagicLinks['PMID'] ?? false, |
1256 | 'magicRFCLinks' => $enableMagicLinks['RFC'] ?? false, |
1257 | 'thumbsize' => $userOptionsLookup->getDefaultOption( 'thumbsize' ), |
1258 | 'userlang' => $contentLanguage, |
1259 | ]; |
1260 | } |
1261 | |
1262 | /** |
1263 | * Get user options |
1264 | * |
1265 | * @param UserIdentity $user |
1266 | * @param Language $lang |
1267 | */ |
1268 | private function initialiseFromUser( UserIdentity $user, Language $lang ) { |
1269 | // Initially lazy loaded option defaults must not be taken into account, |
1270 | // otherwise lazy loading does not work. Setting a default for lazy option |
1271 | // is useful for matching with canonical options. |
1272 | $this->options = $this->nullifyLazyOption( self::getDefaults() ); |
1273 | |
1274 | $this->mUser = $user; |
1275 | $services = MediaWikiServices::getInstance(); |
1276 | $optionsLookup = $services->getUserOptionsLookup(); |
1277 | $this->options['thumbsize'] = $optionsLookup->getOption( $user, 'thumbsize' ); |
1278 | $this->options['userlang'] = $lang; |
1279 | } |
1280 | |
1281 | /** |
1282 | * Check if these options match that of another options set |
1283 | * |
1284 | * This ignores report limit settings that only affect HTML comments |
1285 | * |
1286 | * @param ParserOptions $other |
1287 | * @return bool |
1288 | * @since 1.25 |
1289 | */ |
1290 | public function matches( ParserOptions $other ) { |
1291 | // Compare most options |
1292 | $options = array_keys( $this->options ); |
1293 | $options = array_diff( $options, [ |
1294 | 'enableLimitReport', // only affects HTML comments |
1295 | 'tidy', // Has no effect since 1.35; removed in 1.36 |
1296 | ] ); |
1297 | foreach ( $options as $option ) { |
1298 | // Resolve any lazy options |
1299 | $this->lazyLoadOption( $option ); |
1300 | $other->lazyLoadOption( $option ); |
1301 | |
1302 | $o1 = $this->optionToString( $this->options[$option] ); |
1303 | $o2 = $this->optionToString( $other->options[$option] ); |
1304 | if ( $o1 !== $o2 ) { |
1305 | return false; |
1306 | } |
1307 | } |
1308 | |
1309 | // Compare most other fields |
1310 | foreach ( ( new ReflectionClass( $this ) )->getProperties() as $property ) { |
1311 | $field = $property->getName(); |
1312 | if ( $property->isStatic() ) { |
1313 | continue; |
1314 | } |
1315 | if ( in_array( $field, [ |
1316 | 'options', // Already checked above |
1317 | 'onAccessCallback', // only used for ParserOutput option tracking |
1318 | ] ) ) { |
1319 | continue; |
1320 | } |
1321 | |
1322 | if ( !is_object( $this->$field ) && $this->$field !== $other->$field ) { |
1323 | return false; |
1324 | } |
1325 | } |
1326 | |
1327 | return true; |
1328 | } |
1329 | |
1330 | /** |
1331 | * @param ParserOptions $other |
1332 | * @return bool Whether the cache key relevant options match those of $other |
1333 | * @since 1.33 |
1334 | */ |
1335 | public function matchesForCacheKey( ParserOptions $other ) { |
1336 | foreach ( self::allCacheVaryingOptions() as $option ) { |
1337 | // Populate any lazy options |
1338 | $this->lazyLoadOption( $option ); |
1339 | $other->lazyLoadOption( $option ); |
1340 | |
1341 | $o1 = $this->optionToString( $this->options[$option] ); |
1342 | $o2 = $this->optionToString( $other->options[$option] ); |
1343 | if ( $o1 !== $o2 ) { |
1344 | return false; |
1345 | } |
1346 | } |
1347 | |
1348 | return true; |
1349 | } |
1350 | |
1351 | /** |
1352 | * Registers a callback for tracking which ParserOptions which are used. |
1353 | * |
1354 | * @since 1.16 |
1355 | * @param callable|null $callback |
1356 | */ |
1357 | public function registerWatcher( $callback ) { |
1358 | $this->onAccessCallback = $callback; |
1359 | } |
1360 | |
1361 | /** |
1362 | * Record that an option was internally accessed. |
1363 | * |
1364 | * This calls the watcher set by ParserOptions::registerWatcher(). |
1365 | * Typically, the watcher callback is ParserOutput::recordOption(). |
1366 | * The information registered this way is consumed by ParserCache::save(). |
1367 | * |
1368 | * @param string $optionName Name of the option |
1369 | */ |
1370 | private function optionUsed( $optionName ) { |
1371 | if ( $this->onAccessCallback ) { |
1372 | call_user_func( $this->onAccessCallback, $optionName ); |
1373 | } |
1374 | } |
1375 | |
1376 | /** |
1377 | * Return all option keys that vary the options hash |
1378 | * @since 1.30 |
1379 | * @return string[] |
1380 | */ |
1381 | public static function allCacheVaryingOptions() { |
1382 | return array_keys( array_filter( self::getCacheVaryingOptionsHash() ) ); |
1383 | } |
1384 | |
1385 | /** |
1386 | * Convert an option to a string value |
1387 | * @param mixed $value |
1388 | * @return string |
1389 | */ |
1390 | private function optionToString( $value ) { |
1391 | if ( $value === true ) { |
1392 | return '1'; |
1393 | } elseif ( $value === false ) { |
1394 | return '0'; |
1395 | } elseif ( $value === null ) { |
1396 | return ''; |
1397 | } elseif ( $value instanceof Language ) { |
1398 | return $value->getCode(); |
1399 | } elseif ( is_array( $value ) ) { |
1400 | return '[' . implode( ',', array_map( [ $this, 'optionToString' ], $value ) ) . ']'; |
1401 | } else { |
1402 | return (string)$value; |
1403 | } |
1404 | } |
1405 | |
1406 | /** |
1407 | * Generate a hash string with the values set on these ParserOptions |
1408 | * for the keys given in the array. |
1409 | * This will be used as part of the hash key for the parser cache, |
1410 | * so users sharing the options with vary for the same page share |
1411 | * the same cached data safely. |
1412 | * |
1413 | * @since 1.17 |
1414 | * @param string[] $forOptions |
1415 | * @param Title|null $title Used to get the content language of the page (since r97636) |
1416 | * @return string Page rendering hash |
1417 | */ |
1418 | public function optionsHash( $forOptions, $title = null ) { |
1419 | $renderHashAppend = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::RenderHashAppend ); |
1420 | |
1421 | $inCacheKey = self::allCacheVaryingOptions(); |
1422 | |
1423 | // Resolve any lazy options |
1424 | $lazyOpts = array_intersect( $forOptions, |
1425 | $inCacheKey, array_keys( self::getLazyOptions() ) ); |
1426 | foreach ( $lazyOpts as $k ) { |
1427 | $this->lazyLoadOption( $k ); |
1428 | } |
1429 | |
1430 | $options = $this->options; |
1431 | $defaults = self::getDefaults(); |
1432 | |
1433 | // We only include used options with non-canonical values in the key |
1434 | // so adding a new option doesn't invalidate the entire parser cache. |
1435 | // The drawback to this is that changing the default value of an option |
1436 | // requires manual invalidation of existing cache entries, as mentioned |
1437 | // in the docs on the relevant methods and hooks. |
1438 | $values = []; |
1439 | foreach ( array_intersect( $inCacheKey, $forOptions ) as $option ) { |
1440 | $v = $this->optionToString( $options[$option] ); |
1441 | $d = $this->optionToString( $defaults[$option] ); |
1442 | if ( $v !== $d ) { |
1443 | $values[] = "$option=$v"; |
1444 | } |
1445 | } |
1446 | |
1447 | $confstr = $values ? implode( '!', $values ) : 'canonical'; |
1448 | |
1449 | // add in language specific options, if any |
1450 | // @todo FIXME: This is just a way of retrieving the url/user preferred variant |
1451 | $services = MediaWikiServices::getInstance(); |
1452 | $lang = $title ? $title->getPageLanguage() : $services->getContentLanguage(); |
1453 | $converter = $services->getLanguageConverterFactory()->getLanguageConverter( $lang ); |
1454 | $confstr .= $converter->getExtraHashOptions(); |
1455 | |
1456 | $confstr .= $renderHashAppend; |
1457 | |
1458 | if ( $this->mExtraKey != '' ) { |
1459 | $confstr .= $this->mExtraKey; |
1460 | } |
1461 | |
1462 | $user = $services->getUserFactory()->newFromUserIdentity( $this->getUserIdentity() ); |
1463 | // Give a chance for extensions to modify the hash, if they have |
1464 | // extra options or other effects on the parser cache. |
1465 | ( new HookRunner( $services->getHookContainer() ) )->onPageRenderingHash( |
1466 | $confstr, |
1467 | $user, |
1468 | $forOptions |
1469 | ); |
1470 | |
1471 | // Make it a valid memcached key fragment |
1472 | $confstr = str_replace( ' ', '_', $confstr ); |
1473 | |
1474 | return $confstr; |
1475 | } |
1476 | |
1477 | /** |
1478 | * Test whether these options are safe to cache |
1479 | * @param string[]|null $usedOptions the list of options actually used in the parse. Defaults to all options. |
1480 | * @return bool |
1481 | * @since 1.30 |
1482 | */ |
1483 | public function isSafeToCache( array $usedOptions = null ) { |
1484 | $defaults = self::getDefaults(); |
1485 | $inCacheKey = self::getCacheVaryingOptionsHash(); |
1486 | $usedOptions ??= array_keys( $this->options ); |
1487 | foreach ( $usedOptions as $option ) { |
1488 | if ( empty( $inCacheKey[$option] ) && empty( self::$callbacks[$option] ) ) { |
1489 | $v = $this->optionToString( $this->options[$option] ?? null ); |
1490 | $d = $this->optionToString( $defaults[$option] ?? null ); |
1491 | if ( $v !== $d ) { |
1492 | return false; |
1493 | } |
1494 | } |
1495 | } |
1496 | return true; |
1497 | } |
1498 | |
1499 | /** |
1500 | * Sets a hook to force that a page exists, and sets a current revision callback to return |
1501 | * a revision with custom content when the current revision of the page is requested. |
1502 | * |
1503 | * @since 1.25 |
1504 | * @param Title $title |
1505 | * @param Content $content |
1506 | * @param UserIdentity $user The user that the fake revision is attributed to |
1507 | * @return ScopedCallback to unset the hook |
1508 | */ |
1509 | public function setupFakeRevision( $title, $content, $user ) { |
1510 | $oldCallback = $this->setCurrentRevisionRecordCallback( |
1511 | static function ( |
1512 | $titleToCheck, $parser = null ) use ( $title, $content, $user, &$oldCallback |
1513 | ) { |
1514 | if ( $titleToCheck->equals( $title ) ) { |
1515 | $revRecord = new MutableRevisionRecord( $title ); |
1516 | $revRecord->setContent( SlotRecord::MAIN, $content ) |
1517 | ->setUser( $user ) |
1518 | ->setTimestamp( MWTimestamp::now( TS_MW ) ) |
1519 | ->setPageId( $title->getArticleID() ) |
1520 | ->setParentId( $title->getLatestRevID() ); |
1521 | return $revRecord; |
1522 | } else { |
1523 | return call_user_func( $oldCallback, $titleToCheck, $parser ); |
1524 | } |
1525 | } |
1526 | ); |
1527 | |
1528 | $hookContainer = MediaWikiServices::getInstance()->getHookContainer(); |
1529 | $hookScope = $hookContainer->scopedRegister( |
1530 | 'TitleExists', |
1531 | static function ( $titleToCheck, &$exists ) use ( $title ) { |
1532 | if ( $titleToCheck->equals( $title ) ) { |
1533 | $exists = true; |
1534 | } |
1535 | } |
1536 | ); |
1537 | |
1538 | $linkCache = MediaWikiServices::getInstance()->getLinkCache(); |
1539 | $linkCache->clearBadLink( $title->getPrefixedDBkey() ); |
1540 | |
1541 | return new ScopedCallback( function () use ( $title, $hookScope, $linkCache, $oldCallback ) { |
1542 | ScopedCallback::consume( $hookScope ); |
1543 | $linkCache->clearLink( $title ); |
1544 | $this->setCurrentRevisionRecordCallback( $oldCallback ); |
1545 | } ); |
1546 | } |
1547 | |
1548 | /** |
1549 | * Returns reason for rendering the content. This human-readable, intended for logging and debugging only. |
1550 | * Expected values include "edit", "view", "purge", "LinksUpdate", etc. |
1551 | * @return string |
1552 | */ |
1553 | public function getRenderReason(): string { |
1554 | return $this->renderReason; |
1555 | } |
1556 | |
1557 | /** |
1558 | * Sets reason for rendering the content. This human-readable, intended for logging and debugging only. |
1559 | * Expected values include "edit", "view", "purge", "LinksUpdate", etc. |
1560 | * @param string $renderReason |
1561 | */ |
1562 | public function setRenderReason( string $renderReason ): void { |
1563 | $this->renderReason = $renderReason; |
1564 | } |
1565 | } |
1566 | |
1567 | /** |
1568 | * For really cool vim folding this needs to be at the end: |
1569 | * vim: foldmarker=@{,@} foldmethod=marker |
1570 | */ |