This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Preparation for the final page rendering. |
||
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 | */ |
||
22 | |||
23 | use MediaWiki\Logger\LoggerFactory; |
||
24 | use MediaWiki\Session\SessionManager; |
||
25 | use WrappedString\WrappedString; |
||
26 | use WrappedString\WrappedStringList; |
||
27 | |||
28 | /** |
||
29 | * This class should be covered by a general architecture document which does |
||
30 | * not exist as of January 2011. This is one of the Core classes and should |
||
31 | * be read at least once by any new developers. |
||
32 | * |
||
33 | * This class is used to prepare the final rendering. A skin is then |
||
34 | * applied to the output parameters (links, javascript, html, categories ...). |
||
35 | * |
||
36 | * @todo FIXME: Another class handles sending the whole page to the client. |
||
37 | * |
||
38 | * Some comments comes from a pairing session between Zak Greant and Antoine Musso |
||
39 | * in November 2010. |
||
40 | * |
||
41 | * @todo document |
||
42 | */ |
||
43 | class OutputPage extends ContextSource { |
||
44 | /** @var array Should be private. Used with addMeta() which adds "<meta>" */ |
||
45 | protected $mMetatags = []; |
||
46 | |||
47 | /** @var array */ |
||
48 | protected $mLinktags = []; |
||
49 | |||
50 | /** @var bool */ |
||
51 | protected $mCanonicalUrl = false; |
||
52 | |||
53 | /** |
||
54 | * @var array Additional stylesheets. Looks like this is for extensions. |
||
55 | * Might be replaced by ResourceLoader. |
||
56 | */ |
||
57 | protected $mExtStyles = []; |
||
58 | |||
59 | /** |
||
60 | * @var string Should be private - has getter and setter. Contains |
||
61 | * the HTML title */ |
||
62 | public $mPagetitle = ''; |
||
63 | |||
64 | /** |
||
65 | * @var string Contains all of the "<body>" content. Should be private we |
||
66 | * got set/get accessors and the append() method. |
||
67 | */ |
||
68 | public $mBodytext = ''; |
||
69 | |||
70 | /** @var string Stores contents of "<title>" tag */ |
||
71 | private $mHTMLtitle = ''; |
||
72 | |||
73 | /** |
||
74 | * @var bool Is the displayed content related to the source of the |
||
75 | * corresponding wiki article. |
||
76 | */ |
||
77 | private $mIsarticle = false; |
||
78 | |||
79 | /** @var bool Stores "article flag" toggle. */ |
||
80 | private $mIsArticleRelated = true; |
||
81 | |||
82 | /** |
||
83 | * @var bool We have to set isPrintable(). Some pages should |
||
84 | * never be printed (ex: redirections). |
||
85 | */ |
||
86 | private $mPrintable = false; |
||
87 | |||
88 | /** |
||
89 | * @var array Contains the page subtitle. Special pages usually have some |
||
90 | * links here. Don't confuse with site subtitle added by skins. |
||
91 | */ |
||
92 | private $mSubtitle = []; |
||
93 | |||
94 | /** @var string */ |
||
95 | public $mRedirect = ''; |
||
96 | |||
97 | /** @var int */ |
||
98 | protected $mStatusCode; |
||
99 | |||
100 | /** |
||
101 | * @var string Used for sending cache control. |
||
102 | * The whole caching system should probably be moved into its own class. |
||
103 | */ |
||
104 | protected $mLastModified = ''; |
||
105 | |||
106 | /** @var array */ |
||
107 | protected $mCategoryLinks = []; |
||
108 | |||
109 | /** @var array */ |
||
110 | protected $mCategories = []; |
||
111 | |||
112 | /** @var array */ |
||
113 | protected $mIndicators = []; |
||
114 | |||
115 | /** @var array Array of Interwiki Prefixed (non DB key) Titles (e.g. 'fr:Test page') */ |
||
116 | private $mLanguageLinks = []; |
||
117 | |||
118 | /** |
||
119 | * Used for JavaScript (predates ResourceLoader) |
||
120 | * @todo We should split JS / CSS. |
||
121 | * mScripts content is inserted as is in "<head>" by Skin. This might |
||
122 | * contain either a link to a stylesheet or inline CSS. |
||
123 | */ |
||
124 | private $mScripts = ''; |
||
125 | |||
126 | /** @var string Inline CSS styles. Use addInlineStyle() sparingly */ |
||
127 | protected $mInlineStyles = ''; |
||
128 | |||
129 | /** |
||
130 | * @var string Used by skin template. |
||
131 | * Example: $tpl->set( 'displaytitle', $out->mPageLinkTitle ); |
||
132 | */ |
||
133 | public $mPageLinkTitle = ''; |
||
134 | |||
135 | /** @var array Array of elements in "<head>". Parser might add its own headers! */ |
||
136 | protected $mHeadItems = []; |
||
137 | |||
138 | /** @var array */ |
||
139 | protected $mModules = []; |
||
140 | |||
141 | /** @var array */ |
||
142 | protected $mModuleScripts = []; |
||
143 | |||
144 | /** @var array */ |
||
145 | protected $mModuleStyles = []; |
||
146 | |||
147 | /** @var ResourceLoader */ |
||
148 | protected $mResourceLoader; |
||
149 | |||
150 | /** @var ResourceLoaderClientHtml */ |
||
151 | private $rlClient; |
||
152 | |||
153 | /** @var ResourceLoaderContext */ |
||
154 | private $rlClientContext; |
||
155 | |||
156 | /** @var string */ |
||
157 | private $rlUserModuleState; |
||
158 | |||
159 | /** @var array */ |
||
160 | private $rlExemptStyleModules; |
||
161 | |||
162 | /** @var array */ |
||
163 | protected $mJsConfigVars = []; |
||
164 | |||
165 | /** @var array */ |
||
166 | protected $mTemplateIds = []; |
||
167 | |||
168 | /** @var array */ |
||
169 | protected $mImageTimeKeys = []; |
||
170 | |||
171 | /** @var string */ |
||
172 | public $mRedirectCode = ''; |
||
173 | |||
174 | protected $mFeedLinksAppendQuery = null; |
||
175 | |||
176 | /** @var array |
||
177 | * What level of 'untrustworthiness' is allowed in CSS/JS modules loaded on this page? |
||
178 | * @see ResourceLoaderModule::$origin |
||
179 | * ResourceLoaderModule::ORIGIN_ALL is assumed unless overridden; |
||
180 | */ |
||
181 | protected $mAllowedModules = [ |
||
182 | ResourceLoaderModule::TYPE_COMBINED => ResourceLoaderModule::ORIGIN_ALL, |
||
183 | ]; |
||
184 | |||
185 | /** @var bool Whether output is disabled. If this is true, the 'output' method will do nothing. */ |
||
186 | protected $mDoNothing = false; |
||
187 | |||
188 | // Parser related. |
||
189 | |||
190 | /** @var int */ |
||
191 | protected $mContainsNewMagic = 0; |
||
192 | |||
193 | /** |
||
194 | * lazy initialised, use parserOptions() |
||
195 | * @var ParserOptions |
||
196 | */ |
||
197 | protected $mParserOptions = null; |
||
198 | |||
199 | /** |
||
200 | * Handles the Atom / RSS links. |
||
201 | * We probably only support Atom in 2011. |
||
202 | * @see $wgAdvertisedFeedTypes |
||
203 | */ |
||
204 | private $mFeedLinks = []; |
||
205 | |||
206 | // Gwicke work on squid caching? Roughly from 2003. |
||
207 | protected $mEnableClientCache = true; |
||
208 | |||
209 | /** @var bool Flag if output should only contain the body of the article. */ |
||
210 | private $mArticleBodyOnly = false; |
||
211 | |||
212 | /** @var bool */ |
||
213 | protected $mNewSectionLink = false; |
||
214 | |||
215 | /** @var bool */ |
||
216 | protected $mHideNewSectionLink = false; |
||
217 | |||
218 | /** |
||
219 | * @var bool Comes from the parser. This was probably made to load CSS/JS |
||
220 | * only if we had "<gallery>". Used directly in CategoryPage.php. |
||
221 | * Looks like ResourceLoader can replace this. |
||
222 | */ |
||
223 | public $mNoGallery = false; |
||
224 | |||
225 | /** @var string */ |
||
226 | private $mPageTitleActionText = ''; |
||
227 | |||
228 | /** @var int Cache stuff. Looks like mEnableClientCache */ |
||
229 | protected $mCdnMaxage = 0; |
||
230 | /** @var int Upper limit on mCdnMaxage */ |
||
231 | protected $mCdnMaxageLimit = INF; |
||
232 | |||
233 | /** |
||
234 | * @var bool Controls if anti-clickjacking / frame-breaking headers will |
||
235 | * be sent. This should be done for pages where edit actions are possible. |
||
236 | * Setters: $this->preventClickjacking() and $this->allowClickjacking(). |
||
237 | */ |
||
238 | protected $mPreventClickjacking = true; |
||
239 | |||
240 | /** @var int To include the variable {{REVISIONID}} */ |
||
241 | private $mRevisionId = null; |
||
242 | |||
243 | /** @var string */ |
||
244 | private $mRevisionTimestamp = null; |
||
245 | |||
246 | /** @var array */ |
||
247 | protected $mFileVersion = null; |
||
248 | |||
249 | /** |
||
250 | * @var array An array of stylesheet filenames (relative from skins path), |
||
251 | * with options for CSS media, IE conditions, and RTL/LTR direction. |
||
252 | * For internal use; add settings in the skin via $this->addStyle() |
||
253 | * |
||
254 | * Style again! This seems like a code duplication since we already have |
||
255 | * mStyles. This is what makes Open Source amazing. |
||
256 | */ |
||
257 | protected $styles = []; |
||
258 | |||
259 | private $mIndexPolicy = 'index'; |
||
260 | private $mFollowPolicy = 'follow'; |
||
261 | private $mVaryHeader = [ |
||
262 | 'Accept-Encoding' => [ 'match=gzip' ], |
||
263 | ]; |
||
264 | |||
265 | /** |
||
266 | * If the current page was reached through a redirect, $mRedirectedFrom contains the Title |
||
267 | * of the redirect. |
||
268 | * |
||
269 | * @var Title |
||
270 | */ |
||
271 | private $mRedirectedFrom = null; |
||
272 | |||
273 | /** |
||
274 | * Additional key => value data |
||
275 | */ |
||
276 | private $mProperties = []; |
||
277 | |||
278 | /** |
||
279 | * @var string|null ResourceLoader target for load.php links. If null, will be omitted |
||
280 | */ |
||
281 | private $mTarget = null; |
||
282 | |||
283 | /** |
||
284 | * @var bool Whether parser output should contain table of contents |
||
285 | */ |
||
286 | private $mEnableTOC = true; |
||
287 | |||
288 | /** |
||
289 | * @var bool Whether parser output should contain section edit links |
||
290 | */ |
||
291 | private $mEnableSectionEditLinks = true; |
||
292 | |||
293 | /** |
||
294 | * @var string|null The URL to send in a <link> element with rel=copyright |
||
295 | */ |
||
296 | private $copyrightUrl; |
||
297 | |||
298 | /** |
||
299 | * Constructor for OutputPage. This should not be called directly. |
||
300 | * Instead a new RequestContext should be created and it will implicitly create |
||
301 | * a OutputPage tied to that context. |
||
302 | * @param IContextSource|null $context |
||
303 | */ |
||
304 | function __construct( IContextSource $context = null ) { |
||
305 | if ( $context === null ) { |
||
306 | # Extensions should use `new RequestContext` instead of `new OutputPage` now. |
||
307 | wfDeprecated( __METHOD__, '1.18' ); |
||
308 | } else { |
||
309 | $this->setContext( $context ); |
||
310 | } |
||
311 | } |
||
312 | |||
313 | /** |
||
314 | * Redirect to $url rather than displaying the normal page |
||
315 | * |
||
316 | * @param string $url URL |
||
317 | * @param string $responsecode HTTP status code |
||
318 | */ |
||
319 | public function redirect( $url, $responsecode = '302' ) { |
||
320 | # Strip newlines as a paranoia check for header injection in PHP<5.1.2 |
||
321 | $this->mRedirect = str_replace( "\n", '', $url ); |
||
322 | $this->mRedirectCode = $responsecode; |
||
323 | } |
||
324 | |||
325 | /** |
||
326 | * Get the URL to redirect to, or an empty string if not redirect URL set |
||
327 | * |
||
328 | * @return string |
||
329 | */ |
||
330 | public function getRedirect() { |
||
331 | return $this->mRedirect; |
||
332 | } |
||
333 | |||
334 | /** |
||
335 | * Set the copyright URL to send with the output. |
||
336 | * Empty string to omit, null to reset. |
||
337 | * |
||
338 | * @since 1.26 |
||
339 | * |
||
340 | * @param string|null $url |
||
341 | */ |
||
342 | public function setCopyrightUrl( $url ) { |
||
343 | $this->copyrightUrl = $url; |
||
344 | } |
||
345 | |||
346 | /** |
||
347 | * Set the HTTP status code to send with the output. |
||
348 | * |
||
349 | * @param int $statusCode |
||
350 | */ |
||
351 | public function setStatusCode( $statusCode ) { |
||
352 | $this->mStatusCode = $statusCode; |
||
353 | } |
||
354 | |||
355 | /** |
||
356 | * Add a new "<meta>" tag |
||
357 | * To add an http-equiv meta tag, precede the name with "http:" |
||
358 | * |
||
359 | * @param string $name Tag name |
||
360 | * @param string $val Tag value |
||
361 | */ |
||
362 | function addMeta( $name, $val ) { |
||
363 | array_push( $this->mMetatags, [ $name, $val ] ); |
||
364 | } |
||
365 | |||
366 | /** |
||
367 | * Returns the current <meta> tags |
||
368 | * |
||
369 | * @since 1.25 |
||
370 | * @return array |
||
371 | */ |
||
372 | public function getMetaTags() { |
||
373 | return $this->mMetatags; |
||
374 | } |
||
375 | |||
376 | /** |
||
377 | * Add a new \<link\> tag to the page header. |
||
378 | * |
||
379 | * Note: use setCanonicalUrl() for rel=canonical. |
||
380 | * |
||
381 | * @param array $linkarr Associative array of attributes. |
||
382 | */ |
||
383 | function addLink( array $linkarr ) { |
||
384 | array_push( $this->mLinktags, $linkarr ); |
||
385 | } |
||
386 | |||
387 | /** |
||
388 | * Returns the current <link> tags |
||
389 | * |
||
390 | * @since 1.25 |
||
391 | * @return array |
||
392 | */ |
||
393 | public function getLinkTags() { |
||
394 | return $this->mLinktags; |
||
395 | } |
||
396 | |||
397 | /** |
||
398 | * Add a new \<link\> with "rel" attribute set to "meta" |
||
399 | * |
||
400 | * @param array $linkarr Associative array mapping attribute names to their |
||
401 | * values, both keys and values will be escaped, and the |
||
402 | * "rel" attribute will be automatically added |
||
403 | */ |
||
404 | function addMetadataLink( array $linkarr ) { |
||
405 | $linkarr['rel'] = $this->getMetadataAttribute(); |
||
406 | $this->addLink( $linkarr ); |
||
407 | } |
||
408 | |||
409 | /** |
||
410 | * Set the URL to be used for the <link rel=canonical>. This should be used |
||
411 | * in preference to addLink(), to avoid duplicate link tags. |
||
412 | * @param string $url |
||
413 | */ |
||
414 | function setCanonicalUrl( $url ) { |
||
415 | $this->mCanonicalUrl = $url; |
||
0 ignored issues
–
show
|
|||
416 | } |
||
417 | |||
418 | /** |
||
419 | * Returns the URL to be used for the <link rel=canonical> if |
||
420 | * one is set. |
||
421 | * |
||
422 | * @since 1.25 |
||
423 | * @return bool|string |
||
424 | */ |
||
425 | public function getCanonicalUrl() { |
||
426 | return $this->mCanonicalUrl; |
||
427 | } |
||
428 | |||
429 | /** |
||
430 | * Get the value of the "rel" attribute for metadata links |
||
431 | * |
||
432 | * @return string |
||
433 | */ |
||
434 | public function getMetadataAttribute() { |
||
435 | # note: buggy CC software only reads first "meta" link |
||
436 | static $haveMeta = false; |
||
437 | if ( $haveMeta ) { |
||
438 | return 'alternate meta'; |
||
439 | } else { |
||
440 | $haveMeta = true; |
||
441 | return 'meta'; |
||
442 | } |
||
443 | } |
||
444 | |||
445 | /** |
||
446 | * Add raw HTML to the list of scripts (including \<script\> tag, etc.) |
||
447 | * Internal use only. Use OutputPage::addModules() or OutputPage::addJsConfigVars() |
||
448 | * if possible. |
||
449 | * |
||
450 | * @param string $script Raw HTML |
||
451 | */ |
||
452 | function addScript( $script ) { |
||
453 | $this->mScripts .= $script; |
||
454 | } |
||
455 | |||
456 | /** |
||
457 | * Register and add a stylesheet from an extension directory. |
||
458 | * |
||
459 | * @deprecated since 1.27 use addModuleStyles() or addStyle() instead |
||
460 | * @param string $url Path to sheet. Provide either a full url (beginning |
||
461 | * with 'http', etc) or a relative path from the document root |
||
462 | * (beginning with '/'). Otherwise it behaves identically to |
||
463 | * addStyle() and draws from the /skins folder. |
||
464 | */ |
||
465 | public function addExtensionStyle( $url ) { |
||
466 | wfDeprecated( __METHOD__, '1.27' ); |
||
467 | array_push( $this->mExtStyles, $url ); |
||
468 | } |
||
469 | |||
470 | /** |
||
471 | * Get all styles added by extensions |
||
472 | * |
||
473 | * @deprecated since 1.27 |
||
474 | * @return array |
||
475 | */ |
||
476 | function getExtStyle() { |
||
477 | wfDeprecated( __METHOD__, '1.27' ); |
||
478 | return $this->mExtStyles; |
||
479 | } |
||
480 | |||
481 | /** |
||
482 | * Add a JavaScript file out of skins/common, or a given relative path. |
||
483 | * Internal use only. Use OutputPage::addModules() if possible. |
||
484 | * |
||
485 | * @param string $file Filename in skins/common or complete on-server path |
||
486 | * (/foo/bar.js) |
||
487 | * @param string $version Style version of the file. Defaults to $wgStyleVersion |
||
488 | */ |
||
489 | public function addScriptFile( $file, $version = null ) { |
||
490 | // See if $file parameter is an absolute URL or begins with a slash |
||
491 | if ( substr( $file, 0, 1 ) == '/' || preg_match( '#^[a-z]*://#i', $file ) ) { |
||
492 | $path = $file; |
||
493 | } else { |
||
494 | $path = $this->getConfig()->get( 'StylePath' ) . "/common/{$file}"; |
||
495 | } |
||
496 | if ( is_null( $version ) ) { |
||
497 | $version = $this->getConfig()->get( 'StyleVersion' ); |
||
498 | } |
||
499 | $this->addScript( Html::linkedScript( wfAppendQuery( $path, $version ) ) ); |
||
500 | } |
||
501 | |||
502 | /** |
||
503 | * Add a self-contained script tag with the given contents |
||
504 | * Internal use only. Use OutputPage::addModules() if possible. |
||
505 | * |
||
506 | * @param string $script JavaScript text, no script tags |
||
507 | */ |
||
508 | public function addInlineScript( $script ) { |
||
509 | $this->mScripts .= Html::inlineScript( $script ); |
||
510 | } |
||
511 | |||
512 | /** |
||
513 | * Filter an array of modules to remove insufficiently trustworthy members, and modules |
||
514 | * which are no longer registered (eg a page is cached before an extension is disabled) |
||
515 | * @param array $modules |
||
516 | * @param string|null $position If not null, only return modules with this position |
||
517 | * @param string $type |
||
518 | * @return array |
||
519 | */ |
||
520 | protected function filterModules( array $modules, $position = null, |
||
521 | $type = ResourceLoaderModule::TYPE_COMBINED |
||
522 | ) { |
||
523 | $resourceLoader = $this->getResourceLoader(); |
||
524 | $filteredModules = []; |
||
525 | foreach ( $modules as $val ) { |
||
526 | $module = $resourceLoader->getModule( $val ); |
||
527 | if ( $module instanceof ResourceLoaderModule |
||
528 | && $module->getOrigin() <= $this->getAllowedModules( $type ) |
||
529 | && ( is_null( $position ) || $module->getPosition() == $position ) |
||
530 | && ( !$this->mTarget || in_array( $this->mTarget, $module->getTargets() ) ) |
||
0 ignored issues
–
show
The expression
$this->mTarget of type string|null is loosely compared to false ; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
![]() |
|||
531 | ) { |
||
532 | $filteredModules[] = $val; |
||
533 | } |
||
534 | } |
||
535 | return $filteredModules; |
||
536 | } |
||
537 | |||
538 | /** |
||
539 | * Get the list of modules to include on this page |
||
540 | * |
||
541 | * @param bool $filter Whether to filter out insufficiently trustworthy modules |
||
542 | * @param string|null $position If not null, only return modules with this position |
||
543 | * @param string $param |
||
544 | * @return array Array of module names |
||
545 | */ |
||
546 | public function getModules( $filter = false, $position = null, $param = 'mModules', |
||
547 | $type = ResourceLoaderModule::TYPE_COMBINED |
||
548 | ) { |
||
549 | $modules = array_values( array_unique( $this->$param ) ); |
||
550 | return $filter |
||
551 | ? $this->filterModules( $modules, $position, $type ) |
||
552 | : $modules; |
||
553 | } |
||
554 | |||
555 | /** |
||
556 | * Add one or more modules recognized by ResourceLoader. Modules added |
||
557 | * through this function will be loaded by ResourceLoader when the |
||
558 | * page loads. |
||
559 | * |
||
560 | * @param string|array $modules Module name (string) or array of module names |
||
561 | */ |
||
562 | public function addModules( $modules ) { |
||
563 | $this->mModules = array_merge( $this->mModules, (array)$modules ); |
||
564 | } |
||
565 | |||
566 | /** |
||
567 | * Get the list of module JS to include on this page |
||
568 | * |
||
569 | * @param bool $filter |
||
570 | * @param string|null $position |
||
571 | * @return array Array of module names |
||
572 | */ |
||
573 | public function getModuleScripts( $filter = false, $position = null ) { |
||
574 | return $this->getModules( $filter, $position, 'mModuleScripts', |
||
575 | ResourceLoaderModule::TYPE_SCRIPTS |
||
576 | ); |
||
577 | } |
||
578 | |||
579 | /** |
||
580 | * Add only JS of one or more modules recognized by ResourceLoader. Module |
||
581 | * scripts added through this function will be loaded by ResourceLoader when |
||
582 | * the page loads. |
||
583 | * |
||
584 | * @param string|array $modules Module name (string) or array of module names |
||
585 | */ |
||
586 | public function addModuleScripts( $modules ) { |
||
587 | $this->mModuleScripts = array_merge( $this->mModuleScripts, (array)$modules ); |
||
588 | } |
||
589 | |||
590 | /** |
||
591 | * Get the list of module CSS to include on this page |
||
592 | * |
||
593 | * @param bool $filter |
||
594 | * @param string|null $position |
||
595 | * @return array Array of module names |
||
596 | */ |
||
597 | public function getModuleStyles( $filter = false, $position = null ) { |
||
598 | return $this->getModules( $filter, $position, 'mModuleStyles', |
||
599 | ResourceLoaderModule::TYPE_STYLES |
||
600 | ); |
||
601 | } |
||
602 | |||
603 | /** |
||
604 | * Add only CSS of one or more modules recognized by ResourceLoader. |
||
605 | * |
||
606 | * Module styles added through this function will be added using standard link CSS |
||
607 | * tags, rather than as a combined Javascript and CSS package. Thus, they will |
||
608 | * load when JavaScript is disabled (unless CSS also happens to be disabled). |
||
609 | * |
||
610 | * @param string|array $modules Module name (string) or array of module names |
||
611 | */ |
||
612 | public function addModuleStyles( $modules ) { |
||
613 | $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules ); |
||
614 | } |
||
615 | |||
616 | /** |
||
617 | * @return null|string ResourceLoader target |
||
618 | */ |
||
619 | public function getTarget() { |
||
620 | return $this->mTarget; |
||
621 | } |
||
622 | |||
623 | /** |
||
624 | * Sets ResourceLoader target for load.php links. If null, will be omitted |
||
625 | * |
||
626 | * @param string|null $target |
||
627 | */ |
||
628 | public function setTarget( $target ) { |
||
629 | $this->mTarget = $target; |
||
630 | } |
||
631 | |||
632 | /** |
||
633 | * Get an array of head items |
||
634 | * |
||
635 | * @return array |
||
636 | */ |
||
637 | function getHeadItemsArray() { |
||
638 | return $this->mHeadItems; |
||
639 | } |
||
640 | |||
641 | /** |
||
642 | * Add or replace a head item to the output |
||
643 | * |
||
644 | * Whenever possible, use more specific options like ResourceLoader modules, |
||
645 | * OutputPage::addLink(), OutputPage::addMetaLink() and OutputPage::addFeedLink() |
||
646 | * Fallback options for those are: OutputPage::addStyle, OutputPage::addScript(), |
||
647 | * OutputPage::addInlineScript() and OutputPage::addInlineStyle() |
||
648 | * This would be your very LAST fallback. |
||
649 | * |
||
650 | * @param string $name Item name |
||
651 | * @param string $value Raw HTML |
||
652 | */ |
||
653 | public function addHeadItem( $name, $value ) { |
||
654 | $this->mHeadItems[$name] = $value; |
||
655 | } |
||
656 | |||
657 | /** |
||
658 | * Add one or more head items to the output |
||
659 | * |
||
660 | * @since 1.28 |
||
661 | * @param string|string[] $value Raw HTML |
||
0 ignored issues
–
show
There is no parameter named
$value . Did you maybe mean $values ?
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit. Consider the following example. The parameter /**
* @param array $germany
* @param array $ireland
*/
function finale($germany, $island) {
return "2:1";
}
The most likely cause is that the parameter was changed, but the annotation was not. ![]() |
|||
662 | */ |
||
663 | public function addHeadItems( $values ) { |
||
664 | $this->mHeadItems = array_merge( $this->mHeadItems, (array)$values ); |
||
665 | } |
||
666 | |||
667 | /** |
||
668 | * Check if the header item $name is already set |
||
669 | * |
||
670 | * @param string $name Item name |
||
671 | * @return bool |
||
672 | */ |
||
673 | public function hasHeadItem( $name ) { |
||
674 | return isset( $this->mHeadItems[$name] ); |
||
675 | } |
||
676 | |||
677 | /** |
||
678 | * @deprecated since 1.28 Obsolete - wgUseETag experiment was removed. |
||
679 | * @param string $tag |
||
680 | */ |
||
681 | public function setETag( $tag ) { |
||
682 | } |
||
683 | |||
684 | /** |
||
685 | * Set whether the output should only contain the body of the article, |
||
686 | * without any skin, sidebar, etc. |
||
687 | * Used e.g. when calling with "action=render". |
||
688 | * |
||
689 | * @param bool $only Whether to output only the body of the article |
||
690 | */ |
||
691 | public function setArticleBodyOnly( $only ) { |
||
692 | $this->mArticleBodyOnly = $only; |
||
693 | } |
||
694 | |||
695 | /** |
||
696 | * Return whether the output will contain only the body of the article |
||
697 | * |
||
698 | * @return bool |
||
699 | */ |
||
700 | public function getArticleBodyOnly() { |
||
701 | return $this->mArticleBodyOnly; |
||
702 | } |
||
703 | |||
704 | /** |
||
705 | * Set an additional output property |
||
706 | * @since 1.21 |
||
707 | * |
||
708 | * @param string $name |
||
709 | * @param mixed $value |
||
710 | */ |
||
711 | public function setProperty( $name, $value ) { |
||
712 | $this->mProperties[$name] = $value; |
||
713 | } |
||
714 | |||
715 | /** |
||
716 | * Get an additional output property |
||
717 | * @since 1.21 |
||
718 | * |
||
719 | * @param string $name |
||
720 | * @return mixed Property value or null if not found |
||
721 | */ |
||
722 | public function getProperty( $name ) { |
||
723 | if ( isset( $this->mProperties[$name] ) ) { |
||
724 | return $this->mProperties[$name]; |
||
725 | } else { |
||
726 | return null; |
||
727 | } |
||
728 | } |
||
729 | |||
730 | /** |
||
731 | * checkLastModified tells the client to use the client-cached page if |
||
732 | * possible. If successful, the OutputPage is disabled so that |
||
733 | * any future call to OutputPage->output() have no effect. |
||
734 | * |
||
735 | * Side effect: sets mLastModified for Last-Modified header |
||
736 | * |
||
737 | * @param string $timestamp |
||
738 | * |
||
739 | * @return bool True if cache-ok headers was sent. |
||
740 | */ |
||
741 | public function checkLastModified( $timestamp ) { |
||
742 | if ( !$timestamp || $timestamp == '19700101000000' ) { |
||
743 | wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" ); |
||
744 | return false; |
||
745 | } |
||
746 | $config = $this->getConfig(); |
||
747 | if ( !$config->get( 'CachePages' ) ) { |
||
748 | wfDebug( __METHOD__ . ": CACHE DISABLED\n" ); |
||
749 | return false; |
||
750 | } |
||
751 | |||
752 | $timestamp = wfTimestamp( TS_MW, $timestamp ); |
||
753 | $modifiedTimes = [ |
||
754 | 'page' => $timestamp, |
||
755 | 'user' => $this->getUser()->getTouched(), |
||
756 | 'epoch' => $config->get( 'CacheEpoch' ) |
||
757 | ]; |
||
758 | if ( $config->get( 'UseSquid' ) ) { |
||
759 | // bug 44570: the core page itself may not change, but resources might |
||
760 | $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, time() - $config->get( 'SquidMaxage' ) ); |
||
761 | } |
||
762 | Hooks::run( 'OutputPageCheckLastModified', [ &$modifiedTimes, $this ] ); |
||
763 | |||
764 | $maxModified = max( $modifiedTimes ); |
||
765 | $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified ); |
||
0 ignored issues
–
show
It seems like
wfTimestamp(TS_RFC2822, $maxModified) can also be of type false . However, the property $mLastModified is declared as type string . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||
766 | |||
767 | $clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' ); |
||
768 | if ( $clientHeader === false ) { |
||
769 | wfDebug( __METHOD__ . ": client did not send If-Modified-Since header", 'private' ); |
||
770 | return false; |
||
771 | } |
||
772 | |||
773 | # IE sends sizes after the date like this: |
||
774 | # Wed, 20 Aug 2003 06:51:19 GMT; length=5202 |
||
775 | # this breaks strtotime(). |
||
776 | $clientHeader = preg_replace( '/;.*$/', '', $clientHeader ); |
||
777 | |||
778 | MediaWiki\suppressWarnings(); // E_STRICT system time bitching |
||
779 | $clientHeaderTime = strtotime( $clientHeader ); |
||
780 | MediaWiki\restoreWarnings(); |
||
781 | if ( !$clientHeaderTime ) { |
||
782 | wfDebug( __METHOD__ |
||
783 | . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" ); |
||
784 | return false; |
||
785 | } |
||
786 | $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime ); |
||
787 | |||
788 | # Make debug info |
||
789 | $info = ''; |
||
790 | foreach ( $modifiedTimes as $name => $value ) { |
||
791 | if ( $info !== '' ) { |
||
792 | $info .= ', '; |
||
793 | } |
||
794 | $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value ); |
||
795 | } |
||
796 | |||
797 | wfDebug( __METHOD__ . ": client sent If-Modified-Since: " . |
||
798 | wfTimestamp( TS_ISO_8601, $clientHeaderTime ), 'private' ); |
||
799 | wfDebug( __METHOD__ . ": effective Last-Modified: " . |
||
800 | wfTimestamp( TS_ISO_8601, $maxModified ), 'private' ); |
||
801 | if ( $clientHeaderTime < $maxModified ) { |
||
802 | wfDebug( __METHOD__ . ": STALE, $info", 'private' ); |
||
803 | return false; |
||
804 | } |
||
805 | |||
806 | # Not modified |
||
807 | # Give a 304 Not Modified response code and disable body output |
||
808 | wfDebug( __METHOD__ . ": NOT MODIFIED, $info", 'private' ); |
||
809 | ini_set( 'zlib.output_compression', 0 ); |
||
810 | $this->getRequest()->response()->statusHeader( 304 ); |
||
811 | $this->sendCacheControl(); |
||
812 | $this->disable(); |
||
813 | |||
814 | // Don't output a compressed blob when using ob_gzhandler; |
||
815 | // it's technically against HTTP spec and seems to confuse |
||
816 | // Firefox when the response gets split over two packets. |
||
817 | wfClearOutputBuffers(); |
||
818 | |||
819 | return true; |
||
820 | } |
||
821 | |||
822 | /** |
||
823 | * Override the last modified timestamp |
||
824 | * |
||
825 | * @param string $timestamp New timestamp, in a format readable by |
||
826 | * wfTimestamp() |
||
827 | */ |
||
828 | public function setLastModified( $timestamp ) { |
||
829 | $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp ); |
||
0 ignored issues
–
show
It seems like
wfTimestamp(TS_RFC2822, $timestamp) can also be of type false . However, the property $mLastModified is declared as type string . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||
830 | } |
||
831 | |||
832 | /** |
||
833 | * Set the robot policy for the page: <http://www.robotstxt.org/meta.html> |
||
834 | * |
||
835 | * @param string $policy The literal string to output as the contents of |
||
836 | * the meta tag. Will be parsed according to the spec and output in |
||
837 | * standardized form. |
||
838 | * @return null |
||
839 | */ |
||
840 | public function setRobotPolicy( $policy ) { |
||
841 | $policy = Article::formatRobotPolicy( $policy ); |
||
842 | |||
843 | if ( isset( $policy['index'] ) ) { |
||
844 | $this->setIndexPolicy( $policy['index'] ); |
||
845 | } |
||
846 | if ( isset( $policy['follow'] ) ) { |
||
847 | $this->setFollowPolicy( $policy['follow'] ); |
||
848 | } |
||
849 | } |
||
850 | |||
851 | /** |
||
852 | * Set the index policy for the page, but leave the follow policy un- |
||
853 | * touched. |
||
854 | * |
||
855 | * @param string $policy Either 'index' or 'noindex'. |
||
856 | * @return null |
||
857 | */ |
||
858 | public function setIndexPolicy( $policy ) { |
||
859 | $policy = trim( $policy ); |
||
860 | if ( in_array( $policy, [ 'index', 'noindex' ] ) ) { |
||
861 | $this->mIndexPolicy = $policy; |
||
862 | } |
||
863 | } |
||
864 | |||
865 | /** |
||
866 | * Set the follow policy for the page, but leave the index policy un- |
||
867 | * touched. |
||
868 | * |
||
869 | * @param string $policy Either 'follow' or 'nofollow'. |
||
870 | * @return null |
||
871 | */ |
||
872 | public function setFollowPolicy( $policy ) { |
||
873 | $policy = trim( $policy ); |
||
874 | if ( in_array( $policy, [ 'follow', 'nofollow' ] ) ) { |
||
875 | $this->mFollowPolicy = $policy; |
||
876 | } |
||
877 | } |
||
878 | |||
879 | /** |
||
880 | * Set the new value of the "action text", this will be added to the |
||
881 | * "HTML title", separated from it with " - ". |
||
882 | * |
||
883 | * @param string $text New value of the "action text" |
||
884 | */ |
||
885 | public function setPageTitleActionText( $text ) { |
||
886 | $this->mPageTitleActionText = $text; |
||
887 | } |
||
888 | |||
889 | /** |
||
890 | * Get the value of the "action text" |
||
891 | * |
||
892 | * @return string |
||
893 | */ |
||
894 | public function getPageTitleActionText() { |
||
895 | return $this->mPageTitleActionText; |
||
896 | } |
||
897 | |||
898 | /** |
||
899 | * "HTML title" means the contents of "<title>". |
||
900 | * It is stored as plain, unescaped text and will be run through htmlspecialchars in the skin file. |
||
901 | * |
||
902 | * @param string|Message $name |
||
903 | */ |
||
904 | public function setHTMLTitle( $name ) { |
||
905 | if ( $name instanceof Message ) { |
||
906 | $this->mHTMLtitle = $name->setContext( $this->getContext() )->text(); |
||
907 | } else { |
||
908 | $this->mHTMLtitle = $name; |
||
909 | } |
||
910 | } |
||
911 | |||
912 | /** |
||
913 | * Return the "HTML title", i.e. the content of the "<title>" tag. |
||
914 | * |
||
915 | * @return string |
||
916 | */ |
||
917 | public function getHTMLTitle() { |
||
918 | return $this->mHTMLtitle; |
||
919 | } |
||
920 | |||
921 | /** |
||
922 | * Set $mRedirectedFrom, the Title of the page which redirected us to the current page. |
||
923 | * |
||
924 | * @param Title $t |
||
925 | */ |
||
926 | public function setRedirectedFrom( $t ) { |
||
927 | $this->mRedirectedFrom = $t; |
||
928 | } |
||
929 | |||
930 | /** |
||
931 | * "Page title" means the contents of \<h1\>. It is stored as a valid HTML |
||
932 | * fragment. This function allows good tags like \<sup\> in the \<h1\> tag, |
||
933 | * but not bad tags like \<script\>. This function automatically sets |
||
934 | * \<title\> to the same content as \<h1\> but with all tags removed. Bad |
||
935 | * tags that were escaped in \<h1\> will still be escaped in \<title\>, and |
||
936 | * good tags like \<i\> will be dropped entirely. |
||
937 | * |
||
938 | * @param string|Message $name |
||
939 | */ |
||
940 | public function setPageTitle( $name ) { |
||
941 | if ( $name instanceof Message ) { |
||
942 | $name = $name->setContext( $this->getContext() )->text(); |
||
943 | } |
||
944 | |||
945 | # change "<script>foo&bar</script>" to "<script>foo&bar</script>" |
||
946 | # but leave "<i>foobar</i>" alone |
||
947 | $nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $name ) ); |
||
948 | $this->mPagetitle = $nameWithTags; |
||
949 | |||
950 | # change "<i>foo&bar</i>" to "foo&bar" |
||
951 | $this->setHTMLTitle( |
||
952 | $this->msg( 'pagetitle' )->rawParams( Sanitizer::stripAllTags( $nameWithTags ) ) |
||
953 | ->inContentLanguage() |
||
954 | ); |
||
955 | } |
||
956 | |||
957 | /** |
||
958 | * Return the "page title", i.e. the content of the \<h1\> tag. |
||
959 | * |
||
960 | * @return string |
||
961 | */ |
||
962 | public function getPageTitle() { |
||
963 | return $this->mPagetitle; |
||
964 | } |
||
965 | |||
966 | /** |
||
967 | * Set the Title object to use |
||
968 | * |
||
969 | * @param Title $t |
||
970 | */ |
||
971 | public function setTitle( Title $t ) { |
||
972 | $this->getContext()->setTitle( $t ); |
||
0 ignored issues
–
show
It seems like you code against a concrete implementation and not the interface
IContextSource as the method setTitle() does only exist in the following implementations of said interface: DerivativeContext , EditWatchlistNormalHTMLForm , HTMLForm , OOUIHTMLForm , OutputPage , PreferencesForm , RequestContext , UploadForm , VFormHTMLForm .
Let’s take a look at an example: interface User
{
/** @return string */
public function getPassword();
}
class MyUser implements User
{
public function getPassword()
{
// return something
}
public function getDisplayName()
{
// return some name.
}
}
class AuthSystem
{
public function authenticate(User $user)
{
$this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
// do something.
}
}
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break. Available Fixes
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types
inside the if block in such a case.
![]() |
|||
973 | } |
||
974 | |||
975 | /** |
||
976 | * Replace the subtitle with $str |
||
977 | * |
||
978 | * @param string|Message $str New value of the subtitle. String should be safe HTML. |
||
979 | */ |
||
980 | public function setSubtitle( $str ) { |
||
981 | $this->clearSubtitle(); |
||
982 | $this->addSubtitle( $str ); |
||
983 | } |
||
984 | |||
985 | /** |
||
986 | * Add $str to the subtitle |
||
987 | * |
||
988 | * @param string|Message $str String or Message to add to the subtitle. String should be safe HTML. |
||
989 | */ |
||
990 | public function addSubtitle( $str ) { |
||
991 | if ( $str instanceof Message ) { |
||
992 | $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse(); |
||
993 | } else { |
||
994 | $this->mSubtitle[] = $str; |
||
995 | } |
||
996 | } |
||
997 | |||
998 | /** |
||
999 | * Build message object for a subtitle containing a backlink to a page |
||
1000 | * |
||
1001 | * @param Title $title Title to link to |
||
1002 | * @param array $query Array of additional parameters to include in the link |
||
1003 | * @return Message |
||
1004 | * @since 1.25 |
||
1005 | */ |
||
1006 | public static function buildBacklinkSubtitle( Title $title, $query = [] ) { |
||
1007 | if ( $title->isRedirect() ) { |
||
1008 | $query['redirect'] = 'no'; |
||
1009 | } |
||
1010 | return wfMessage( 'backlinksubtitle' ) |
||
1011 | ->rawParams( Linker::link( $title, null, [], $query ) ); |
||
1012 | } |
||
1013 | |||
1014 | /** |
||
1015 | * Add a subtitle containing a backlink to a page |
||
1016 | * |
||
1017 | * @param Title $title Title to link to |
||
1018 | * @param array $query Array of additional parameters to include in the link |
||
1019 | */ |
||
1020 | public function addBacklinkSubtitle( Title $title, $query = [] ) { |
||
1021 | $this->addSubtitle( self::buildBacklinkSubtitle( $title, $query ) ); |
||
1022 | } |
||
1023 | |||
1024 | /** |
||
1025 | * Clear the subtitles |
||
1026 | */ |
||
1027 | public function clearSubtitle() { |
||
1028 | $this->mSubtitle = []; |
||
1029 | } |
||
1030 | |||
1031 | /** |
||
1032 | * Get the subtitle |
||
1033 | * |
||
1034 | * @return string |
||
1035 | */ |
||
1036 | public function getSubtitle() { |
||
1037 | return implode( "<br />\n\t\t\t\t", $this->mSubtitle ); |
||
1038 | } |
||
1039 | |||
1040 | /** |
||
1041 | * Set the page as printable, i.e. it'll be displayed with all |
||
1042 | * print styles included |
||
1043 | */ |
||
1044 | public function setPrintable() { |
||
1045 | $this->mPrintable = true; |
||
1046 | } |
||
1047 | |||
1048 | /** |
||
1049 | * Return whether the page is "printable" |
||
1050 | * |
||
1051 | * @return bool |
||
1052 | */ |
||
1053 | public function isPrintable() { |
||
1054 | return $this->mPrintable; |
||
1055 | } |
||
1056 | |||
1057 | /** |
||
1058 | * Disable output completely, i.e. calling output() will have no effect |
||
1059 | */ |
||
1060 | public function disable() { |
||
1061 | $this->mDoNothing = true; |
||
1062 | } |
||
1063 | |||
1064 | /** |
||
1065 | * Return whether the output will be completely disabled |
||
1066 | * |
||
1067 | * @return bool |
||
1068 | */ |
||
1069 | public function isDisabled() { |
||
1070 | return $this->mDoNothing; |
||
1071 | } |
||
1072 | |||
1073 | /** |
||
1074 | * Show an "add new section" link? |
||
1075 | * |
||
1076 | * @return bool |
||
1077 | */ |
||
1078 | public function showNewSectionLink() { |
||
1079 | return $this->mNewSectionLink; |
||
1080 | } |
||
1081 | |||
1082 | /** |
||
1083 | * Forcibly hide the new section link? |
||
1084 | * |
||
1085 | * @return bool |
||
1086 | */ |
||
1087 | public function forceHideNewSectionLink() { |
||
1088 | return $this->mHideNewSectionLink; |
||
1089 | } |
||
1090 | |||
1091 | /** |
||
1092 | * Add or remove feed links in the page header |
||
1093 | * This is mainly kept for backward compatibility, see OutputPage::addFeedLink() |
||
1094 | * for the new version |
||
1095 | * @see addFeedLink() |
||
1096 | * |
||
1097 | * @param bool $show True: add default feeds, false: remove all feeds |
||
1098 | */ |
||
1099 | public function setSyndicated( $show = true ) { |
||
1100 | if ( $show ) { |
||
1101 | $this->setFeedAppendQuery( false ); |
||
1102 | } else { |
||
1103 | $this->mFeedLinks = []; |
||
1104 | } |
||
1105 | } |
||
1106 | |||
1107 | /** |
||
1108 | * Add default feeds to the page header |
||
1109 | * This is mainly kept for backward compatibility, see OutputPage::addFeedLink() |
||
1110 | * for the new version |
||
1111 | * @see addFeedLink() |
||
1112 | * |
||
1113 | * @param string $val Query to append to feed links or false to output |
||
1114 | * default links |
||
1115 | */ |
||
1116 | public function setFeedAppendQuery( $val ) { |
||
1117 | $this->mFeedLinks = []; |
||
1118 | |||
1119 | foreach ( $this->getConfig()->get( 'AdvertisedFeedTypes' ) as $type ) { |
||
1120 | $query = "feed=$type"; |
||
1121 | if ( is_string( $val ) ) { |
||
1122 | $query .= '&' . $val; |
||
1123 | } |
||
1124 | $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query ); |
||
1125 | } |
||
1126 | } |
||
1127 | |||
1128 | /** |
||
1129 | * Add a feed link to the page header |
||
1130 | * |
||
1131 | * @param string $format Feed type, should be a key of $wgFeedClasses |
||
1132 | * @param string $href URL |
||
1133 | */ |
||
1134 | public function addFeedLink( $format, $href ) { |
||
1135 | if ( in_array( $format, $this->getConfig()->get( 'AdvertisedFeedTypes' ) ) ) { |
||
1136 | $this->mFeedLinks[$format] = $href; |
||
1137 | } |
||
1138 | } |
||
1139 | |||
1140 | /** |
||
1141 | * Should we output feed links for this page? |
||
1142 | * @return bool |
||
1143 | */ |
||
1144 | public function isSyndicated() { |
||
1145 | return count( $this->mFeedLinks ) > 0; |
||
1146 | } |
||
1147 | |||
1148 | /** |
||
1149 | * Return URLs for each supported syndication format for this page. |
||
1150 | * @return array Associating format keys with URLs |
||
1151 | */ |
||
1152 | public function getSyndicationLinks() { |
||
1153 | return $this->mFeedLinks; |
||
1154 | } |
||
1155 | |||
1156 | /** |
||
1157 | * Will currently always return null |
||
1158 | * |
||
1159 | * @return null |
||
1160 | */ |
||
1161 | public function getFeedAppendQuery() { |
||
1162 | return $this->mFeedLinksAppendQuery; |
||
1163 | } |
||
1164 | |||
1165 | /** |
||
1166 | * Set whether the displayed content is related to the source of the |
||
1167 | * corresponding article on the wiki |
||
1168 | * Setting true will cause the change "article related" toggle to true |
||
1169 | * |
||
1170 | * @param bool $v |
||
1171 | */ |
||
1172 | public function setArticleFlag( $v ) { |
||
1173 | $this->mIsarticle = $v; |
||
1174 | if ( $v ) { |
||
1175 | $this->mIsArticleRelated = $v; |
||
1176 | } |
||
1177 | } |
||
1178 | |||
1179 | /** |
||
1180 | * Return whether the content displayed page is related to the source of |
||
1181 | * the corresponding article on the wiki |
||
1182 | * |
||
1183 | * @return bool |
||
1184 | */ |
||
1185 | public function isArticle() { |
||
1186 | return $this->mIsarticle; |
||
1187 | } |
||
1188 | |||
1189 | /** |
||
1190 | * Set whether this page is related an article on the wiki |
||
1191 | * Setting false will cause the change of "article flag" toggle to false |
||
1192 | * |
||
1193 | * @param bool $v |
||
1194 | */ |
||
1195 | public function setArticleRelated( $v ) { |
||
1196 | $this->mIsArticleRelated = $v; |
||
1197 | if ( !$v ) { |
||
1198 | $this->mIsarticle = false; |
||
1199 | } |
||
1200 | } |
||
1201 | |||
1202 | /** |
||
1203 | * Return whether this page is related an article on the wiki |
||
1204 | * |
||
1205 | * @return bool |
||
1206 | */ |
||
1207 | public function isArticleRelated() { |
||
1208 | return $this->mIsArticleRelated; |
||
1209 | } |
||
1210 | |||
1211 | /** |
||
1212 | * Add new language links |
||
1213 | * |
||
1214 | * @param string[] $newLinkArray Array of interwiki-prefixed (non DB key) titles |
||
1215 | * (e.g. 'fr:Test page') |
||
1216 | */ |
||
1217 | public function addLanguageLinks( array $newLinkArray ) { |
||
1218 | $this->mLanguageLinks += $newLinkArray; |
||
1219 | } |
||
1220 | |||
1221 | /** |
||
1222 | * Reset the language links and add new language links |
||
1223 | * |
||
1224 | * @param string[] $newLinkArray Array of interwiki-prefixed (non DB key) titles |
||
1225 | * (e.g. 'fr:Test page') |
||
1226 | */ |
||
1227 | public function setLanguageLinks( array $newLinkArray ) { |
||
1228 | $this->mLanguageLinks = $newLinkArray; |
||
1229 | } |
||
1230 | |||
1231 | /** |
||
1232 | * Get the list of language links |
||
1233 | * |
||
1234 | * @return string[] Array of interwiki-prefixed (non DB key) titles (e.g. 'fr:Test page') |
||
1235 | */ |
||
1236 | public function getLanguageLinks() { |
||
1237 | return $this->mLanguageLinks; |
||
1238 | } |
||
1239 | |||
1240 | /** |
||
1241 | * Add an array of categories, with names in the keys |
||
1242 | * |
||
1243 | * @param array $categories Mapping category name => sort key |
||
1244 | */ |
||
1245 | public function addCategoryLinks( array $categories ) { |
||
1246 | global $wgContLang; |
||
1247 | |||
1248 | if ( !is_array( $categories ) || count( $categories ) == 0 ) { |
||
1249 | return; |
||
1250 | } |
||
1251 | |||
1252 | # Add the links to a LinkBatch |
||
1253 | $arr = [ NS_CATEGORY => $categories ]; |
||
1254 | $lb = new LinkBatch; |
||
1255 | $lb->setArray( $arr ); |
||
1256 | |||
1257 | # Fetch existence plus the hiddencat property |
||
1258 | $dbr = wfGetDB( DB_REPLICA ); |
||
1259 | $fields = array_merge( |
||
1260 | LinkCache::getSelectFields(), |
||
1261 | [ 'page_namespace', 'page_title', 'pp_value' ] |
||
1262 | ); |
||
1263 | |||
1264 | $res = $dbr->select( [ 'page', 'page_props' ], |
||
1265 | $fields, |
||
1266 | $lb->constructSet( 'page', $dbr ), |
||
0 ignored issues
–
show
It seems like
$dbr defined by wfGetDB(DB_REPLICA) on line 1258 can be null ; however, LinkBatch::constructSet() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() It seems like
$lb->constructSet('page', $dbr) targeting LinkBatch::constructSet() can also be of type boolean ; however, Database::select() does only seem to accept string , maybe add an additional type check?
This check looks at variables that are passed out again to other methods. If the outgoing method call has stricter type requirements than the method itself, an issue is raised. An additional type check may prevent trouble. ![]() |
|||
1267 | __METHOD__, |
||
1268 | [], |
||
1269 | [ 'page_props' => [ 'LEFT JOIN', [ |
||
1270 | 'pp_propname' => 'hiddencat', |
||
1271 | 'pp_page = page_id' |
||
1272 | ] ] ] |
||
1273 | ); |
||
1274 | |||
1275 | # Add the results to the link cache |
||
1276 | $lb->addResultToCache( LinkCache::singleton(), $res ); |
||
1277 | |||
1278 | # Set all the values to 'normal'. |
||
1279 | $categories = array_fill_keys( array_keys( $categories ), 'normal' ); |
||
1280 | |||
1281 | # Mark hidden categories |
||
1282 | foreach ( $res as $row ) { |
||
1283 | if ( isset( $row->pp_value ) ) { |
||
1284 | $categories[$row->page_title] = 'hidden'; |
||
1285 | } |
||
1286 | } |
||
1287 | |||
1288 | # Add the remaining categories to the skin |
||
1289 | if ( Hooks::run( |
||
1290 | 'OutputPageMakeCategoryLinks', |
||
1291 | [ &$this, $categories, &$this->mCategoryLinks ] ) |
||
1292 | ) { |
||
1293 | foreach ( $categories as $category => $type ) { |
||
1294 | // array keys will cast numeric category names to ints, so cast back to string |
||
1295 | $category = (string)$category; |
||
1296 | $origcategory = $category; |
||
1297 | $title = Title::makeTitleSafe( NS_CATEGORY, $category ); |
||
1298 | if ( !$title ) { |
||
1299 | continue; |
||
1300 | } |
||
1301 | $wgContLang->findVariantLink( $category, $title, true ); |
||
1302 | if ( $category != $origcategory && array_key_exists( $category, $categories ) ) { |
||
1303 | continue; |
||
1304 | } |
||
1305 | $text = $wgContLang->convertHtml( $title->getText() ); |
||
1306 | $this->mCategories[] = $title->getText(); |
||
1307 | $this->mCategoryLinks[$type][] = Linker::link( $title, $text ); |
||
1308 | } |
||
1309 | } |
||
1310 | } |
||
1311 | |||
1312 | /** |
||
1313 | * Reset the category links (but not the category list) and add $categories |
||
1314 | * |
||
1315 | * @param array $categories Mapping category name => sort key |
||
1316 | */ |
||
1317 | public function setCategoryLinks( array $categories ) { |
||
1318 | $this->mCategoryLinks = []; |
||
1319 | $this->addCategoryLinks( $categories ); |
||
1320 | } |
||
1321 | |||
1322 | /** |
||
1323 | * Get the list of category links, in a 2-D array with the following format: |
||
1324 | * $arr[$type][] = $link, where $type is either "normal" or "hidden" (for |
||
1325 | * hidden categories) and $link a HTML fragment with a link to the category |
||
1326 | * page |
||
1327 | * |
||
1328 | * @return array |
||
1329 | */ |
||
1330 | public function getCategoryLinks() { |
||
1331 | return $this->mCategoryLinks; |
||
1332 | } |
||
1333 | |||
1334 | /** |
||
1335 | * Get the list of category names this page belongs to |
||
1336 | * |
||
1337 | * @return array Array of strings |
||
1338 | */ |
||
1339 | public function getCategories() { |
||
1340 | return $this->mCategories; |
||
1341 | } |
||
1342 | |||
1343 | /** |
||
1344 | * Add an array of indicators, with their identifiers as array |
||
1345 | * keys and HTML contents as values. |
||
1346 | * |
||
1347 | * In case of duplicate keys, existing values are overwritten. |
||
1348 | * |
||
1349 | * @param array $indicators |
||
1350 | * @since 1.25 |
||
1351 | */ |
||
1352 | public function setIndicators( array $indicators ) { |
||
1353 | $this->mIndicators = $indicators + $this->mIndicators; |
||
1354 | // Keep ordered by key |
||
1355 | ksort( $this->mIndicators ); |
||
1356 | } |
||
1357 | |||
1358 | /** |
||
1359 | * Get the indicators associated with this page. |
||
1360 | * |
||
1361 | * The array will be internally ordered by item keys. |
||
1362 | * |
||
1363 | * @return array Keys: identifiers, values: HTML contents |
||
1364 | * @since 1.25 |
||
1365 | */ |
||
1366 | public function getIndicators() { |
||
1367 | return $this->mIndicators; |
||
1368 | } |
||
1369 | |||
1370 | /** |
||
1371 | * Adds help link with an icon via page indicators. |
||
1372 | * Link target can be overridden by a local message containing a wikilink: |
||
1373 | * the message key is: lowercase action or special page name + '-helppage'. |
||
1374 | * @param string $to Target MediaWiki.org page title or encoded URL. |
||
1375 | * @param bool $overrideBaseUrl Whether $url is a full URL, to avoid MW.o. |
||
1376 | * @since 1.25 |
||
1377 | */ |
||
1378 | public function addHelpLink( $to, $overrideBaseUrl = false ) { |
||
1379 | $this->addModuleStyles( 'mediawiki.helplink' ); |
||
1380 | $text = $this->msg( 'helppage-top-gethelp' )->escaped(); |
||
1381 | |||
1382 | if ( $overrideBaseUrl ) { |
||
1383 | $helpUrl = $to; |
||
1384 | } else { |
||
1385 | $toUrlencoded = wfUrlencode( str_replace( ' ', '_', $to ) ); |
||
1386 | $helpUrl = "//www.mediawiki.org/wiki/Special:MyLanguage/$toUrlencoded"; |
||
1387 | } |
||
1388 | |||
1389 | $link = Html::rawElement( |
||
1390 | 'a', |
||
1391 | [ |
||
1392 | 'href' => $helpUrl, |
||
1393 | 'target' => '_blank', |
||
1394 | 'class' => 'mw-helplink', |
||
1395 | ], |
||
1396 | $text |
||
1397 | ); |
||
1398 | |||
1399 | $this->setIndicators( [ 'mw-helplink' => $link ] ); |
||
1400 | } |
||
1401 | |||
1402 | /** |
||
1403 | * Do not allow scripts which can be modified by wiki users to load on this page; |
||
1404 | * only allow scripts bundled with, or generated by, the software. |
||
1405 | * Site-wide styles are controlled by a config setting, since they can be |
||
1406 | * used to create a custom skin/theme, but not user-specific ones. |
||
1407 | * |
||
1408 | * @todo this should be given a more accurate name |
||
1409 | */ |
||
1410 | public function disallowUserJs() { |
||
1411 | $this->reduceAllowedModules( |
||
1412 | ResourceLoaderModule::TYPE_SCRIPTS, |
||
1413 | ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL |
||
1414 | ); |
||
1415 | |||
1416 | // Site-wide styles are controlled by a config setting, see bug 71621 |
||
1417 | // for background on why. User styles are never allowed. |
||
1418 | if ( $this->getConfig()->get( 'AllowSiteCSSOnRestrictedPages' ) ) { |
||
1419 | $styleOrigin = ResourceLoaderModule::ORIGIN_USER_SITEWIDE; |
||
1420 | } else { |
||
1421 | $styleOrigin = ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL; |
||
1422 | } |
||
1423 | $this->reduceAllowedModules( |
||
1424 | ResourceLoaderModule::TYPE_STYLES, |
||
1425 | $styleOrigin |
||
1426 | ); |
||
1427 | } |
||
1428 | |||
1429 | /** |
||
1430 | * Show what level of JavaScript / CSS untrustworthiness is allowed on this page |
||
1431 | * @see ResourceLoaderModule::$origin |
||
1432 | * @param string $type ResourceLoaderModule TYPE_ constant |
||
1433 | * @return int ResourceLoaderModule ORIGIN_ class constant |
||
1434 | */ |
||
1435 | public function getAllowedModules( $type ) { |
||
1436 | if ( $type == ResourceLoaderModule::TYPE_COMBINED ) { |
||
1437 | return min( array_values( $this->mAllowedModules ) ); |
||
1438 | } else { |
||
1439 | return isset( $this->mAllowedModules[$type] ) |
||
1440 | ? $this->mAllowedModules[$type] |
||
1441 | : ResourceLoaderModule::ORIGIN_ALL; |
||
1442 | } |
||
1443 | } |
||
1444 | |||
1445 | /** |
||
1446 | * Limit the highest level of CSS/JS untrustworthiness allowed. |
||
1447 | * |
||
1448 | * If passed the same or a higher level than the current level of untrustworthiness set, the |
||
1449 | * level will remain unchanged. |
||
1450 | * |
||
1451 | * @param string $type |
||
1452 | * @param int $level ResourceLoaderModule class constant |
||
1453 | */ |
||
1454 | public function reduceAllowedModules( $type, $level ) { |
||
1455 | $this->mAllowedModules[$type] = min( $this->getAllowedModules( $type ), $level ); |
||
1456 | } |
||
1457 | |||
1458 | /** |
||
1459 | * Prepend $text to the body HTML |
||
1460 | * |
||
1461 | * @param string $text HTML |
||
1462 | */ |
||
1463 | public function prependHTML( $text ) { |
||
1464 | $this->mBodytext = $text . $this->mBodytext; |
||
1465 | } |
||
1466 | |||
1467 | /** |
||
1468 | * Append $text to the body HTML |
||
1469 | * |
||
1470 | * @param string $text HTML |
||
1471 | */ |
||
1472 | public function addHTML( $text ) { |
||
1473 | $this->mBodytext .= $text; |
||
1474 | } |
||
1475 | |||
1476 | /** |
||
1477 | * Shortcut for adding an Html::element via addHTML. |
||
1478 | * |
||
1479 | * @since 1.19 |
||
1480 | * |
||
1481 | * @param string $element |
||
1482 | * @param array $attribs |
||
1483 | * @param string $contents |
||
1484 | */ |
||
1485 | public function addElement( $element, array $attribs = [], $contents = '' ) { |
||
1486 | $this->addHTML( Html::element( $element, $attribs, $contents ) ); |
||
1487 | } |
||
1488 | |||
1489 | /** |
||
1490 | * Clear the body HTML |
||
1491 | */ |
||
1492 | public function clearHTML() { |
||
1493 | $this->mBodytext = ''; |
||
1494 | } |
||
1495 | |||
1496 | /** |
||
1497 | * Get the body HTML |
||
1498 | * |
||
1499 | * @return string HTML |
||
1500 | */ |
||
1501 | public function getHTML() { |
||
1502 | return $this->mBodytext; |
||
1503 | } |
||
1504 | |||
1505 | /** |
||
1506 | * Get/set the ParserOptions object to use for wikitext parsing |
||
1507 | * |
||
1508 | * @param ParserOptions|null $options Either the ParserOption to use or null to only get the |
||
1509 | * current ParserOption object |
||
1510 | * @return ParserOptions |
||
1511 | */ |
||
1512 | public function parserOptions( $options = null ) { |
||
1513 | if ( $options !== null && !empty( $options->isBogus ) ) { |
||
1514 | // Someone is trying to set a bogus pre-$wgUser PO. Check if it has |
||
1515 | // been changed somehow, and keep it if so. |
||
1516 | $anonPO = ParserOptions::newFromAnon(); |
||
1517 | $anonPO->setEditSection( false ); |
||
1518 | if ( !$options->matches( $anonPO ) ) { |
||
1519 | wfLogWarning( __METHOD__ . ': Setting a changed bogus ParserOptions: ' . wfGetAllCallers( 5 ) ); |
||
1520 | $options->isBogus = false; |
||
0 ignored issues
–
show
The property
isBogus does not seem to exist in ParserOptions .
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name. If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading. ![]() |
|||
1521 | } |
||
1522 | } |
||
1523 | |||
1524 | if ( !$this->mParserOptions ) { |
||
1525 | if ( !$this->getContext()->getUser()->isSafeToLoad() ) { |
||
1526 | // $wgUser isn't unstubbable yet, so don't try to get a |
||
1527 | // ParserOptions for it. And don't cache this ParserOptions |
||
1528 | // either. |
||
1529 | $po = ParserOptions::newFromAnon(); |
||
1530 | $po->setEditSection( false ); |
||
1531 | $po->isBogus = true; |
||
1532 | if ( $options !== null ) { |
||
1533 | $this->mParserOptions = empty( $options->isBogus ) ? $options : null; |
||
1534 | } |
||
1535 | return $po; |
||
1536 | } |
||
1537 | |||
1538 | $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() ); |
||
1539 | $this->mParserOptions->setEditSection( false ); |
||
1540 | } |
||
1541 | |||
1542 | if ( $options !== null && !empty( $options->isBogus ) ) { |
||
1543 | // They're trying to restore the bogus pre-$wgUser PO. Do the right |
||
1544 | // thing. |
||
1545 | return wfSetVar( $this->mParserOptions, null, true ); |
||
1546 | } else { |
||
1547 | return wfSetVar( $this->mParserOptions, $options ); |
||
1548 | } |
||
1549 | } |
||
1550 | |||
1551 | /** |
||
1552 | * Set the revision ID which will be seen by the wiki text parser |
||
1553 | * for things such as embedded {{REVISIONID}} variable use. |
||
1554 | * |
||
1555 | * @param int|null $revid An positive integer, or null |
||
1556 | * @return mixed Previous value |
||
1557 | */ |
||
1558 | public function setRevisionId( $revid ) { |
||
1559 | $val = is_null( $revid ) ? null : intval( $revid ); |
||
1560 | return wfSetVar( $this->mRevisionId, $val ); |
||
1561 | } |
||
1562 | |||
1563 | /** |
||
1564 | * Get the displayed revision ID |
||
1565 | * |
||
1566 | * @return int |
||
1567 | */ |
||
1568 | public function getRevisionId() { |
||
1569 | return $this->mRevisionId; |
||
1570 | } |
||
1571 | |||
1572 | /** |
||
1573 | * Set the timestamp of the revision which will be displayed. This is used |
||
1574 | * to avoid a extra DB call in Skin::lastModified(). |
||
1575 | * |
||
1576 | * @param string|null $timestamp |
||
1577 | * @return mixed Previous value |
||
1578 | */ |
||
1579 | public function setRevisionTimestamp( $timestamp ) { |
||
1580 | return wfSetVar( $this->mRevisionTimestamp, $timestamp ); |
||
1581 | } |
||
1582 | |||
1583 | /** |
||
1584 | * Get the timestamp of displayed revision. |
||
1585 | * This will be null if not filled by setRevisionTimestamp(). |
||
1586 | * |
||
1587 | * @return string|null |
||
1588 | */ |
||
1589 | public function getRevisionTimestamp() { |
||
1590 | return $this->mRevisionTimestamp; |
||
1591 | } |
||
1592 | |||
1593 | /** |
||
1594 | * Set the displayed file version |
||
1595 | * |
||
1596 | * @param File|bool $file |
||
1597 | * @return mixed Previous value |
||
1598 | */ |
||
1599 | public function setFileVersion( $file ) { |
||
1600 | $val = null; |
||
1601 | if ( $file instanceof File && $file->exists() ) { |
||
1602 | $val = [ 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() ]; |
||
1603 | } |
||
1604 | return wfSetVar( $this->mFileVersion, $val, true ); |
||
1605 | } |
||
1606 | |||
1607 | /** |
||
1608 | * Get the displayed file version |
||
1609 | * |
||
1610 | * @return array|null ('time' => MW timestamp, 'sha1' => sha1) |
||
1611 | */ |
||
1612 | public function getFileVersion() { |
||
1613 | return $this->mFileVersion; |
||
1614 | } |
||
1615 | |||
1616 | /** |
||
1617 | * Get the templates used on this page |
||
1618 | * |
||
1619 | * @return array (namespace => dbKey => revId) |
||
1620 | * @since 1.18 |
||
1621 | */ |
||
1622 | public function getTemplateIds() { |
||
1623 | return $this->mTemplateIds; |
||
1624 | } |
||
1625 | |||
1626 | /** |
||
1627 | * Get the files used on this page |
||
1628 | * |
||
1629 | * @return array (dbKey => array('time' => MW timestamp or null, 'sha1' => sha1 or '')) |
||
1630 | * @since 1.18 |
||
1631 | */ |
||
1632 | public function getFileSearchOptions() { |
||
1633 | return $this->mImageTimeKeys; |
||
1634 | } |
||
1635 | |||
1636 | /** |
||
1637 | * Convert wikitext to HTML and add it to the buffer |
||
1638 | * Default assumes that the current page title will be used. |
||
1639 | * |
||
1640 | * @param string $text |
||
1641 | * @param bool $linestart Is this the start of a line? |
||
1642 | * @param bool $interface Is this text in the user interface language? |
||
1643 | * @throws MWException |
||
1644 | */ |
||
1645 | public function addWikiText( $text, $linestart = true, $interface = true ) { |
||
1646 | $title = $this->getTitle(); // Work around E_STRICT |
||
1647 | if ( !$title ) { |
||
1648 | throw new MWException( 'Title is null' ); |
||
1649 | } |
||
1650 | $this->addWikiTextTitle( $text, $title, $linestart, /*tidy*/false, $interface ); |
||
1651 | } |
||
1652 | |||
1653 | /** |
||
1654 | * Add wikitext with a custom Title object |
||
1655 | * |
||
1656 | * @param string $text Wikitext |
||
1657 | * @param Title $title |
||
1658 | * @param bool $linestart Is this the start of a line? |
||
1659 | */ |
||
1660 | public function addWikiTextWithTitle( $text, &$title, $linestart = true ) { |
||
1661 | $this->addWikiTextTitle( $text, $title, $linestart ); |
||
1662 | } |
||
1663 | |||
1664 | /** |
||
1665 | * Add wikitext with a custom Title object and tidy enabled. |
||
1666 | * |
||
1667 | * @param string $text Wikitext |
||
1668 | * @param Title $title |
||
1669 | * @param bool $linestart Is this the start of a line? |
||
1670 | */ |
||
1671 | function addWikiTextTitleTidy( $text, &$title, $linestart = true ) { |
||
1672 | $this->addWikiTextTitle( $text, $title, $linestart, true ); |
||
1673 | } |
||
1674 | |||
1675 | /** |
||
1676 | * Add wikitext with tidy enabled |
||
1677 | * |
||
1678 | * @param string $text Wikitext |
||
1679 | * @param bool $linestart Is this the start of a line? |
||
1680 | */ |
||
1681 | public function addWikiTextTidy( $text, $linestart = true ) { |
||
1682 | $title = $this->getTitle(); |
||
1683 | $this->addWikiTextTitleTidy( $text, $title, $linestart ); |
||
0 ignored issues
–
show
It seems like
$title defined by $this->getTitle() on line 1682 can be null ; however, OutputPage::addWikiTextTitleTidy() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
1684 | } |
||
1685 | |||
1686 | /** |
||
1687 | * Add wikitext with a custom Title object |
||
1688 | * |
||
1689 | * @param string $text Wikitext |
||
1690 | * @param Title $title |
||
1691 | * @param bool $linestart Is this the start of a line? |
||
1692 | * @param bool $tidy Whether to use tidy |
||
1693 | * @param bool $interface Whether it is an interface message |
||
1694 | * (for example disables conversion) |
||
1695 | */ |
||
1696 | public function addWikiTextTitle( $text, Title $title, $linestart, |
||
1697 | $tidy = false, $interface = false |
||
1698 | ) { |
||
1699 | global $wgParser; |
||
1700 | |||
1701 | $popts = $this->parserOptions(); |
||
1702 | $oldTidy = $popts->setTidy( $tidy ); |
||
1703 | $popts->setInterfaceMessage( (bool)$interface ); |
||
1704 | |||
1705 | $parserOutput = $wgParser->getFreshParser()->parse( |
||
1706 | $text, $title, $popts, |
||
1707 | $linestart, true, $this->mRevisionId |
||
1708 | ); |
||
1709 | |||
1710 | $popts->setTidy( $oldTidy ); |
||
1711 | |||
1712 | $this->addParserOutput( $parserOutput ); |
||
1713 | } |
||
1714 | |||
1715 | /** |
||
1716 | * Add a ParserOutput object, but without Html. |
||
1717 | * |
||
1718 | * @deprecated since 1.24, use addParserOutputMetadata() instead. |
||
1719 | * @param ParserOutput $parserOutput |
||
1720 | */ |
||
1721 | public function addParserOutputNoText( $parserOutput ) { |
||
1722 | wfDeprecated( __METHOD__, '1.24' ); |
||
1723 | $this->addParserOutputMetadata( $parserOutput ); |
||
1724 | } |
||
1725 | |||
1726 | /** |
||
1727 | * Add all metadata associated with a ParserOutput object, but without the actual HTML. This |
||
1728 | * includes categories, language links, ResourceLoader modules, effects of certain magic words, |
||
1729 | * and so on. |
||
1730 | * |
||
1731 | * @since 1.24 |
||
1732 | * @param ParserOutput $parserOutput |
||
1733 | */ |
||
1734 | public function addParserOutputMetadata( $parserOutput ) { |
||
1735 | $this->mLanguageLinks += $parserOutput->getLanguageLinks(); |
||
1736 | $this->addCategoryLinks( $parserOutput->getCategories() ); |
||
1737 | $this->setIndicators( $parserOutput->getIndicators() ); |
||
1738 | $this->mNewSectionLink = $parserOutput->getNewSection(); |
||
1739 | $this->mHideNewSectionLink = $parserOutput->getHideNewSection(); |
||
1740 | |||
1741 | if ( !$parserOutput->isCacheable() ) { |
||
1742 | $this->enableClientCache( false ); |
||
1743 | } |
||
1744 | $this->mNoGallery = $parserOutput->getNoGallery(); |
||
1745 | $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() ); |
||
1746 | $this->addModules( $parserOutput->getModules() ); |
||
1747 | $this->addModuleScripts( $parserOutput->getModuleScripts() ); |
||
1748 | $this->addModuleStyles( $parserOutput->getModuleStyles() ); |
||
1749 | $this->addJsConfigVars( $parserOutput->getJsConfigVars() ); |
||
1750 | $this->mPreventClickjacking = $this->mPreventClickjacking |
||
1751 | || $parserOutput->preventClickjacking(); |
||
1752 | |||
1753 | // Template versioning... |
||
1754 | foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) { |
||
1755 | if ( isset( $this->mTemplateIds[$ns] ) ) { |
||
1756 | $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns]; |
||
1757 | } else { |
||
1758 | $this->mTemplateIds[$ns] = $dbks; |
||
1759 | } |
||
1760 | } |
||
1761 | // File versioning... |
||
1762 | foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) { |
||
1763 | $this->mImageTimeKeys[$dbk] = $data; |
||
1764 | } |
||
1765 | |||
1766 | // Hooks registered in the object |
||
1767 | $parserOutputHooks = $this->getConfig()->get( 'ParserOutputHooks' ); |
||
1768 | foreach ( $parserOutput->getOutputHooks() as $hookInfo ) { |
||
1769 | list( $hookName, $data ) = $hookInfo; |
||
1770 | if ( isset( $parserOutputHooks[$hookName] ) ) { |
||
1771 | call_user_func( $parserOutputHooks[$hookName], $this, $parserOutput, $data ); |
||
1772 | } |
||
1773 | } |
||
1774 | |||
1775 | // enable OOUI if requested via ParserOutput |
||
1776 | if ( $parserOutput->getEnableOOUI() ) { |
||
1777 | $this->enableOOUI(); |
||
1778 | } |
||
1779 | |||
1780 | // Link flags are ignored for now, but may in the future be |
||
1781 | // used to mark individual language links. |
||
1782 | $linkFlags = []; |
||
1783 | Hooks::run( 'LanguageLinks', [ $this->getTitle(), &$this->mLanguageLinks, &$linkFlags ] ); |
||
1784 | Hooks::run( 'OutputPageParserOutput', [ &$this, $parserOutput ] ); |
||
1785 | } |
||
1786 | |||
1787 | /** |
||
1788 | * Add the HTML and enhancements for it (like ResourceLoader modules) associated with a |
||
1789 | * ParserOutput object, without any other metadata. |
||
1790 | * |
||
1791 | * @since 1.24 |
||
1792 | * @param ParserOutput $parserOutput |
||
1793 | */ |
||
1794 | public function addParserOutputContent( $parserOutput ) { |
||
1795 | $this->addParserOutputText( $parserOutput ); |
||
1796 | |||
1797 | $this->addModules( $parserOutput->getModules() ); |
||
1798 | $this->addModuleScripts( $parserOutput->getModuleScripts() ); |
||
1799 | $this->addModuleStyles( $parserOutput->getModuleStyles() ); |
||
1800 | |||
1801 | $this->addJsConfigVars( $parserOutput->getJsConfigVars() ); |
||
1802 | } |
||
1803 | |||
1804 | /** |
||
1805 | * Add the HTML associated with a ParserOutput object, without any metadata. |
||
1806 | * |
||
1807 | * @since 1.24 |
||
1808 | * @param ParserOutput $parserOutput |
||
1809 | */ |
||
1810 | public function addParserOutputText( $parserOutput ) { |
||
1811 | $text = $parserOutput->getText(); |
||
1812 | Hooks::run( 'OutputPageBeforeHTML', [ &$this, &$text ] ); |
||
1813 | $this->addHTML( $text ); |
||
1814 | } |
||
1815 | |||
1816 | /** |
||
1817 | * Add everything from a ParserOutput object. |
||
1818 | * |
||
1819 | * @param ParserOutput $parserOutput |
||
1820 | */ |
||
1821 | function addParserOutput( $parserOutput ) { |
||
1822 | $this->addParserOutputMetadata( $parserOutput ); |
||
1823 | $parserOutput->setTOCEnabled( $this->mEnableTOC ); |
||
1824 | |||
1825 | // Touch section edit links only if not previously disabled |
||
1826 | if ( $parserOutput->getEditSectionTokens() ) { |
||
1827 | $parserOutput->setEditSectionTokens( $this->mEnableSectionEditLinks ); |
||
1828 | } |
||
1829 | |||
1830 | $this->addParserOutputText( $parserOutput ); |
||
1831 | } |
||
1832 | |||
1833 | /** |
||
1834 | * Add the output of a QuickTemplate to the output buffer |
||
1835 | * |
||
1836 | * @param QuickTemplate $template |
||
1837 | */ |
||
1838 | public function addTemplate( &$template ) { |
||
1839 | $this->addHTML( $template->getHTML() ); |
||
1840 | } |
||
1841 | |||
1842 | /** |
||
1843 | * Parse wikitext and return the HTML. |
||
1844 | * |
||
1845 | * @param string $text |
||
1846 | * @param bool $linestart Is this the start of a line? |
||
1847 | * @param bool $interface Use interface language ($wgLang instead of |
||
1848 | * $wgContLang) while parsing language sensitive magic words like GRAMMAR and PLURAL. |
||
1849 | * This also disables LanguageConverter. |
||
1850 | * @param Language $language Target language object, will override $interface |
||
1851 | * @throws MWException |
||
1852 | * @return string HTML |
||
1853 | */ |
||
1854 | public function parse( $text, $linestart = true, $interface = false, $language = null ) { |
||
1855 | global $wgParser; |
||
1856 | |||
1857 | if ( is_null( $this->getTitle() ) ) { |
||
1858 | throw new MWException( 'Empty $mTitle in ' . __METHOD__ ); |
||
1859 | } |
||
1860 | |||
1861 | $popts = $this->parserOptions(); |
||
1862 | if ( $interface ) { |
||
1863 | $popts->setInterfaceMessage( true ); |
||
1864 | } |
||
1865 | if ( $language !== null ) { |
||
1866 | $oldLang = $popts->setTargetLanguage( $language ); |
||
1867 | } |
||
1868 | |||
1869 | $parserOutput = $wgParser->getFreshParser()->parse( |
||
1870 | $text, $this->getTitle(), $popts, |
||
1871 | $linestart, true, $this->mRevisionId |
||
1872 | ); |
||
1873 | |||
1874 | if ( $interface ) { |
||
1875 | $popts->setInterfaceMessage( false ); |
||
1876 | } |
||
1877 | if ( $language !== null ) { |
||
1878 | $popts->setTargetLanguage( $oldLang ); |
||
0 ignored issues
–
show
The variable
$oldLang does not seem to be defined for all execution paths leading up to this point.
If you define a variable conditionally, it can happen that it is not defined for all execution paths. Let’s take a look at an example: function myFunction($a) {
switch ($a) {
case 'foo':
$x = 1;
break;
case 'bar':
$x = 2;
break;
}
// $x is potentially undefined here.
echo $x;
}
In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined. Available Fixes
![]() |
|||
1879 | } |
||
1880 | |||
1881 | return $parserOutput->getText(); |
||
1882 | } |
||
1883 | |||
1884 | /** |
||
1885 | * Parse wikitext, strip paragraphs, and return the HTML. |
||
1886 | * |
||
1887 | * @param string $text |
||
1888 | * @param bool $linestart Is this the start of a line? |
||
1889 | * @param bool $interface Use interface language ($wgLang instead of |
||
1890 | * $wgContLang) while parsing language sensitive magic |
||
1891 | * words like GRAMMAR and PLURAL |
||
1892 | * @return string HTML |
||
1893 | */ |
||
1894 | public function parseInline( $text, $linestart = true, $interface = false ) { |
||
1895 | $parsed = $this->parse( $text, $linestart, $interface ); |
||
1896 | return Parser::stripOuterParagraph( $parsed ); |
||
1897 | } |
||
1898 | |||
1899 | /** |
||
1900 | * @param $maxage |
||
1901 | * @deprecated since 1.27 Use setCdnMaxage() instead |
||
1902 | */ |
||
1903 | public function setSquidMaxage( $maxage ) { |
||
1904 | $this->setCdnMaxage( $maxage ); |
||
1905 | } |
||
1906 | |||
1907 | /** |
||
1908 | * Set the value of the "s-maxage" part of the "Cache-control" HTTP header |
||
1909 | * |
||
1910 | * @param int $maxage Maximum cache time on the CDN, in seconds. |
||
1911 | */ |
||
1912 | public function setCdnMaxage( $maxage ) { |
||
1913 | $this->mCdnMaxage = min( $maxage, $this->mCdnMaxageLimit ); |
||
1914 | } |
||
1915 | |||
1916 | /** |
||
1917 | * Lower the value of the "s-maxage" part of the "Cache-control" HTTP header |
||
1918 | * |
||
1919 | * @param int $maxage Maximum cache time on the CDN, in seconds |
||
1920 | * @since 1.27 |
||
1921 | */ |
||
1922 | public function lowerCdnMaxage( $maxage ) { |
||
1923 | $this->mCdnMaxageLimit = min( $maxage, $this->mCdnMaxageLimit ); |
||
1924 | $this->setCdnMaxage( $this->mCdnMaxage ); |
||
1925 | } |
||
1926 | |||
1927 | /** |
||
1928 | * Get TTL in [$minTTL,$maxTTL] in pass it to lowerCdnMaxage() |
||
1929 | * |
||
1930 | * This sets and returns $minTTL if $mtime is false or null. Otherwise, |
||
1931 | * the TTL is higher the older the $mtime timestamp is. Essentially, the |
||
1932 | * TTL is 90% of the age of the object, subject to the min and max. |
||
1933 | * |
||
1934 | * @param string|integer|float|bool|null $mtime Last-Modified timestamp |
||
1935 | * @param integer $minTTL Mimimum TTL in seconds [default: 1 minute] |
||
1936 | * @param integer $maxTTL Maximum TTL in seconds [default: $wgSquidMaxage] |
||
1937 | * @return integer TTL in seconds |
||
1938 | * @since 1.28 |
||
1939 | */ |
||
1940 | public function adaptCdnTTL( $mtime, $minTTL = 0, $maxTTL = 0 ) { |
||
1941 | $minTTL = $minTTL ?: IExpiringStore::TTL_MINUTE; |
||
1942 | $maxTTL = $maxTTL ?: $this->getConfig()->get( 'SquidMaxage' ); |
||
1943 | |||
1944 | if ( $mtime === null || $mtime === false ) { |
||
1945 | return $minTTL; // entity does not exist |
||
1946 | } |
||
1947 | |||
1948 | $age = time() - wfTimestamp( TS_UNIX, $mtime ); |
||
1949 | $adaptiveTTL = max( .9 * $age, $minTTL ); |
||
1950 | $adaptiveTTL = min( $adaptiveTTL, $maxTTL ); |
||
1951 | |||
1952 | $this->lowerCdnMaxage( (int)$adaptiveTTL ); |
||
1953 | |||
1954 | return $adaptiveTTL; |
||
1955 | } |
||
1956 | |||
1957 | /** |
||
1958 | * Use enableClientCache(false) to force it to send nocache headers |
||
1959 | * |
||
1960 | * @param bool $state |
||
1961 | * |
||
1962 | * @return bool |
||
1963 | */ |
||
1964 | public function enableClientCache( $state ) { |
||
1965 | return wfSetVar( $this->mEnableClientCache, $state ); |
||
1966 | } |
||
1967 | |||
1968 | /** |
||
1969 | * Get the list of cookies that will influence on the cache |
||
1970 | * |
||
1971 | * @return array |
||
1972 | */ |
||
1973 | function getCacheVaryCookies() { |
||
1974 | static $cookies; |
||
1975 | if ( $cookies === null ) { |
||
1976 | $config = $this->getConfig(); |
||
1977 | $cookies = array_merge( |
||
1978 | SessionManager::singleton()->getVaryCookies(), |
||
1979 | [ |
||
1980 | 'forceHTTPS', |
||
1981 | ], |
||
1982 | $config->get( 'CacheVaryCookies' ) |
||
1983 | ); |
||
1984 | Hooks::run( 'GetCacheVaryCookies', [ $this, &$cookies ] ); |
||
1985 | } |
||
1986 | return $cookies; |
||
1987 | } |
||
1988 | |||
1989 | /** |
||
1990 | * Check if the request has a cache-varying cookie header |
||
1991 | * If it does, it's very important that we don't allow public caching |
||
1992 | * |
||
1993 | * @return bool |
||
1994 | */ |
||
1995 | function haveCacheVaryCookies() { |
||
1996 | $request = $this->getRequest(); |
||
1997 | foreach ( $this->getCacheVaryCookies() as $cookieName ) { |
||
1998 | if ( $request->getCookie( $cookieName, '', '' ) !== '' ) { |
||
1999 | wfDebug( __METHOD__ . ": found $cookieName\n" ); |
||
2000 | return true; |
||
2001 | } |
||
2002 | } |
||
2003 | wfDebug( __METHOD__ . ": no cache-varying cookies found\n" ); |
||
2004 | return false; |
||
2005 | } |
||
2006 | |||
2007 | /** |
||
2008 | * Add an HTTP header that will influence on the cache |
||
2009 | * |
||
2010 | * @param string $header Header name |
||
2011 | * @param string[]|null $option Options for the Key header. See |
||
2012 | * https://datatracker.ietf.org/doc/draft-fielding-http-key/ |
||
2013 | * for the list of valid options. |
||
2014 | */ |
||
2015 | public function addVaryHeader( $header, array $option = null ) { |
||
2016 | if ( !array_key_exists( $header, $this->mVaryHeader ) ) { |
||
2017 | $this->mVaryHeader[$header] = []; |
||
2018 | } |
||
2019 | if ( !is_array( $option ) ) { |
||
2020 | $option = []; |
||
2021 | } |
||
2022 | $this->mVaryHeader[$header] = array_unique( array_merge( $this->mVaryHeader[$header], $option ) ); |
||
2023 | } |
||
2024 | |||
2025 | /** |
||
2026 | * Return a Vary: header on which to vary caches. Based on the keys of $mVaryHeader, |
||
2027 | * such as Accept-Encoding or Cookie |
||
2028 | * |
||
2029 | * @return string |
||
2030 | */ |
||
2031 | public function getVaryHeader() { |
||
2032 | // If we vary on cookies, let's make sure it's always included here too. |
||
2033 | if ( $this->getCacheVaryCookies() ) { |
||
2034 | $this->addVaryHeader( 'Cookie' ); |
||
2035 | } |
||
2036 | |||
2037 | foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) { |
||
2038 | $this->addVaryHeader( $header, $options ); |
||
2039 | } |
||
2040 | return 'Vary: ' . implode( ', ', array_keys( $this->mVaryHeader ) ); |
||
2041 | } |
||
2042 | |||
2043 | /** |
||
2044 | * Get a complete Key header |
||
2045 | * |
||
2046 | * @return string |
||
2047 | */ |
||
2048 | public function getKeyHeader() { |
||
2049 | $cvCookies = $this->getCacheVaryCookies(); |
||
2050 | |||
2051 | $cookiesOption = []; |
||
2052 | foreach ( $cvCookies as $cookieName ) { |
||
2053 | $cookiesOption[] = 'param=' . $cookieName; |
||
2054 | } |
||
2055 | $this->addVaryHeader( 'Cookie', $cookiesOption ); |
||
2056 | |||
2057 | foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) { |
||
2058 | $this->addVaryHeader( $header, $options ); |
||
2059 | } |
||
2060 | |||
2061 | $headers = []; |
||
2062 | foreach ( $this->mVaryHeader as $header => $option ) { |
||
2063 | $newheader = $header; |
||
2064 | if ( is_array( $option ) && count( $option ) > 0 ) { |
||
2065 | $newheader .= ';' . implode( ';', $option ); |
||
2066 | } |
||
2067 | $headers[] = $newheader; |
||
2068 | } |
||
2069 | $key = 'Key: ' . implode( ',', $headers ); |
||
2070 | |||
2071 | return $key; |
||
2072 | } |
||
2073 | |||
2074 | /** |
||
2075 | * T23672: Add Accept-Language to Vary and Key headers |
||
2076 | * if there's no 'variant' parameter existed in GET. |
||
2077 | * |
||
2078 | * For example: |
||
2079 | * /w/index.php?title=Main_page should always be served; but |
||
2080 | * /w/index.php?title=Main_page&variant=zh-cn should never be served. |
||
2081 | */ |
||
2082 | function addAcceptLanguage() { |
||
2083 | $title = $this->getTitle(); |
||
2084 | if ( !$title instanceof Title ) { |
||
2085 | return; |
||
2086 | } |
||
2087 | |||
2088 | $lang = $title->getPageLanguage(); |
||
2089 | if ( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) { |
||
2090 | $variants = $lang->getVariants(); |
||
2091 | $aloption = []; |
||
2092 | foreach ( $variants as $variant ) { |
||
2093 | if ( $variant === $lang->getCode() ) { |
||
2094 | continue; |
||
2095 | } else { |
||
2096 | $aloption[] = 'substr=' . $variant; |
||
2097 | |||
2098 | // IE and some other browsers use BCP 47 standards in |
||
2099 | // their Accept-Language header, like "zh-CN" or "zh-Hant". |
||
2100 | // We should handle these too. |
||
2101 | $variantBCP47 = wfBCP47( $variant ); |
||
2102 | if ( $variantBCP47 !== $variant ) { |
||
2103 | $aloption[] = 'substr=' . $variantBCP47; |
||
2104 | } |
||
2105 | } |
||
2106 | } |
||
2107 | $this->addVaryHeader( 'Accept-Language', $aloption ); |
||
2108 | } |
||
2109 | } |
||
2110 | |||
2111 | /** |
||
2112 | * Set a flag which will cause an X-Frame-Options header appropriate for |
||
2113 | * edit pages to be sent. The header value is controlled by |
||
2114 | * $wgEditPageFrameOptions. |
||
2115 | * |
||
2116 | * This is the default for special pages. If you display a CSRF-protected |
||
2117 | * form on an ordinary view page, then you need to call this function. |
||
2118 | * |
||
2119 | * @param bool $enable |
||
2120 | */ |
||
2121 | public function preventClickjacking( $enable = true ) { |
||
2122 | $this->mPreventClickjacking = $enable; |
||
2123 | } |
||
2124 | |||
2125 | /** |
||
2126 | * Turn off frame-breaking. Alias for $this->preventClickjacking(false). |
||
2127 | * This can be called from pages which do not contain any CSRF-protected |
||
2128 | * HTML form. |
||
2129 | */ |
||
2130 | public function allowClickjacking() { |
||
2131 | $this->mPreventClickjacking = false; |
||
2132 | } |
||
2133 | |||
2134 | /** |
||
2135 | * Get the prevent-clickjacking flag |
||
2136 | * |
||
2137 | * @since 1.24 |
||
2138 | * @return bool |
||
2139 | */ |
||
2140 | public function getPreventClickjacking() { |
||
2141 | return $this->mPreventClickjacking; |
||
2142 | } |
||
2143 | |||
2144 | /** |
||
2145 | * Get the X-Frame-Options header value (without the name part), or false |
||
2146 | * if there isn't one. This is used by Skin to determine whether to enable |
||
2147 | * JavaScript frame-breaking, for clients that don't support X-Frame-Options. |
||
2148 | * |
||
2149 | * @return string |
||
2150 | */ |
||
2151 | public function getFrameOptions() { |
||
2152 | $config = $this->getConfig(); |
||
2153 | if ( $config->get( 'BreakFrames' ) ) { |
||
2154 | return 'DENY'; |
||
2155 | } elseif ( $this->mPreventClickjacking && $config->get( 'EditPageFrameOptions' ) ) { |
||
2156 | return $config->get( 'EditPageFrameOptions' ); |
||
2157 | } |
||
2158 | return false; |
||
2159 | } |
||
2160 | |||
2161 | /** |
||
2162 | * Send cache control HTTP headers |
||
2163 | */ |
||
2164 | public function sendCacheControl() { |
||
2165 | $response = $this->getRequest()->response(); |
||
2166 | $config = $this->getConfig(); |
||
2167 | |||
2168 | $this->addVaryHeader( 'Cookie' ); |
||
2169 | $this->addAcceptLanguage(); |
||
2170 | |||
2171 | # don't serve compressed data to clients who can't handle it |
||
2172 | # maintain different caches for logged-in users and non-logged in ones |
||
2173 | $response->header( $this->getVaryHeader() ); |
||
2174 | |||
2175 | if ( $config->get( 'UseKeyHeader' ) ) { |
||
2176 | $response->header( $this->getKeyHeader() ); |
||
2177 | } |
||
2178 | |||
2179 | if ( $this->mEnableClientCache ) { |
||
2180 | if ( |
||
2181 | $config->get( 'UseSquid' ) && |
||
2182 | !$response->hasCookies() && |
||
2183 | !SessionManager::getGlobalSession()->isPersistent() && |
||
2184 | !$this->isPrintable() && |
||
2185 | $this->mCdnMaxage != 0 && |
||
2186 | !$this->haveCacheVaryCookies() |
||
2187 | ) { |
||
2188 | if ( $config->get( 'UseESI' ) ) { |
||
2189 | # We'll purge the proxy cache explicitly, but require end user agents |
||
2190 | # to revalidate against the proxy on each visit. |
||
2191 | # Surrogate-Control controls our CDN, Cache-Control downstream caches |
||
2192 | wfDebug( __METHOD__ . |
||
2193 | ": proxy caching with ESI; {$this->mLastModified} **", 'private' ); |
||
2194 | # start with a shorter timeout for initial testing |
||
2195 | # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"'); |
||
2196 | $response->header( |
||
2197 | "Surrogate-Control: max-age={$config->get( 'SquidMaxage' )}" . |
||
2198 | "+{$this->mCdnMaxage}, content=\"ESI/1.0\"" |
||
2199 | ); |
||
2200 | $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' ); |
||
2201 | } else { |
||
2202 | # We'll purge the proxy cache for anons explicitly, but require end user agents |
||
2203 | # to revalidate against the proxy on each visit. |
||
2204 | # IMPORTANT! The CDN needs to replace the Cache-Control header with |
||
2205 | # Cache-Control: s-maxage=0, must-revalidate, max-age=0 |
||
2206 | wfDebug( __METHOD__ . |
||
2207 | ": local proxy caching; {$this->mLastModified} **", 'private' ); |
||
2208 | # start with a shorter timeout for initial testing |
||
2209 | # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" ); |
||
2210 | $response->header( "Cache-Control: " . |
||
2211 | "s-maxage={$this->mCdnMaxage}, must-revalidate, max-age=0" ); |
||
2212 | } |
||
2213 | View Code Duplication | } else { |
|
2214 | # We do want clients to cache if they can, but they *must* check for updates |
||
2215 | # on revisiting the page. |
||
2216 | wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **", 'private' ); |
||
2217 | $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' ); |
||
2218 | $response->header( "Cache-Control: private, must-revalidate, max-age=0" ); |
||
2219 | } |
||
2220 | if ( $this->mLastModified ) { |
||
2221 | $response->header( "Last-Modified: {$this->mLastModified}" ); |
||
2222 | } |
||
2223 | View Code Duplication | } else { |
|
2224 | wfDebug( __METHOD__ . ": no caching **", 'private' ); |
||
2225 | |||
2226 | # In general, the absence of a last modified header should be enough to prevent |
||
2227 | # the client from using its cache. We send a few other things just to make sure. |
||
2228 | $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' ); |
||
2229 | $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' ); |
||
2230 | $response->header( 'Pragma: no-cache' ); |
||
2231 | } |
||
2232 | } |
||
2233 | |||
2234 | /** |
||
2235 | * Finally, all the text has been munged and accumulated into |
||
2236 | * the object, let's actually output it: |
||
2237 | * |
||
2238 | * @param bool $return Set to true to get the result as a string rather than sending it |
||
2239 | * @return string|null |
||
2240 | * @throws Exception |
||
2241 | * @throws FatalError |
||
2242 | * @throws MWException |
||
2243 | */ |
||
2244 | public function output( $return = false ) { |
||
2245 | global $wgContLang; |
||
2246 | |||
2247 | if ( $this->mDoNothing ) { |
||
2248 | return $return ? '' : null; |
||
2249 | } |
||
2250 | |||
2251 | $response = $this->getRequest()->response(); |
||
2252 | $config = $this->getConfig(); |
||
2253 | |||
2254 | if ( $this->mRedirect != '' ) { |
||
2255 | # Standards require redirect URLs to be absolute |
||
2256 | $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT ); |
||
0 ignored issues
–
show
It seems like
wfExpandUrl($this->mRedirect, PROTO_CURRENT) can also be of type false . However, the property $mRedirect is declared as type string . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||
2257 | |||
2258 | $redirect = $this->mRedirect; |
||
2259 | $code = $this->mRedirectCode; |
||
2260 | |||
2261 | if ( Hooks::run( "BeforePageRedirect", [ $this, &$redirect, &$code ] ) ) { |
||
2262 | if ( $code == '301' || $code == '303' ) { |
||
2263 | if ( !$config->get( 'DebugRedirects' ) ) { |
||
2264 | $response->statusHeader( $code ); |
||
2265 | } |
||
2266 | $this->mLastModified = wfTimestamp( TS_RFC2822 ); |
||
0 ignored issues
–
show
It seems like
wfTimestamp(TS_RFC2822) can also be of type false . However, the property $mLastModified is declared as type string . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||
2267 | } |
||
2268 | if ( $config->get( 'VaryOnXFP' ) ) { |
||
2269 | $this->addVaryHeader( 'X-Forwarded-Proto' ); |
||
2270 | } |
||
2271 | $this->sendCacheControl(); |
||
2272 | |||
2273 | $response->header( "Content-Type: text/html; charset=utf-8" ); |
||
2274 | if ( $config->get( 'DebugRedirects' ) ) { |
||
2275 | $url = htmlspecialchars( $redirect ); |
||
2276 | print "<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n"; |
||
2277 | print "<p>Location: <a href=\"$url\">$url</a></p>\n"; |
||
2278 | print "</body>\n</html>\n"; |
||
2279 | } else { |
||
2280 | $response->header( 'Location: ' . $redirect ); |
||
2281 | } |
||
2282 | } |
||
2283 | |||
2284 | return $return ? '' : null; |
||
2285 | } elseif ( $this->mStatusCode ) { |
||
2286 | $response->statusHeader( $this->mStatusCode ); |
||
2287 | } |
||
2288 | |||
2289 | # Buffer output; final headers may depend on later processing |
||
2290 | ob_start(); |
||
2291 | |||
2292 | $response->header( 'Content-type: ' . $config->get( 'MimeType' ) . '; charset=UTF-8' ); |
||
2293 | $response->header( 'Content-language: ' . $wgContLang->getHtmlCode() ); |
||
2294 | |||
2295 | // Avoid Internet Explorer "compatibility view" in IE 8-10, so that |
||
2296 | // jQuery etc. can work correctly. |
||
2297 | $response->header( 'X-UA-Compatible: IE=Edge' ); |
||
2298 | |||
2299 | // Prevent framing, if requested |
||
2300 | $frameOptions = $this->getFrameOptions(); |
||
2301 | if ( $frameOptions ) { |
||
2302 | $response->header( "X-Frame-Options: $frameOptions" ); |
||
2303 | } |
||
2304 | |||
2305 | if ( $this->mArticleBodyOnly ) { |
||
2306 | echo $this->mBodytext; |
||
2307 | } else { |
||
2308 | $sk = $this->getSkin(); |
||
2309 | // add skin specific modules |
||
2310 | $modules = $sk->getDefaultModules(); |
||
2311 | |||
2312 | // Enforce various default modules for all pages and all skins |
||
2313 | $coreModules = [ |
||
2314 | // Keep this list as small as possible |
||
2315 | 'site', |
||
2316 | 'mediawiki.page.startup', |
||
2317 | 'mediawiki.user', |
||
2318 | ]; |
||
2319 | |||
2320 | // Support for high-density display images if enabled |
||
2321 | if ( $config->get( 'ResponsiveImages' ) ) { |
||
2322 | $coreModules[] = 'mediawiki.hidpi'; |
||
2323 | } |
||
2324 | |||
2325 | $this->addModules( $coreModules ); |
||
2326 | foreach ( $modules as $group ) { |
||
2327 | $this->addModules( $group ); |
||
2328 | } |
||
2329 | MWDebug::addModules( $this ); |
||
2330 | |||
2331 | // Hook that allows last minute changes to the output page, e.g. |
||
2332 | // adding of CSS or Javascript by extensions. |
||
2333 | Hooks::run( 'BeforePageDisplay', [ &$this, &$sk ] ); |
||
2334 | |||
2335 | try { |
||
2336 | $sk->outputPage(); |
||
2337 | } catch ( Exception $e ) { |
||
2338 | ob_end_clean(); // bug T129657 |
||
2339 | throw $e; |
||
2340 | } |
||
2341 | } |
||
2342 | |||
2343 | try { |
||
2344 | // This hook allows last minute changes to final overall output by modifying output buffer |
||
2345 | Hooks::run( 'AfterFinalPageOutput', [ $this ] ); |
||
2346 | } catch ( Exception $e ) { |
||
2347 | ob_end_clean(); // bug T129657 |
||
2348 | throw $e; |
||
2349 | } |
||
2350 | |||
2351 | $this->sendCacheControl(); |
||
2352 | |||
2353 | if ( $return ) { |
||
2354 | return ob_get_clean(); |
||
2355 | } else { |
||
2356 | ob_end_flush(); |
||
2357 | return null; |
||
2358 | } |
||
2359 | } |
||
2360 | |||
2361 | /** |
||
2362 | * Prepare this object to display an error page; disable caching and |
||
2363 | * indexing, clear the current text and redirect, set the page's title |
||
2364 | * and optionally an custom HTML title (content of the "<title>" tag). |
||
2365 | * |
||
2366 | * @param string|Message $pageTitle Will be passed directly to setPageTitle() |
||
2367 | * @param string|Message $htmlTitle Will be passed directly to setHTMLTitle(); |
||
2368 | * optional, if not passed the "<title>" attribute will be |
||
2369 | * based on $pageTitle |
||
2370 | */ |
||
2371 | public function prepareErrorPage( $pageTitle, $htmlTitle = false ) { |
||
2372 | $this->setPageTitle( $pageTitle ); |
||
2373 | if ( $htmlTitle !== false ) { |
||
2374 | $this->setHTMLTitle( $htmlTitle ); |
||
2375 | } |
||
2376 | $this->setRobotPolicy( 'noindex,nofollow' ); |
||
2377 | $this->setArticleRelated( false ); |
||
2378 | $this->enableClientCache( false ); |
||
2379 | $this->mRedirect = ''; |
||
2380 | $this->clearSubtitle(); |
||
2381 | $this->clearHTML(); |
||
2382 | } |
||
2383 | |||
2384 | /** |
||
2385 | * Output a standard error page |
||
2386 | * |
||
2387 | * showErrorPage( 'titlemsg', 'pagetextmsg' ); |
||
2388 | * showErrorPage( 'titlemsg', 'pagetextmsg', [ 'param1', 'param2' ] ); |
||
2389 | * showErrorPage( 'titlemsg', $messageObject ); |
||
2390 | * showErrorPage( $titleMessageObject, $messageObject ); |
||
2391 | * |
||
2392 | * @param string|Message $title Message key (string) for page title, or a Message object |
||
2393 | * @param string|Message $msg Message key (string) for page text, or a Message object |
||
2394 | * @param array $params Message parameters; ignored if $msg is a Message object |
||
2395 | */ |
||
2396 | public function showErrorPage( $title, $msg, $params = [] ) { |
||
2397 | if ( !$title instanceof Message ) { |
||
2398 | $title = $this->msg( $title ); |
||
2399 | } |
||
2400 | |||
2401 | $this->prepareErrorPage( $title ); |
||
2402 | |||
2403 | if ( $msg instanceof Message ) { |
||
2404 | if ( $params !== [] ) { |
||
2405 | trigger_error( 'Argument ignored: $params. The message parameters argument ' |
||
2406 | . 'is discarded when the $msg argument is a Message object instead of ' |
||
2407 | . 'a string.', E_USER_NOTICE ); |
||
2408 | } |
||
2409 | $this->addHTML( $msg->parseAsBlock() ); |
||
2410 | } else { |
||
2411 | $this->addWikiMsgArray( $msg, $params ); |
||
2412 | } |
||
2413 | |||
2414 | $this->returnToMain(); |
||
2415 | } |
||
2416 | |||
2417 | /** |
||
2418 | * Output a standard permission error page |
||
2419 | * |
||
2420 | * @param array $errors Error message keys or [key, param...] arrays |
||
2421 | * @param string $action Action that was denied or null if unknown |
||
2422 | */ |
||
2423 | public function showPermissionsErrorPage( array $errors, $action = null ) { |
||
2424 | foreach ( $errors as $key => $error ) { |
||
2425 | $errors[$key] = (array)$error; |
||
2426 | } |
||
2427 | |||
2428 | // For some action (read, edit, create and upload), display a "login to do this action" |
||
2429 | // error if all of the following conditions are met: |
||
2430 | // 1. the user is not logged in |
||
2431 | // 2. the only error is insufficient permissions (i.e. no block or something else) |
||
2432 | // 3. the error can be avoided simply by logging in |
||
2433 | if ( in_array( $action, [ 'read', 'edit', 'createpage', 'createtalk', 'upload' ] ) |
||
2434 | && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] ) |
||
2435 | && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' ) |
||
2436 | && ( User::groupHasPermission( 'user', $action ) |
||
2437 | || User::groupHasPermission( 'autoconfirmed', $action ) ) |
||
2438 | ) { |
||
2439 | $displayReturnto = null; |
||
2440 | |||
2441 | # Due to bug 32276, if a user does not have read permissions, |
||
2442 | # $this->getTitle() will just give Special:Badtitle, which is |
||
2443 | # not especially useful as a returnto parameter. Use the title |
||
2444 | # from the request instead, if there was one. |
||
2445 | $request = $this->getRequest(); |
||
2446 | $returnto = Title::newFromText( $request->getVal( 'title', '' ) ); |
||
2447 | if ( $action == 'edit' ) { |
||
2448 | $msg = 'whitelistedittext'; |
||
2449 | $displayReturnto = $returnto; |
||
2450 | } elseif ( $action == 'createpage' || $action == 'createtalk' ) { |
||
2451 | $msg = 'nocreatetext'; |
||
2452 | } elseif ( $action == 'upload' ) { |
||
2453 | $msg = 'uploadnologintext'; |
||
2454 | } else { # Read |
||
2455 | $msg = 'loginreqpagetext'; |
||
2456 | $displayReturnto = Title::newMainPage(); |
||
2457 | } |
||
2458 | |||
2459 | $query = []; |
||
2460 | |||
2461 | if ( $returnto ) { |
||
2462 | $query['returnto'] = $returnto->getPrefixedText(); |
||
2463 | |||
2464 | View Code Duplication | if ( !$request->wasPosted() ) { |
|
2465 | $returntoquery = $request->getValues(); |
||
2466 | unset( $returntoquery['title'] ); |
||
2467 | unset( $returntoquery['returnto'] ); |
||
2468 | unset( $returntoquery['returntoquery'] ); |
||
2469 | $query['returntoquery'] = wfArrayToCgi( $returntoquery ); |
||
2470 | } |
||
2471 | } |
||
2472 | $loginLink = Linker::linkKnown( |
||
2473 | SpecialPage::getTitleFor( 'Userlogin' ), |
||
2474 | $this->msg( 'loginreqlink' )->escaped(), |
||
2475 | [], |
||
2476 | $query |
||
2477 | ); |
||
2478 | |||
2479 | $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) ); |
||
2480 | $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->parse() ); |
||
2481 | |||
2482 | # Don't return to a page the user can't read otherwise |
||
2483 | # we'll end up in a pointless loop |
||
2484 | if ( $displayReturnto && $displayReturnto->userCan( 'read', $this->getUser() ) ) { |
||
2485 | $this->returnToMain( null, $displayReturnto ); |
||
2486 | } |
||
2487 | } else { |
||
2488 | $this->prepareErrorPage( $this->msg( 'permissionserrors' ) ); |
||
2489 | $this->addWikiText( $this->formatPermissionsErrorMessage( $errors, $action ) ); |
||
2490 | } |
||
2491 | } |
||
2492 | |||
2493 | /** |
||
2494 | * Display an error page indicating that a given version of MediaWiki is |
||
2495 | * required to use it |
||
2496 | * |
||
2497 | * @param mixed $version The version of MediaWiki needed to use the page |
||
2498 | */ |
||
2499 | public function versionRequired( $version ) { |
||
2500 | $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) ); |
||
2501 | |||
2502 | $this->addWikiMsg( 'versionrequiredtext', $version ); |
||
2503 | $this->returnToMain(); |
||
2504 | } |
||
2505 | |||
2506 | /** |
||
2507 | * Format a list of error messages |
||
2508 | * |
||
2509 | * @param array $errors Array of arrays returned by Title::getUserPermissionsErrors |
||
2510 | * @param string $action Action that was denied or null if unknown |
||
2511 | * @return string The wikitext error-messages, formatted into a list. |
||
2512 | */ |
||
2513 | public function formatPermissionsErrorMessage( array $errors, $action = null ) { |
||
2514 | if ( $action == null ) { |
||
0 ignored issues
–
show
|
|||
2515 | $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n"; |
||
2516 | } else { |
||
2517 | $action_desc = $this->msg( "action-$action" )->plain(); |
||
2518 | $text = $this->msg( |
||
2519 | 'permissionserrorstext-withaction', |
||
2520 | count( $errors ), |
||
2521 | $action_desc |
||
2522 | )->plain() . "\n\n"; |
||
2523 | } |
||
2524 | |||
2525 | if ( count( $errors ) > 1 ) { |
||
2526 | $text .= '<ul class="permissions-errors">' . "\n"; |
||
2527 | |||
2528 | foreach ( $errors as $error ) { |
||
2529 | $text .= '<li>'; |
||
2530 | $text .= call_user_func_array( [ $this, 'msg' ], $error )->plain(); |
||
2531 | $text .= "</li>\n"; |
||
2532 | } |
||
2533 | $text .= '</ul>'; |
||
2534 | } else { |
||
2535 | $text .= "<div class=\"permissions-errors\">\n" . |
||
2536 | call_user_func_array( [ $this, 'msg' ], reset( $errors ) )->plain() . |
||
2537 | "\n</div>"; |
||
2538 | } |
||
2539 | |||
2540 | return $text; |
||
2541 | } |
||
2542 | |||
2543 | /** |
||
2544 | * Display a page stating that the Wiki is in read-only mode. |
||
2545 | * Should only be called after wfReadOnly() has returned true. |
||
2546 | * |
||
2547 | * Historically, this function was used to show the source of the page that the user |
||
2548 | * was trying to edit and _also_ permissions error messages. The relevant code was |
||
2549 | * moved into EditPage in 1.19 (r102024 / d83c2a431c2a) and removed here in 1.25. |
||
2550 | * |
||
2551 | * @deprecated since 1.25; throw the exception directly |
||
2552 | * @throws ReadOnlyError |
||
2553 | */ |
||
2554 | public function readOnlyPage() { |
||
2555 | if ( func_num_args() > 0 ) { |
||
2556 | throw new MWException( __METHOD__ . ' no longer accepts arguments since 1.25.' ); |
||
2557 | } |
||
2558 | |||
2559 | throw new ReadOnlyError; |
||
2560 | } |
||
2561 | |||
2562 | /** |
||
2563 | * Turn off regular page output and return an error response |
||
2564 | * for when rate limiting has triggered. |
||
2565 | * |
||
2566 | * @deprecated since 1.25; throw the exception directly |
||
2567 | */ |
||
2568 | public function rateLimited() { |
||
2569 | wfDeprecated( __METHOD__, '1.25' ); |
||
2570 | throw new ThrottledError; |
||
2571 | } |
||
2572 | |||
2573 | /** |
||
2574 | * Show a warning about replica DB lag |
||
2575 | * |
||
2576 | * If the lag is higher than $wgSlaveLagCritical seconds, |
||
2577 | * then the warning is a bit more obvious. If the lag is |
||
2578 | * lower than $wgSlaveLagWarning, then no warning is shown. |
||
2579 | * |
||
2580 | * @param int $lag Slave lag |
||
2581 | */ |
||
2582 | public function showLagWarning( $lag ) { |
||
2583 | $config = $this->getConfig(); |
||
2584 | if ( $lag >= $config->get( 'SlaveLagWarning' ) ) { |
||
2585 | $lag = floor( $lag ); // floor to avoid nano seconds to display |
||
2586 | $message = $lag < $config->get( 'SlaveLagCritical' ) |
||
2587 | ? 'lag-warn-normal' |
||
2588 | : 'lag-warn-high'; |
||
2589 | $wrap = Html::rawElement( 'div', [ 'class' => "mw-{$message}" ], "\n$1\n" ); |
||
2590 | $this->wrapWikiMsg( "$wrap\n", [ $message, $this->getLanguage()->formatNum( $lag ) ] ); |
||
2591 | } |
||
2592 | } |
||
2593 | |||
2594 | public function showFatalError( $message ) { |
||
2595 | $this->prepareErrorPage( $this->msg( 'internalerror' ) ); |
||
2596 | |||
2597 | $this->addHTML( $message ); |
||
2598 | } |
||
2599 | |||
2600 | public function showUnexpectedValueError( $name, $val ) { |
||
2601 | $this->showFatalError( $this->msg( 'unexpected', $name, $val )->text() ); |
||
2602 | } |
||
2603 | |||
2604 | public function showFileCopyError( $old, $new ) { |
||
2605 | $this->showFatalError( $this->msg( 'filecopyerror', $old, $new )->text() ); |
||
2606 | } |
||
2607 | |||
2608 | public function showFileRenameError( $old, $new ) { |
||
2609 | $this->showFatalError( $this->msg( 'filerenameerror', $old, $new )->text() ); |
||
2610 | } |
||
2611 | |||
2612 | public function showFileDeleteError( $name ) { |
||
2613 | $this->showFatalError( $this->msg( 'filedeleteerror', $name )->text() ); |
||
2614 | } |
||
2615 | |||
2616 | public function showFileNotFoundError( $name ) { |
||
2617 | $this->showFatalError( $this->msg( 'filenotfound', $name )->text() ); |
||
2618 | } |
||
2619 | |||
2620 | /** |
||
2621 | * Add a "return to" link pointing to a specified title |
||
2622 | * |
||
2623 | * @param Title $title Title to link |
||
2624 | * @param array $query Query string parameters |
||
2625 | * @param string $text Text of the link (input is not escaped) |
||
2626 | * @param array $options Options array to pass to Linker |
||
2627 | */ |
||
2628 | public function addReturnTo( $title, array $query = [], $text = null, $options = [] ) { |
||
2629 | $link = $this->msg( 'returnto' )->rawParams( |
||
2630 | Linker::link( $title, $text, [], $query, $options ) )->escaped(); |
||
2631 | $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" ); |
||
2632 | } |
||
2633 | |||
2634 | /** |
||
2635 | * Add a "return to" link pointing to a specified title, |
||
2636 | * or the title indicated in the request, or else the main page |
||
2637 | * |
||
2638 | * @param mixed $unused |
||
2639 | * @param Title|string $returnto Title or String to return to |
||
2640 | * @param string $returntoquery Query string for the return to link |
||
2641 | */ |
||
2642 | public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) { |
||
2643 | if ( $returnto == null ) { |
||
0 ignored issues
–
show
|
|||
2644 | $returnto = $this->getRequest()->getText( 'returnto' ); |
||
2645 | } |
||
2646 | |||
2647 | if ( $returntoquery == null ) { |
||
0 ignored issues
–
show
|
|||
2648 | $returntoquery = $this->getRequest()->getText( 'returntoquery' ); |
||
2649 | } |
||
2650 | |||
2651 | if ( $returnto === '' ) { |
||
2652 | $returnto = Title::newMainPage(); |
||
2653 | } |
||
2654 | |||
2655 | if ( is_object( $returnto ) ) { |
||
2656 | $titleObj = $returnto; |
||
2657 | } else { |
||
2658 | $titleObj = Title::newFromText( $returnto ); |
||
2659 | } |
||
2660 | if ( !is_object( $titleObj ) ) { |
||
2661 | $titleObj = Title::newMainPage(); |
||
2662 | } |
||
2663 | |||
2664 | $this->addReturnTo( $titleObj, wfCgiToArray( $returntoquery ) ); |
||
0 ignored issues
–
show
It seems like
$titleObj can be null ; however, addReturnTo() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
2665 | } |
||
2666 | |||
2667 | private function getRlClientContext() { |
||
2668 | if ( !$this->rlClientContext ) { |
||
2669 | $query = ResourceLoader::makeLoaderQuery( |
||
2670 | [], // modules; not relevant |
||
2671 | $this->getLanguage()->getCode(), |
||
2672 | $this->getSkin()->getSkinName(), |
||
2673 | $this->getUser()->isLoggedIn() ? $this->getUser()->getName() : null, |
||
2674 | null, // version; not relevant |
||
2675 | ResourceLoader::inDebugMode(), |
||
2676 | null, // only; not relevant |
||
2677 | $this->isPrintable(), |
||
2678 | $this->getRequest()->getBool( 'handheld' ) |
||
2679 | ); |
||
2680 | $this->rlClientContext = new ResourceLoaderContext( |
||
2681 | $this->getResourceLoader(), |
||
2682 | new FauxRequest( $query ) |
||
2683 | ); |
||
2684 | } |
||
2685 | return $this->rlClientContext; |
||
2686 | } |
||
2687 | |||
2688 | /** |
||
2689 | * Call this to freeze the module queue and JS config and create a formatter. |
||
2690 | * |
||
2691 | * Depending on the Skin, this may get lazy-initialised in either headElement() or |
||
2692 | * getBottomScripts(). See SkinTemplate::prepareQuickTemplate(). Calling this too early may |
||
2693 | * cause unexpected side-effects since disallowUserJs() may be called at any time to change |
||
2694 | * the module filters retroactively. Skins and extension hooks may also add modules until very |
||
2695 | * late in the request lifecycle. |
||
2696 | * |
||
2697 | * @return ResourceLoaderClientHtml |
||
2698 | */ |
||
2699 | public function getRlClient() { |
||
2700 | if ( !$this->rlClient ) { |
||
2701 | $context = $this->getRlClientContext(); |
||
2702 | $rl = $this->getResourceLoader(); |
||
2703 | $this->addModules( [ |
||
2704 | 'user.options', |
||
2705 | 'user.tokens', |
||
2706 | ] ); |
||
2707 | $this->addModuleStyles( [ |
||
2708 | 'site.styles', |
||
2709 | 'noscript', |
||
2710 | 'user.styles', |
||
2711 | 'user.cssprefs', |
||
2712 | ] ); |
||
2713 | $this->getSkin()->setupSkinUserCss( $this ); |
||
2714 | |||
2715 | // Prepare exempt modules for buildExemptModules() |
||
2716 | $exemptGroups = [ 'site' => [], 'noscript' => [], 'private' => [], 'user' => [] ]; |
||
2717 | $exemptStates = []; |
||
2718 | $moduleStyles = $this->getModuleStyles( /*filter*/ true ); |
||
2719 | |||
2720 | // Preload getTitleInfo for isKnownEmpty calls below and in ResourceLoaderClientHtml |
||
2721 | // Separate user-specific batch for improved cache-hit ratio. |
||
2722 | $userBatch = [ 'user.styles', 'user' ]; |
||
2723 | $siteBatch = array_diff( $moduleStyles, $userBatch ); |
||
2724 | $dbr = wfGetDB( DB_REPLICA ); |
||
2725 | ResourceLoaderWikiModule::preloadTitleInfo( $context, $dbr, $siteBatch ); |
||
0 ignored issues
–
show
It seems like
$dbr defined by wfGetDB(DB_REPLICA) on line 2724 can be null ; however, ResourceLoaderWikiModule::preloadTitleInfo() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
2726 | ResourceLoaderWikiModule::preloadTitleInfo( $context, $dbr, $userBatch ); |
||
0 ignored issues
–
show
It seems like
$dbr defined by wfGetDB(DB_REPLICA) on line 2724 can be null ; however, ResourceLoaderWikiModule::preloadTitleInfo() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
2727 | |||
2728 | // Filter out modules handled by buildExemptModules() |
||
2729 | $moduleStyles = array_filter( $moduleStyles, |
||
2730 | function ( $name ) use ( $rl, $context, &$exemptGroups, &$exemptStates ) { |
||
2731 | $module = $rl->getModule( $name ); |
||
2732 | if ( $module ) { |
||
2733 | if ( $name === 'user.styles' && $this->isUserCssPreview() ) { |
||
2734 | $exemptStates[$name] = 'ready'; |
||
2735 | // Special case in buildExemptModules() |
||
2736 | return false; |
||
2737 | } |
||
2738 | $group = $module->getGroup(); |
||
2739 | if ( isset( $exemptGroups[$group] ) ) { |
||
2740 | $exemptStates[$name] = 'ready'; |
||
2741 | if ( !$module->isKnownEmpty( $context ) ) { |
||
2742 | // E.g. Don't output empty <styles> |
||
2743 | $exemptGroups[$group][] = $name; |
||
2744 | } |
||
2745 | return false; |
||
2746 | } |
||
2747 | } |
||
2748 | return true; |
||
2749 | } |
||
2750 | ); |
||
2751 | $this->rlExemptStyleModules = $exemptGroups; |
||
2752 | |||
2753 | $isUserModuleFiltered = !$this->filterModules( [ 'user' ] ); |
||
2754 | // If this page filters out 'user', makeResourceLoaderLink will drop it. |
||
2755 | // Avoid indefinite "loading" state or untrue "ready" state (T145368). |
||
2756 | if ( !$isUserModuleFiltered ) { |
||
2757 | // Manually handled by getBottomScripts() |
||
2758 | $userModule = $rl->getModule( 'user' ); |
||
2759 | $userState = $userModule->isKnownEmpty( $context ) && !$this->isUserJsPreview() |
||
2760 | ? 'ready' |
||
2761 | : 'loading'; |
||
2762 | $this->rlUserModuleState = $exemptStates['user'] = $userState; |
||
2763 | } |
||
2764 | |||
2765 | $rlClient = new ResourceLoaderClientHtml( $context, $this->getTarget() ); |
||
0 ignored issues
–
show
It seems like
$this->getTarget() targeting OutputPage::getTarget() can also be of type string ; however, ResourceLoaderClientHtml::__construct() does only seem to accept object<aray>|null , maybe add an additional type check?
This check looks at variables that are passed out again to other methods. If the outgoing method call has stricter type requirements than the method itself, an issue is raised. An additional type check may prevent trouble. ![]() |
|||
2766 | $rlClient->setConfig( $this->getJSVars() ); |
||
2767 | $rlClient->setModules( $this->getModules( /*filter*/ true ) ); |
||
2768 | $rlClient->setModuleStyles( $moduleStyles ); |
||
2769 | $rlClient->setModuleScripts( $this->getModuleScripts( /*filter*/ true ) ); |
||
2770 | $rlClient->setExemptStates( $exemptStates ); |
||
2771 | $this->rlClient = $rlClient; |
||
2772 | } |
||
2773 | return $this->rlClient; |
||
2774 | } |
||
2775 | |||
2776 | /** |
||
2777 | * @param Skin $sk The given Skin |
||
2778 | * @param bool $includeStyle Unused |
||
2779 | * @return string The doctype, opening "<html>", and head element. |
||
2780 | */ |
||
2781 | public function headElement( Skin $sk, $includeStyle = true ) { |
||
2782 | global $wgContLang; |
||
2783 | |||
2784 | $userdir = $this->getLanguage()->getDir(); |
||
2785 | $sitedir = $wgContLang->getDir(); |
||
2786 | |||
2787 | $pieces = []; |
||
2788 | $pieces[] = Html::htmlHeader( Sanitizer::mergeAttributes( |
||
2789 | $this->getRlClient()->getDocumentAttributes(), |
||
2790 | $sk->getHtmlElementAttributes() |
||
2791 | ) ); |
||
2792 | $pieces[] = Html::openElement( 'head' ); |
||
2793 | |||
2794 | if ( $this->getHTMLTitle() == '' ) { |
||
2795 | $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() )->inContentLanguage() ); |
||
2796 | } |
||
2797 | |||
2798 | if ( !Html::isXmlMimeType( $this->getConfig()->get( 'MimeType' ) ) ) { |
||
2799 | // Add <meta charset="UTF-8"> |
||
2800 | // This should be before <title> since it defines the charset used by |
||
2801 | // text including the text inside <title>. |
||
2802 | // The spec recommends defining XHTML5's charset using the XML declaration |
||
2803 | // instead of meta. |
||
2804 | // Our XML declaration is output by Html::htmlHeader. |
||
2805 | // https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-type |
||
2806 | // https://html.spec.whatwg.org/multipage/semantics.html#charset |
||
2807 | $pieces[] = Html::element( 'meta', [ 'charset' => 'UTF-8' ] ); |
||
2808 | } |
||
2809 | |||
2810 | $pieces[] = Html::element( 'title', null, $this->getHTMLTitle() ); |
||
2811 | $pieces[] = $this->getRlClient()->getHeadHtml(); |
||
2812 | $pieces[] = $this->buildExemptModules(); |
||
2813 | $pieces = array_merge( $pieces, array_values( $this->getHeadLinksArray() ) ); |
||
2814 | $pieces = array_merge( $pieces, array_values( $this->mHeadItems ) ); |
||
2815 | $pieces[] = Html::closeElement( 'head' ); |
||
2816 | |||
2817 | $bodyClasses = []; |
||
2818 | $bodyClasses[] = 'mediawiki'; |
||
2819 | |||
2820 | # Classes for LTR/RTL directionality support |
||
2821 | $bodyClasses[] = $userdir; |
||
2822 | $bodyClasses[] = "sitedir-$sitedir"; |
||
2823 | |||
2824 | if ( $this->getLanguage()->capitalizeAllNouns() ) { |
||
2825 | # A <body> class is probably not the best way to do this . . . |
||
2826 | $bodyClasses[] = 'capitalize-all-nouns'; |
||
2827 | } |
||
2828 | |||
2829 | // Parser feature migration class |
||
2830 | // The idea is that this will eventually be removed, after the wikitext |
||
2831 | // which requires it is cleaned up. |
||
2832 | $bodyClasses[] = 'mw-hide-empty-elt'; |
||
2833 | |||
2834 | $bodyClasses[] = $sk->getPageClasses( $this->getTitle() ); |
||
0 ignored issues
–
show
It seems like
$this->getTitle() can be null ; however, getPageClasses() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
2835 | $bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() ); |
||
2836 | $bodyClasses[] = |
||
2837 | 'action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) ); |
||
2838 | |||
2839 | $bodyAttrs = []; |
||
2840 | // While the implode() is not strictly needed, it's used for backwards compatibility |
||
2841 | // (this used to be built as a string and hooks likely still expect that). |
||
2842 | $bodyAttrs['class'] = implode( ' ', $bodyClasses ); |
||
2843 | |||
2844 | // Allow skins and extensions to add body attributes they need |
||
2845 | $sk->addToBodyAttributes( $this, $bodyAttrs ); |
||
2846 | Hooks::run( 'OutputPageBodyAttributes', [ $this, $sk, &$bodyAttrs ] ); |
||
2847 | |||
2848 | $pieces[] = Html::openElement( 'body', $bodyAttrs ); |
||
2849 | |||
2850 | return self::combineWrappedStrings( $pieces ); |
||
2851 | } |
||
2852 | |||
2853 | /** |
||
2854 | * Get a ResourceLoader object associated with this OutputPage |
||
2855 | * |
||
2856 | * @return ResourceLoader |
||
2857 | */ |
||
2858 | public function getResourceLoader() { |
||
2859 | if ( is_null( $this->mResourceLoader ) ) { |
||
2860 | $this->mResourceLoader = new ResourceLoader( |
||
2861 | $this->getConfig(), |
||
2862 | LoggerFactory::getInstance( 'resourceloader' ) |
||
2863 | ); |
||
2864 | } |
||
2865 | return $this->mResourceLoader; |
||
2866 | } |
||
2867 | |||
2868 | /** |
||
2869 | * Explicily load or embed modules on a page. |
||
2870 | * |
||
2871 | * @param array|string $modules One or more module names |
||
2872 | * @param string $only ResourceLoaderModule TYPE_ class constant |
||
2873 | * @param array $extraQuery [optional] Array with extra query parameters for the request |
||
2874 | * @return string|WrappedStringList HTML |
||
2875 | */ |
||
2876 | public function makeResourceLoaderLink( $modules, $only, array $extraQuery = [] ) { |
||
2877 | // Apply 'target' and 'origin' filters |
||
2878 | $modules = $this->filterModules( (array)$modules, null, $only ); |
||
2879 | |||
2880 | return ResourceLoaderClientHtml::makeLoad( |
||
2881 | $this->getRlClientContext(), |
||
2882 | $modules, |
||
2883 | $only, |
||
2884 | $extraQuery |
||
2885 | ); |
||
2886 | } |
||
2887 | |||
2888 | /** |
||
2889 | * Combine WrappedString chunks and filter out empty ones |
||
2890 | * |
||
2891 | * @param array $chunks |
||
2892 | * @return string|WrappedStringList HTML |
||
2893 | */ |
||
2894 | protected static function combineWrappedStrings( array $chunks ) { |
||
2895 | // Filter out empty values |
||
2896 | $chunks = array_filter( $chunks, 'strlen' ); |
||
2897 | return WrappedString::join( "\n", $chunks ); |
||
2898 | } |
||
2899 | |||
2900 | private function isUserJsPreview() { |
||
2901 | return $this->getConfig()->get( 'AllowUserJs' ) |
||
2902 | && $this->getTitle() |
||
2903 | && $this->getTitle()->isJsSubpage() |
||
2904 | && $this->userCanPreview(); |
||
2905 | } |
||
2906 | |||
2907 | private function isUserCssPreview() { |
||
2908 | return $this->getConfig()->get( 'AllowUserCss' ) |
||
2909 | && $this->getTitle() |
||
2910 | && $this->getTitle()->isCssSubpage() |
||
2911 | && $this->userCanPreview(); |
||
2912 | } |
||
2913 | |||
2914 | /** |
||
2915 | * JS stuff to put at the bottom of the `<body>`. These are modules with position 'bottom', |
||
2916 | * legacy scripts ($this->mScripts), and user JS. |
||
2917 | * |
||
2918 | * @return string|WrappedStringList HTML |
||
2919 | */ |
||
2920 | public function getBottomScripts() { |
||
2921 | $chunks = []; |
||
2922 | $chunks[] = $this->getRlClient()->getBodyHtml(); |
||
2923 | |||
2924 | // Legacy non-ResourceLoader scripts |
||
2925 | $chunks[] = $this->mScripts; |
||
2926 | |||
2927 | // Exempt 'user' module |
||
2928 | // - May need excludepages for live preview. (T28283) |
||
2929 | // - Must use TYPE_COMBINED so its response is handled by mw.loader.implement() which |
||
2930 | // ensures execution is scheduled after the "site" module. |
||
2931 | // - Don't load if module state is already resolved as "ready". |
||
2932 | if ( $this->rlUserModuleState === 'loading' ) { |
||
2933 | if ( $this->isUserJsPreview() ) { |
||
2934 | $chunks[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_COMBINED, |
||
2935 | [ 'excludepage' => $this->getTitle()->getPrefixedDBkey() ] |
||
2936 | ); |
||
2937 | $chunks[] = ResourceLoader::makeInlineScript( |
||
2938 | Xml::encodeJsCall( 'mw.loader.using', [ |
||
0 ignored issues
–
show
|
|||
2939 | [ 'user', 'site' ], |
||
2940 | new XmlJsCode( |
||
2941 | 'function () {' |
||
2942 | . Xml::encodeJsCall( '$.globalEval', [ |
||
2943 | $this->getRequest()->getText( 'wpTextbox1' ) |
||
2944 | ] ) |
||
2945 | . '}' |
||
2946 | ) |
||
2947 | ] ) |
||
2948 | ); |
||
2949 | // FIXME: If the user is previewing, say, ./vector.js, his ./common.js will be loaded |
||
2950 | // asynchronously and may arrive *after* the inline script here. So the previewed code |
||
2951 | // may execute before ./common.js runs. Normally, ./common.js runs before ./vector.js. |
||
2952 | // Similarly, when previewing ./common.js and the user module does arrive first, |
||
2953 | // it will arrive without common.js and the inline script runs after. |
||
2954 | // Thus running common after the excluded subpage. |
||
2955 | } else { |
||
2956 | // Load normally |
||
2957 | $chunks[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_COMBINED ); |
||
2958 | } |
||
2959 | } |
||
2960 | |||
2961 | return self::combineWrappedStrings( $chunks ); |
||
2962 | } |
||
2963 | |||
2964 | /** |
||
2965 | * Get the javascript config vars to include on this page |
||
2966 | * |
||
2967 | * @return array Array of javascript config vars |
||
2968 | * @since 1.23 |
||
2969 | */ |
||
2970 | public function getJsConfigVars() { |
||
2971 | return $this->mJsConfigVars; |
||
2972 | } |
||
2973 | |||
2974 | /** |
||
2975 | * Add one or more variables to be set in mw.config in JavaScript |
||
2976 | * |
||
2977 | * @param string|array $keys Key or array of key/value pairs |
||
2978 | * @param mixed $value [optional] Value of the configuration variable |
||
2979 | */ |
||
2980 | View Code Duplication | public function addJsConfigVars( $keys, $value = null ) { |
|
2981 | if ( is_array( $keys ) ) { |
||
2982 | foreach ( $keys as $key => $value ) { |
||
2983 | $this->mJsConfigVars[$key] = $value; |
||
2984 | } |
||
2985 | return; |
||
2986 | } |
||
2987 | |||
2988 | $this->mJsConfigVars[$keys] = $value; |
||
2989 | } |
||
2990 | |||
2991 | /** |
||
2992 | * Get an array containing the variables to be set in mw.config in JavaScript. |
||
2993 | * |
||
2994 | * Do not add things here which can be evaluated in ResourceLoaderStartUpModule |
||
2995 | * - in other words, page-independent/site-wide variables (without state). |
||
2996 | * You will only be adding bloat to the html page and causing page caches to |
||
2997 | * have to be purged on configuration changes. |
||
2998 | * @return array |
||
2999 | */ |
||
3000 | public function getJSVars() { |
||
3001 | global $wgContLang; |
||
3002 | |||
3003 | $curRevisionId = 0; |
||
3004 | $articleId = 0; |
||
3005 | $canonicalSpecialPageName = false; # bug 21115 |
||
3006 | |||
3007 | $title = $this->getTitle(); |
||
3008 | $ns = $title->getNamespace(); |
||
3009 | $canonicalNamespace = MWNamespace::exists( $ns ) |
||
3010 | ? MWNamespace::getCanonicalName( $ns ) |
||
3011 | : $title->getNsText(); |
||
3012 | |||
3013 | $sk = $this->getSkin(); |
||
3014 | // Get the relevant title so that AJAX features can use the correct page name |
||
3015 | // when making API requests from certain special pages (bug 34972). |
||
3016 | $relevantTitle = $sk->getRelevantTitle(); |
||
3017 | $relevantUser = $sk->getRelevantUser(); |
||
3018 | |||
3019 | if ( $ns == NS_SPECIAL ) { |
||
3020 | list( $canonicalSpecialPageName, /*...*/ ) = |
||
3021 | SpecialPageFactory::resolveAlias( $title->getDBkey() ); |
||
3022 | } elseif ( $this->canUseWikiPage() ) { |
||
3023 | $wikiPage = $this->getWikiPage(); |
||
3024 | $curRevisionId = $wikiPage->getLatest(); |
||
3025 | $articleId = $wikiPage->getId(); |
||
3026 | } |
||
3027 | |||
3028 | $lang = $title->getPageViewLanguage(); |
||
3029 | |||
3030 | // Pre-process information |
||
3031 | $separatorTransTable = $lang->separatorTransformTable(); |
||
3032 | $separatorTransTable = $separatorTransTable ? $separatorTransTable : []; |
||
3033 | $compactSeparatorTransTable = [ |
||
3034 | implode( "\t", array_keys( $separatorTransTable ) ), |
||
3035 | implode( "\t", $separatorTransTable ), |
||
3036 | ]; |
||
3037 | $digitTransTable = $lang->digitTransformTable(); |
||
3038 | $digitTransTable = $digitTransTable ? $digitTransTable : []; |
||
3039 | $compactDigitTransTable = [ |
||
3040 | implode( "\t", array_keys( $digitTransTable ) ), |
||
3041 | implode( "\t", $digitTransTable ), |
||
3042 | ]; |
||
3043 | |||
3044 | $user = $this->getUser(); |
||
3045 | |||
3046 | $vars = [ |
||
3047 | 'wgCanonicalNamespace' => $canonicalNamespace, |
||
3048 | 'wgCanonicalSpecialPageName' => $canonicalSpecialPageName, |
||
3049 | 'wgNamespaceNumber' => $title->getNamespace(), |
||
3050 | 'wgPageName' => $title->getPrefixedDBkey(), |
||
3051 | 'wgTitle' => $title->getText(), |
||
3052 | 'wgCurRevisionId' => $curRevisionId, |
||
3053 | 'wgRevisionId' => (int)$this->getRevisionId(), |
||
3054 | 'wgArticleId' => $articleId, |
||
3055 | 'wgIsArticle' => $this->isArticle(), |
||
3056 | 'wgIsRedirect' => $title->isRedirect(), |
||
3057 | 'wgAction' => Action::getActionName( $this->getContext() ), |
||
3058 | 'wgUserName' => $user->isAnon() ? null : $user->getName(), |
||
3059 | 'wgUserGroups' => $user->getEffectiveGroups(), |
||
3060 | 'wgCategories' => $this->getCategories(), |
||
3061 | 'wgBreakFrames' => $this->getFrameOptions() == 'DENY', |
||
3062 | 'wgPageContentLanguage' => $lang->getCode(), |
||
3063 | 'wgPageContentModel' => $title->getContentModel(), |
||
3064 | 'wgSeparatorTransformTable' => $compactSeparatorTransTable, |
||
3065 | 'wgDigitTransformTable' => $compactDigitTransTable, |
||
3066 | 'wgDefaultDateFormat' => $lang->getDefaultDateFormat(), |
||
3067 | 'wgMonthNames' => $lang->getMonthNamesArray(), |
||
3068 | 'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(), |
||
3069 | 'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(), |
||
3070 | 'wgRelevantArticleId' => $relevantTitle->getArticleID(), |
||
3071 | 'wgRequestId' => WebRequest::getRequestId(), |
||
3072 | ]; |
||
3073 | |||
3074 | if ( $user->isLoggedIn() ) { |
||
3075 | $vars['wgUserId'] = $user->getId(); |
||
3076 | $vars['wgUserEditCount'] = $user->getEditCount(); |
||
3077 | $userReg = $user->getRegistration(); |
||
3078 | $vars['wgUserRegistration'] = $userReg ? wfTimestamp( TS_UNIX, $userReg ) * 1000 : null; |
||
3079 | // Get the revision ID of the oldest new message on the user's talk |
||
3080 | // page. This can be used for constructing new message alerts on |
||
3081 | // the client side. |
||
3082 | $vars['wgUserNewMsgRevisionId'] = $user->getNewMessageRevisionId(); |
||
3083 | } |
||
3084 | |||
3085 | if ( $wgContLang->hasVariants() ) { |
||
3086 | $vars['wgUserVariant'] = $wgContLang->getPreferredVariant(); |
||
3087 | } |
||
3088 | // Same test as SkinTemplate |
||
3089 | $vars['wgIsProbablyEditable'] = $title->quickUserCan( 'edit', $user ) |
||
3090 | && ( $title->exists() || $title->quickUserCan( 'create', $user ) ); |
||
3091 | |||
3092 | foreach ( $title->getRestrictionTypes() as $type ) { |
||
3093 | $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type ); |
||
3094 | } |
||
3095 | |||
3096 | if ( $title->isMainPage() ) { |
||
3097 | $vars['wgIsMainPage'] = true; |
||
3098 | } |
||
3099 | |||
3100 | if ( $this->mRedirectedFrom ) { |
||
3101 | $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey(); |
||
3102 | } |
||
3103 | |||
3104 | if ( $relevantUser ) { |
||
3105 | $vars['wgRelevantUserName'] = $relevantUser->getName(); |
||
3106 | } |
||
3107 | |||
3108 | // Allow extensions to add their custom variables to the mw.config map. |
||
3109 | // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not |
||
3110 | // page-dependant but site-wide (without state). |
||
3111 | // Alternatively, you may want to use OutputPage->addJsConfigVars() instead. |
||
3112 | Hooks::run( 'MakeGlobalVariablesScript', [ &$vars, $this ] ); |
||
3113 | |||
3114 | // Merge in variables from addJsConfigVars last |
||
3115 | return array_merge( $vars, $this->getJsConfigVars() ); |
||
3116 | } |
||
3117 | |||
3118 | /** |
||
3119 | * To make it harder for someone to slip a user a fake |
||
3120 | * user-JavaScript or user-CSS preview, a random token |
||
3121 | * is associated with the login session. If it's not |
||
3122 | * passed back with the preview request, we won't render |
||
3123 | * the code. |
||
3124 | * |
||
3125 | * @return bool |
||
3126 | */ |
||
3127 | public function userCanPreview() { |
||
3128 | $request = $this->getRequest(); |
||
3129 | if ( |
||
3130 | $request->getVal( 'action' ) !== 'submit' || |
||
3131 | !$request->getCheck( 'wpPreview' ) || |
||
3132 | !$request->wasPosted() |
||
3133 | ) { |
||
3134 | return false; |
||
3135 | } |
||
3136 | |||
3137 | $user = $this->getUser(); |
||
3138 | |||
3139 | if ( !$user->isLoggedIn() ) { |
||
3140 | // Anons have predictable edit tokens |
||
3141 | return false; |
||
3142 | } |
||
3143 | if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) { |
||
3144 | return false; |
||
3145 | } |
||
3146 | |||
3147 | $title = $this->getTitle(); |
||
3148 | if ( !$title->isJsSubpage() && !$title->isCssSubpage() ) { |
||
3149 | return false; |
||
3150 | } |
||
3151 | if ( !$title->isSubpageOf( $user->getUserPage() ) ) { |
||
3152 | // Don't execute another user's CSS or JS on preview (T85855) |
||
3153 | return false; |
||
3154 | } |
||
3155 | |||
3156 | $errors = $title->getUserPermissionsErrors( 'edit', $user ); |
||
3157 | if ( count( $errors ) !== 0 ) { |
||
3158 | return false; |
||
3159 | } |
||
3160 | |||
3161 | return true; |
||
3162 | } |
||
3163 | |||
3164 | /** |
||
3165 | * @return array Array in format "link name or number => 'link html'". |
||
3166 | */ |
||
3167 | public function getHeadLinksArray() { |
||
3168 | global $wgVersion; |
||
3169 | |||
3170 | $tags = []; |
||
3171 | $config = $this->getConfig(); |
||
3172 | |||
3173 | $canonicalUrl = $this->mCanonicalUrl; |
||
3174 | |||
3175 | $tags['meta-generator'] = Html::element( 'meta', [ |
||
3176 | 'name' => 'generator', |
||
3177 | 'content' => "MediaWiki $wgVersion", |
||
3178 | ] ); |
||
3179 | |||
3180 | if ( $config->get( 'ReferrerPolicy' ) !== false ) { |
||
3181 | $tags['meta-referrer'] = Html::element( 'meta', [ |
||
3182 | 'name' => 'referrer', |
||
3183 | 'content' => $config->get( 'ReferrerPolicy' ) |
||
3184 | ] ); |
||
3185 | } |
||
3186 | |||
3187 | $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}"; |
||
3188 | if ( $p !== 'index,follow' ) { |
||
3189 | // http://www.robotstxt.org/wc/meta-user.html |
||
3190 | // Only show if it's different from the default robots policy |
||
3191 | $tags['meta-robots'] = Html::element( 'meta', [ |
||
3192 | 'name' => 'robots', |
||
3193 | 'content' => $p, |
||
3194 | ] ); |
||
3195 | } |
||
3196 | |||
3197 | foreach ( $this->mMetatags as $tag ) { |
||
3198 | if ( 0 == strcasecmp( 'http:', substr( $tag[0], 0, 5 ) ) ) { |
||
3199 | $a = 'http-equiv'; |
||
3200 | $tag[0] = substr( $tag[0], 5 ); |
||
3201 | } else { |
||
3202 | $a = 'name'; |
||
3203 | } |
||
3204 | $tagName = "meta-{$tag[0]}"; |
||
3205 | if ( isset( $tags[$tagName] ) ) { |
||
3206 | $tagName .= $tag[1]; |
||
3207 | } |
||
3208 | $tags[$tagName] = Html::element( 'meta', |
||
3209 | [ |
||
3210 | $a => $tag[0], |
||
3211 | 'content' => $tag[1] |
||
3212 | ] |
||
3213 | ); |
||
3214 | } |
||
3215 | |||
3216 | foreach ( $this->mLinktags as $tag ) { |
||
3217 | $tags[] = Html::element( 'link', $tag ); |
||
3218 | } |
||
3219 | |||
3220 | # Universal edit button |
||
3221 | if ( $config->get( 'UniversalEditButton' ) && $this->isArticleRelated() ) { |
||
3222 | $user = $this->getUser(); |
||
3223 | if ( $this->getTitle()->quickUserCan( 'edit', $user ) |
||
3224 | && ( $this->getTitle()->exists() || |
||
3225 | $this->getTitle()->quickUserCan( 'create', $user ) ) |
||
3226 | ) { |
||
3227 | // Original UniversalEditButton |
||
3228 | $msg = $this->msg( 'edit' )->text(); |
||
3229 | $tags['universal-edit-button'] = Html::element( 'link', [ |
||
3230 | 'rel' => 'alternate', |
||
3231 | 'type' => 'application/x-wiki', |
||
3232 | 'title' => $msg, |
||
3233 | 'href' => $this->getTitle()->getEditURL(), |
||
3234 | ] ); |
||
3235 | // Alternate edit link |
||
3236 | $tags['alternative-edit'] = Html::element( 'link', [ |
||
3237 | 'rel' => 'edit', |
||
3238 | 'title' => $msg, |
||
3239 | 'href' => $this->getTitle()->getEditURL(), |
||
3240 | ] ); |
||
3241 | } |
||
3242 | } |
||
3243 | |||
3244 | # Generally the order of the favicon and apple-touch-icon links |
||
3245 | # should not matter, but Konqueror (3.5.9 at least) incorrectly |
||
3246 | # uses whichever one appears later in the HTML source. Make sure |
||
3247 | # apple-touch-icon is specified first to avoid this. |
||
3248 | if ( $config->get( 'AppleTouchIcon' ) !== false ) { |
||
3249 | $tags['apple-touch-icon'] = Html::element( 'link', [ |
||
3250 | 'rel' => 'apple-touch-icon', |
||
3251 | 'href' => $config->get( 'AppleTouchIcon' ) |
||
3252 | ] ); |
||
3253 | } |
||
3254 | |||
3255 | if ( $config->get( 'Favicon' ) !== false ) { |
||
3256 | $tags['favicon'] = Html::element( 'link', [ |
||
3257 | 'rel' => 'shortcut icon', |
||
3258 | 'href' => $config->get( 'Favicon' ) |
||
3259 | ] ); |
||
3260 | } |
||
3261 | |||
3262 | # OpenSearch description link |
||
3263 | $tags['opensearch'] = Html::element( 'link', [ |
||
3264 | 'rel' => 'search', |
||
3265 | 'type' => 'application/opensearchdescription+xml', |
||
3266 | 'href' => wfScript( 'opensearch_desc' ), |
||
3267 | 'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(), |
||
3268 | ] ); |
||
3269 | |||
3270 | if ( $config->get( 'EnableAPI' ) ) { |
||
3271 | # Real Simple Discovery link, provides auto-discovery information |
||
3272 | # for the MediaWiki API (and potentially additional custom API |
||
3273 | # support such as WordPress or Twitter-compatible APIs for a |
||
3274 | # blogging extension, etc) |
||
3275 | $tags['rsd'] = Html::element( 'link', [ |
||
3276 | 'rel' => 'EditURI', |
||
3277 | 'type' => 'application/rsd+xml', |
||
3278 | // Output a protocol-relative URL here if $wgServer is protocol-relative. |
||
3279 | // Whether RSD accepts relative or protocol-relative URLs is completely |
||
3280 | // undocumented, though. |
||
3281 | 'href' => wfExpandUrl( wfAppendQuery( |
||
3282 | wfScript( 'api' ), |
||
3283 | [ 'action' => 'rsd' ] ), |
||
3284 | PROTO_RELATIVE |
||
3285 | ), |
||
3286 | ] ); |
||
3287 | } |
||
3288 | |||
3289 | # Language variants |
||
3290 | if ( !$config->get( 'DisableLangConversion' ) ) { |
||
3291 | $lang = $this->getTitle()->getPageLanguage(); |
||
3292 | if ( $lang->hasVariants() ) { |
||
3293 | $variants = $lang->getVariants(); |
||
3294 | foreach ( $variants as $variant ) { |
||
3295 | $tags["variant-$variant"] = Html::element( 'link', [ |
||
3296 | 'rel' => 'alternate', |
||
3297 | 'hreflang' => wfBCP47( $variant ), |
||
3298 | 'href' => $this->getTitle()->getLocalURL( |
||
3299 | [ 'variant' => $variant ] ) |
||
3300 | ] |
||
3301 | ); |
||
3302 | } |
||
3303 | # x-default link per https://support.google.com/webmasters/answer/189077?hl=en |
||
3304 | $tags["variant-x-default"] = Html::element( 'link', [ |
||
3305 | 'rel' => 'alternate', |
||
3306 | 'hreflang' => 'x-default', |
||
3307 | 'href' => $this->getTitle()->getLocalURL() ] ); |
||
3308 | } |
||
3309 | } |
||
3310 | |||
3311 | # Copyright |
||
3312 | if ( $this->copyrightUrl !== null ) { |
||
3313 | $copyright = $this->copyrightUrl; |
||
3314 | } else { |
||
3315 | $copyright = ''; |
||
3316 | if ( $config->get( 'RightsPage' ) ) { |
||
3317 | $copy = Title::newFromText( $config->get( 'RightsPage' ) ); |
||
3318 | |||
3319 | if ( $copy ) { |
||
3320 | $copyright = $copy->getLocalURL(); |
||
3321 | } |
||
3322 | } |
||
3323 | |||
3324 | if ( !$copyright && $config->get( 'RightsUrl' ) ) { |
||
3325 | $copyright = $config->get( 'RightsUrl' ); |
||
3326 | } |
||
3327 | } |
||
3328 | |||
3329 | if ( $copyright ) { |
||
3330 | $tags['copyright'] = Html::element( 'link', [ |
||
3331 | 'rel' => 'copyright', |
||
3332 | 'href' => $copyright ] |
||
3333 | ); |
||
3334 | } |
||
3335 | |||
3336 | # Feeds |
||
3337 | if ( $config->get( 'Feed' ) ) { |
||
3338 | $feedLinks = []; |
||
3339 | |||
3340 | foreach ( $this->getSyndicationLinks() as $format => $link ) { |
||
3341 | # Use the page name for the title. In principle, this could |
||
3342 | # lead to issues with having the same name for different feeds |
||
3343 | # corresponding to the same page, but we can't avoid that at |
||
3344 | # this low a level. |
||
3345 | |||
3346 | $feedLinks[] = $this->feedLink( |
||
3347 | $format, |
||
3348 | $link, |
||
3349 | # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep) |
||
3350 | $this->msg( |
||
3351 | "page-{$format}-feed", $this->getTitle()->getPrefixedText() |
||
3352 | )->text() |
||
3353 | ); |
||
3354 | } |
||
3355 | |||
3356 | # Recent changes feed should appear on every page (except recentchanges, |
||
3357 | # that would be redundant). Put it after the per-page feed to avoid |
||
3358 | # changing existing behavior. It's still available, probably via a |
||
3359 | # menu in your browser. Some sites might have a different feed they'd |
||
3360 | # like to promote instead of the RC feed (maybe like a "Recent New Articles" |
||
3361 | # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined. |
||
3362 | # If so, use it instead. |
||
3363 | $sitename = $config->get( 'Sitename' ); |
||
3364 | if ( $config->get( 'OverrideSiteFeed' ) ) { |
||
3365 | foreach ( $config->get( 'OverrideSiteFeed' ) as $type => $feedUrl ) { |
||
3366 | // Note, this->feedLink escapes the url. |
||
3367 | $feedLinks[] = $this->feedLink( |
||
3368 | $type, |
||
3369 | $feedUrl, |
||
3370 | $this->msg( "site-{$type}-feed", $sitename )->text() |
||
3371 | ); |
||
3372 | } |
||
3373 | } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) { |
||
3374 | $rctitle = SpecialPage::getTitleFor( 'Recentchanges' ); |
||
3375 | foreach ( $config->get( 'AdvertisedFeedTypes' ) as $format ) { |
||
3376 | $feedLinks[] = $this->feedLink( |
||
3377 | $format, |
||
3378 | $rctitle->getLocalURL( [ 'feed' => $format ] ), |
||
3379 | # For grep: 'site-rss-feed', 'site-atom-feed' |
||
3380 | $this->msg( "site-{$format}-feed", $sitename )->text() |
||
3381 | ); |
||
3382 | } |
||
3383 | } |
||
3384 | |||
3385 | # Allow extensions to change the list pf feeds. This hook is primarily for changing, |
||
3386 | # manipulating or removing existing feed tags. If you want to add new feeds, you should |
||
3387 | # use OutputPage::addFeedLink() instead. |
||
3388 | Hooks::run( 'AfterBuildFeedLinks', [ &$feedLinks ] ); |
||
3389 | |||
3390 | $tags += $feedLinks; |
||
3391 | } |
||
3392 | |||
3393 | # Canonical URL |
||
3394 | if ( $config->get( 'EnableCanonicalServerLink' ) ) { |
||
3395 | if ( $canonicalUrl !== false ) { |
||
3396 | $canonicalUrl = wfExpandUrl( $canonicalUrl, PROTO_CANONICAL ); |
||
3397 | } else { |
||
3398 | if ( $this->isArticleRelated() ) { |
||
3399 | // This affects all requests where "setArticleRelated" is true. This is |
||
3400 | // typically all requests that show content (query title, curid, oldid, diff), |
||
3401 | // and all wikipage actions (edit, delete, purge, info, history etc.). |
||
3402 | // It does not apply to File pages and Special pages. |
||
3403 | // 'history' and 'info' actions address page metadata rather than the page |
||
3404 | // content itself, so they may not be canonicalized to the view page url. |
||
3405 | // TODO: this ought to be better encapsulated in the Action class. |
||
3406 | $action = Action::getActionName( $this->getContext() ); |
||
3407 | if ( in_array( $action, [ 'history', 'info' ] ) ) { |
||
3408 | $query = "action={$action}"; |
||
3409 | } else { |
||
3410 | $query = ''; |
||
3411 | } |
||
3412 | $canonicalUrl = $this->getTitle()->getCanonicalURL( $query ); |
||
3413 | } else { |
||
3414 | $reqUrl = $this->getRequest()->getRequestURL(); |
||
3415 | $canonicalUrl = wfExpandUrl( $reqUrl, PROTO_CANONICAL ); |
||
3416 | } |
||
3417 | } |
||
3418 | } |
||
3419 | if ( $canonicalUrl !== false ) { |
||
3420 | $tags[] = Html::element( 'link', [ |
||
3421 | 'rel' => 'canonical', |
||
3422 | 'href' => $canonicalUrl |
||
3423 | ] ); |
||
3424 | } |
||
3425 | |||
3426 | return $tags; |
||
3427 | } |
||
3428 | |||
3429 | /** |
||
3430 | * @return string HTML tag links to be put in the header. |
||
3431 | * @deprecated since 1.24 Use OutputPage::headElement or if you have to, |
||
3432 | * OutputPage::getHeadLinksArray directly. |
||
3433 | */ |
||
3434 | public function getHeadLinks() { |
||
3435 | wfDeprecated( __METHOD__, '1.24' ); |
||
3436 | return implode( "\n", $this->getHeadLinksArray() ); |
||
3437 | } |
||
3438 | |||
3439 | /** |
||
3440 | * Generate a "<link rel/>" for a feed. |
||
3441 | * |
||
3442 | * @param string $type Feed type |
||
3443 | * @param string $url URL to the feed |
||
3444 | * @param string $text Value of the "title" attribute |
||
3445 | * @return string HTML fragment |
||
3446 | */ |
||
3447 | private function feedLink( $type, $url, $text ) { |
||
3448 | return Html::element( 'link', [ |
||
3449 | 'rel' => 'alternate', |
||
3450 | 'type' => "application/$type+xml", |
||
3451 | 'title' => $text, |
||
3452 | 'href' => $url ] |
||
3453 | ); |
||
3454 | } |
||
3455 | |||
3456 | /** |
||
3457 | * Add a local or specified stylesheet, with the given media options. |
||
3458 | * Internal use only. Use OutputPage::addModuleStyles() if possible. |
||
3459 | * |
||
3460 | * @param string $style URL to the file |
||
3461 | * @param string $media To specify a media type, 'screen', 'printable', 'handheld' or any. |
||
3462 | * @param string $condition For IE conditional comments, specifying an IE version |
||
3463 | * @param string $dir Set to 'rtl' or 'ltr' for direction-specific sheets |
||
3464 | */ |
||
3465 | public function addStyle( $style, $media = '', $condition = '', $dir = '' ) { |
||
3466 | $options = []; |
||
3467 | if ( $media ) { |
||
3468 | $options['media'] = $media; |
||
3469 | } |
||
3470 | if ( $condition ) { |
||
3471 | $options['condition'] = $condition; |
||
3472 | } |
||
3473 | if ( $dir ) { |
||
3474 | $options['dir'] = $dir; |
||
3475 | } |
||
3476 | $this->styles[$style] = $options; |
||
3477 | } |
||
3478 | |||
3479 | /** |
||
3480 | * Adds inline CSS styles |
||
3481 | * Internal use only. Use OutputPage::addModuleStyles() if possible. |
||
3482 | * |
||
3483 | * @param mixed $style_css Inline CSS |
||
3484 | * @param string $flip Set to 'flip' to flip the CSS if needed |
||
3485 | */ |
||
3486 | public function addInlineStyle( $style_css, $flip = 'noflip' ) { |
||
3487 | if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) { |
||
3488 | # If wanted, and the interface is right-to-left, flip the CSS |
||
3489 | $style_css = CSSJanus::transform( $style_css, true, false ); |
||
3490 | } |
||
3491 | $this->mInlineStyles .= Html::inlineStyle( $style_css ); |
||
3492 | } |
||
3493 | |||
3494 | /** |
||
3495 | * Build exempt modules and legacy non-ResourceLoader styles. |
||
3496 | * |
||
3497 | * @return string|WrappedStringList HTML |
||
3498 | */ |
||
3499 | protected function buildExemptModules() { |
||
3500 | global $wgContLang; |
||
3501 | |||
3502 | $resourceLoader = $this->getResourceLoader(); |
||
0 ignored issues
–
show
$resourceLoader is not used, you could remove the assignment.
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently. $myVar = 'Value';
$higher = false;
if (rand(1, 6) > 3) {
$higher = true;
} else {
$higher = false;
}
Both the ![]() |
|||
3503 | $chunks = []; |
||
3504 | // Things that go after the ResourceLoaderDynamicStyles marker |
||
3505 | $append = []; |
||
3506 | |||
3507 | // Exempt 'user' styles module (may need 'excludepages' for live preview) |
||
3508 | if ( $this->isUserCssPreview() ) { |
||
3509 | $append[] = $this->makeResourceLoaderLink( |
||
3510 | 'user.styles', |
||
3511 | ResourceLoaderModule::TYPE_STYLES, |
||
3512 | [ 'excludepage' => $this->getTitle()->getPrefixedDBkey() ] |
||
3513 | ); |
||
3514 | |||
3515 | // Load the previewed CSS. Janus it if needed. |
||
3516 | // User-supplied CSS is assumed to in the wiki's content language. |
||
3517 | $previewedCSS = $this->getRequest()->getText( 'wpTextbox1' ); |
||
3518 | if ( $this->getLanguage()->getDir() !== $wgContLang->getDir() ) { |
||
3519 | $previewedCSS = CSSJanus::transform( $previewedCSS, true, false ); |
||
3520 | } |
||
3521 | $append[] = Html::inlineStyle( $previewedCSS ); |
||
3522 | } |
||
3523 | |||
3524 | // We want site, private and user styles to override dynamically added styles from |
||
3525 | // general modules, but we want dynamically added styles to override statically added |
||
3526 | // style modules. So the order has to be: |
||
3527 | // - page style modules (formatted by ResourceLoaderClientHtml::getHeadHtml()) |
||
3528 | // - dynamically loaded styles (added by mw.loader before ResourceLoaderDynamicStyles) |
||
3529 | // - ResourceLoaderDynamicStyles marker |
||
3530 | // - site/private/user styles |
||
3531 | |||
3532 | // Add legacy styles added through addStyle()/addInlineStyle() here |
||
3533 | $chunks[] = implode( '', $this->buildCssLinksArray() ) . $this->mInlineStyles; |
||
3534 | |||
3535 | $chunks[] = Html::element( |
||
3536 | 'meta', |
||
3537 | [ 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ] |
||
3538 | ); |
||
3539 | |||
3540 | foreach ( $this->rlExemptStyleModules as $group => $moduleNames ) { |
||
3541 | $chunks[] = $this->makeResourceLoaderLink( $moduleNames, |
||
3542 | ResourceLoaderModule::TYPE_STYLES |
||
3543 | ); |
||
3544 | } |
||
3545 | |||
3546 | return self::combineWrappedStrings( array_merge( $chunks, $append ) ); |
||
3547 | } |
||
3548 | |||
3549 | /** |
||
3550 | * @return array |
||
3551 | */ |
||
3552 | public function buildCssLinksArray() { |
||
3553 | $links = []; |
||
3554 | |||
3555 | // Add any extension CSS |
||
3556 | foreach ( $this->mExtStyles as $url ) { |
||
3557 | $this->addStyle( $url ); |
||
3558 | } |
||
3559 | $this->mExtStyles = []; |
||
3560 | |||
3561 | foreach ( $this->styles as $file => $options ) { |
||
3562 | $link = $this->styleLink( $file, $options ); |
||
3563 | if ( $link ) { |
||
3564 | $links[$file] = $link; |
||
3565 | } |
||
3566 | } |
||
3567 | return $links; |
||
3568 | } |
||
3569 | |||
3570 | /** |
||
3571 | * Generate \<link\> tags for stylesheets |
||
3572 | * |
||
3573 | * @param string $style URL to the file |
||
3574 | * @param array $options Option, can contain 'condition', 'dir', 'media' keys |
||
3575 | * @return string HTML fragment |
||
3576 | */ |
||
3577 | protected function styleLink( $style, array $options ) { |
||
3578 | if ( isset( $options['dir'] ) ) { |
||
3579 | if ( $this->getLanguage()->getDir() != $options['dir'] ) { |
||
3580 | return ''; |
||
3581 | } |
||
3582 | } |
||
3583 | |||
3584 | if ( isset( $options['media'] ) ) { |
||
3585 | $media = self::transformCssMedia( $options['media'] ); |
||
3586 | if ( is_null( $media ) ) { |
||
3587 | return ''; |
||
3588 | } |
||
3589 | } else { |
||
3590 | $media = 'all'; |
||
3591 | } |
||
3592 | |||
3593 | if ( substr( $style, 0, 1 ) == '/' || |
||
3594 | substr( $style, 0, 5 ) == 'http:' || |
||
3595 | substr( $style, 0, 6 ) == 'https:' ) { |
||
3596 | $url = $style; |
||
3597 | } else { |
||
3598 | $config = $this->getConfig(); |
||
3599 | $url = $config->get( 'StylePath' ) . '/' . $style . '?' . |
||
3600 | $config->get( 'StyleVersion' ); |
||
3601 | } |
||
3602 | |||
3603 | $link = Html::linkedStyle( $url, $media ); |
||
3604 | |||
3605 | if ( isset( $options['condition'] ) ) { |
||
3606 | $condition = htmlspecialchars( $options['condition'] ); |
||
3607 | $link = "<!--[if $condition]>$link<![endif]-->"; |
||
3608 | } |
||
3609 | return $link; |
||
3610 | } |
||
3611 | |||
3612 | /** |
||
3613 | * Transform path to web-accessible static resource. |
||
3614 | * |
||
3615 | * This is used to add a validation hash as query string. |
||
3616 | * This aids various behaviors: |
||
3617 | * |
||
3618 | * - Put long Cache-Control max-age headers on responses for improved |
||
3619 | * cache performance. |
||
3620 | * - Get the correct version of a file as expected by the current page. |
||
3621 | * - Instantly get the updated version of a file after deployment. |
||
3622 | * |
||
3623 | * Avoid using this for urls included in HTML as otherwise clients may get different |
||
3624 | * versions of a resource when navigating the site depending on when the page was cached. |
||
3625 | * If changes to the url propagate, this is not a problem (e.g. if the url is in |
||
3626 | * an external stylesheet). |
||
3627 | * |
||
3628 | * @since 1.27 |
||
3629 | * @param Config $config |
||
3630 | * @param string $path Path-absolute URL to file (from document root, must start with "/") |
||
3631 | * @return string URL |
||
3632 | */ |
||
3633 | public static function transformResourcePath( Config $config, $path ) { |
||
3634 | global $IP; |
||
3635 | $remotePathPrefix = $config->get( 'ResourceBasePath' ); |
||
3636 | if ( $remotePathPrefix === '' ) { |
||
3637 | // The configured base path is required to be empty string for |
||
3638 | // wikis in the domain root |
||
3639 | $remotePath = '/'; |
||
3640 | } else { |
||
3641 | $remotePath = $remotePathPrefix; |
||
3642 | } |
||
3643 | if ( strpos( $path, $remotePath ) !== 0 ) { |
||
3644 | // Path is outside wgResourceBasePath, ignore. |
||
3645 | return $path; |
||
3646 | } |
||
3647 | $path = RelPath\getRelativePath( $path, $remotePath ); |
||
3648 | return self::transformFilePath( $remotePathPrefix, $IP, $path ); |
||
0 ignored issues
–
show
It seems like
$path defined by \RelPath\getRelativePath($path, $remotePath) on line 3647 can also be of type false ; however, OutputPage::transformFilePath() does only seem to accept string , did you maybe forget to handle an error condition?
This check looks for type mismatches where the missing type is Consider the follow example <?php
function getDate($date)
{
if ($date !== null) {
return new DateTime($date);
}
return false;
}
This function either returns a new ![]() |
|||
3649 | } |
||
3650 | |||
3651 | /** |
||
3652 | * Utility method for transformResourceFilePath(). |
||
3653 | * |
||
3654 | * Caller is responsible for ensuring the file exists. Emits a PHP warning otherwise. |
||
3655 | * |
||
3656 | * @since 1.27 |
||
3657 | * @param string $remotePath URL path prefix that points to $localPath |
||
0 ignored issues
–
show
There is no parameter named
$remotePath . Did you maybe mean $remotePathPrefix ?
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit. Consider the following example. The parameter /**
* @param array $germany
* @param array $ireland
*/
function finale($germany, $island) {
return "2:1";
}
The most likely cause is that the parameter was changed, but the annotation was not. ![]() |
|||
3658 | * @param string $localPath File directory exposed at $remotePath |
||
3659 | * @param string $file Path to target file relative to $localPath |
||
3660 | * @return string URL |
||
3661 | */ |
||
3662 | public static function transformFilePath( $remotePathPrefix, $localPath, $file ) { |
||
3663 | $hash = md5_file( "$localPath/$file" ); |
||
3664 | if ( $hash === false ) { |
||
3665 | wfLogWarning( __METHOD__ . ": Failed to hash $localPath/$file" ); |
||
3666 | $hash = ''; |
||
3667 | } |
||
3668 | return "$remotePathPrefix/$file?" . substr( $hash, 0, 5 ); |
||
3669 | } |
||
3670 | |||
3671 | /** |
||
3672 | * Transform "media" attribute based on request parameters |
||
3673 | * |
||
3674 | * @param string $media Current value of the "media" attribute |
||
3675 | * @return string Modified value of the "media" attribute, or null to skip |
||
3676 | * this stylesheet |
||
3677 | */ |
||
3678 | public static function transformCssMedia( $media ) { |
||
3679 | global $wgRequest; |
||
3680 | |||
3681 | // https://www.w3.org/TR/css3-mediaqueries/#syntax |
||
3682 | $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i'; |
||
3683 | |||
3684 | // Switch in on-screen display for media testing |
||
3685 | $switches = [ |
||
3686 | 'printable' => 'print', |
||
3687 | 'handheld' => 'handheld', |
||
3688 | ]; |
||
3689 | foreach ( $switches as $switch => $targetMedia ) { |
||
3690 | if ( $wgRequest->getBool( $switch ) ) { |
||
3691 | if ( $media == $targetMedia ) { |
||
3692 | $media = ''; |
||
3693 | } elseif ( preg_match( $screenMediaQueryRegex, $media ) === 1 ) { |
||
3694 | /* This regex will not attempt to understand a comma-separated media_query_list |
||
3695 | * |
||
3696 | * Example supported values for $media: |
||
3697 | * 'screen', 'only screen', 'screen and (min-width: 982px)' ), |
||
3698 | * Example NOT supported value for $media: |
||
3699 | * '3d-glasses, screen, print and resolution > 90dpi' |
||
3700 | * |
||
3701 | * If it's a print request, we never want any kind of screen stylesheets |
||
3702 | * If it's a handheld request (currently the only other choice with a switch), |
||
3703 | * we don't want simple 'screen' but we might want screen queries that |
||
3704 | * have a max-width or something, so we'll pass all others on and let the |
||
3705 | * client do the query. |
||
3706 | */ |
||
3707 | if ( $targetMedia == 'print' || $media == 'screen' ) { |
||
3708 | return null; |
||
3709 | } |
||
3710 | } |
||
3711 | } |
||
3712 | } |
||
3713 | |||
3714 | return $media; |
||
3715 | } |
||
3716 | |||
3717 | /** |
||
3718 | * Add a wikitext-formatted message to the output. |
||
3719 | * This is equivalent to: |
||
3720 | * |
||
3721 | * $wgOut->addWikiText( wfMessage( ... )->plain() ) |
||
3722 | */ |
||
3723 | public function addWikiMsg( /*...*/ ) { |
||
3724 | $args = func_get_args(); |
||
3725 | $name = array_shift( $args ); |
||
3726 | $this->addWikiMsgArray( $name, $args ); |
||
3727 | } |
||
3728 | |||
3729 | /** |
||
3730 | * Add a wikitext-formatted message to the output. |
||
3731 | * Like addWikiMsg() except the parameters are taken as an array |
||
3732 | * instead of a variable argument list. |
||
3733 | * |
||
3734 | * @param string $name |
||
3735 | * @param array $args |
||
3736 | */ |
||
3737 | public function addWikiMsgArray( $name, $args ) { |
||
3738 | $this->addHTML( $this->msg( $name, $args )->parseAsBlock() ); |
||
3739 | } |
||
3740 | |||
3741 | /** |
||
3742 | * This function takes a number of message/argument specifications, wraps them in |
||
3743 | * some overall structure, and then parses the result and adds it to the output. |
||
3744 | * |
||
3745 | * In the $wrap, $1 is replaced with the first message, $2 with the second, |
||
3746 | * and so on. The subsequent arguments may be either |
||
3747 | * 1) strings, in which case they are message names, or |
||
3748 | * 2) arrays, in which case, within each array, the first element is the message |
||
3749 | * name, and subsequent elements are the parameters to that message. |
||
3750 | * |
||
3751 | * Don't use this for messages that are not in the user's interface language. |
||
3752 | * |
||
3753 | * For example: |
||
3754 | * |
||
3755 | * $wgOut->wrapWikiMsg( "<div class='error'>\n$1\n</div>", 'some-error' ); |
||
3756 | * |
||
3757 | * Is equivalent to: |
||
3758 | * |
||
3759 | * $wgOut->addWikiText( "<div class='error'>\n" |
||
3760 | * . wfMessage( 'some-error' )->plain() . "\n</div>" ); |
||
3761 | * |
||
3762 | * The newline after the opening div is needed in some wikitext. See bug 19226. |
||
3763 | * |
||
3764 | * @param string $wrap |
||
3765 | */ |
||
3766 | public function wrapWikiMsg( $wrap /*, ...*/ ) { |
||
3767 | $msgSpecs = func_get_args(); |
||
3768 | array_shift( $msgSpecs ); |
||
3769 | $msgSpecs = array_values( $msgSpecs ); |
||
3770 | $s = $wrap; |
||
3771 | foreach ( $msgSpecs as $n => $spec ) { |
||
3772 | if ( is_array( $spec ) ) { |
||
3773 | $args = $spec; |
||
3774 | $name = array_shift( $args ); |
||
3775 | if ( isset( $args['options'] ) ) { |
||
3776 | unset( $args['options'] ); |
||
3777 | wfDeprecated( |
||
3778 | 'Adding "options" to ' . __METHOD__ . ' is no longer supported', |
||
3779 | '1.20' |
||
3780 | ); |
||
3781 | } |
||
3782 | } else { |
||
3783 | $args = []; |
||
3784 | $name = $spec; |
||
3785 | } |
||
3786 | $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s ); |
||
3787 | } |
||
3788 | $this->addWikiText( $s ); |
||
3789 | } |
||
3790 | |||
3791 | /** |
||
3792 | * Enables/disables TOC, doesn't override __NOTOC__ |
||
3793 | * @param bool $flag |
||
3794 | * @since 1.22 |
||
3795 | */ |
||
3796 | public function enableTOC( $flag = true ) { |
||
3797 | $this->mEnableTOC = $flag; |
||
3798 | } |
||
3799 | |||
3800 | /** |
||
3801 | * @return bool |
||
3802 | * @since 1.22 |
||
3803 | */ |
||
3804 | public function isTOCEnabled() { |
||
3805 | return $this->mEnableTOC; |
||
3806 | } |
||
3807 | |||
3808 | /** |
||
3809 | * Enables/disables section edit links, doesn't override __NOEDITSECTION__ |
||
3810 | * @param bool $flag |
||
3811 | * @since 1.23 |
||
3812 | */ |
||
3813 | public function enableSectionEditLinks( $flag = true ) { |
||
3814 | $this->mEnableSectionEditLinks = $flag; |
||
3815 | } |
||
3816 | |||
3817 | /** |
||
3818 | * @return bool |
||
3819 | * @since 1.23 |
||
3820 | */ |
||
3821 | public function sectionEditLinksEnabled() { |
||
3822 | return $this->mEnableSectionEditLinks; |
||
3823 | } |
||
3824 | |||
3825 | /** |
||
3826 | * Helper function to setup the PHP implementation of OOUI to use in this request. |
||
3827 | * |
||
3828 | * @since 1.26 |
||
3829 | * @param String $skinName The Skin name to determine the correct OOUI theme |
||
3830 | * @param String $dir Language direction |
||
3831 | */ |
||
3832 | public static function setupOOUI( $skinName = '', $dir = 'ltr' ) { |
||
3833 | $themes = ExtensionRegistry::getInstance()->getAttribute( 'SkinOOUIThemes' ); |
||
3834 | // Make keys (skin names) lowercase for case-insensitive matching. |
||
3835 | $themes = array_change_key_case( $themes, CASE_LOWER ); |
||
3836 | $theme = isset( $themes[$skinName] ) ? $themes[$skinName] : 'MediaWiki'; |
||
3837 | // For example, 'OOUI\MediaWikiTheme'. |
||
3838 | $themeClass = "OOUI\\{$theme}Theme"; |
||
3839 | OOUI\Theme::setSingleton( new $themeClass() ); |
||
3840 | OOUI\Element::setDefaultDir( $dir ); |
||
3841 | } |
||
3842 | |||
3843 | /** |
||
3844 | * Add ResourceLoader module styles for OOUI and set up the PHP implementation of it for use with |
||
3845 | * MediaWiki and this OutputPage instance. |
||
3846 | * |
||
3847 | * @since 1.25 |
||
3848 | */ |
||
3849 | public function enableOOUI() { |
||
3850 | self::setupOOUI( |
||
3851 | strtolower( $this->getSkin()->getSkinName() ), |
||
3852 | $this->getLanguage()->getDir() |
||
3853 | ); |
||
3854 | $this->addModuleStyles( [ |
||
3855 | 'oojs-ui-core.styles', |
||
3856 | 'oojs-ui.styles.icons', |
||
3857 | 'oojs-ui.styles.indicators', |
||
3858 | 'oojs-ui.styles.textures', |
||
3859 | 'mediawiki.widgets.styles', |
||
3860 | ] ); |
||
3861 | } |
||
3862 | } |
||
3863 |
This check looks for assignments to scalar types that may be of the wrong type.
To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.