2007-01-20 12:50:56 +00:00
|
|
|
|
<?php
|
2023-10-04 03:11:44 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
|
*
|
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
|
*
|
|
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
|
|
|
|
*
|
|
|
|
|
|
* @file
|
|
|
|
|
|
*/
|
2012-08-20 14:55:28 +00:00
|
|
|
|
|
2023-12-14 19:20:33 +00:00
|
|
|
|
namespace MediaWiki\Parser;
|
|
|
|
|
|
|
|
|
|
|
|
use InvalidArgumentException;
|
|
|
|
|
|
use LogicException;
|
2023-11-06 15:22:44 +00:00
|
|
|
|
use MediaWiki\Edit\ParsoidRenderID;
|
2024-05-04 08:58:57 +00:00
|
|
|
|
use MediaWiki\Json\JsonDeserializable;
|
|
|
|
|
|
use MediaWiki\Json\JsonDeserializableTrait;
|
|
|
|
|
|
use MediaWiki\Json\JsonDeserializer;
|
2022-04-10 15:34:45 +00:00
|
|
|
|
use MediaWiki\MainConfigNames;
|
2021-10-12 09:39:32 +00:00
|
|
|
|
use MediaWiki\MediaWikiServices;
|
2024-04-02 21:56:24 +00:00
|
|
|
|
use MediaWiki\Message\Converter;
|
2023-09-05 17:31:53 +00:00
|
|
|
|
use MediaWiki\Output\OutputPage;
|
2023-10-20 13:21:53 +00:00
|
|
|
|
use MediaWiki\Parser\Parsoid\PageBundleParserOutputConverter;
|
2023-12-18 19:33:54 +00:00
|
|
|
|
use MediaWiki\Title\TitleValue;
|
2023-12-14 19:20:33 +00:00
|
|
|
|
use UnexpectedValueException;
|
Add ParserOutput::getLanguage()
Provide a way for backend code to determine the primary language of a
ParserOutput, eg for setting the Content-Language header of an API
response.
This is read-only and backed by extension data at the moment for
transition purposes; if this API sticks we'll graduate it to a
"real" property in the future, with appropriate serialization
to/from JSON (T303329).
Similarly, this patch only includes the most basic code to handle
the various ParserOutput merge cases in
ParserOutput::merge{Internal,Html,Tracking}MetaDataFrom(),
ParserOutput::collectMetadata(), and
OutputPage::addParserOutput{Content,Metadata,Text,}(); mostly
inherited from the fact that the storage is backed by extension
data at the moment.
Generally only the "top-level" parser output gets to set the
primary language; we'll presumably need to ensure that the
language is consistent during merge.
Change-Id: I767daba22805a877d9b806fd77334e508902844b
2022-09-28 21:21:13 +00:00
|
|
|
|
use Wikimedia\Bcp47Code\Bcp47Code;
|
|
|
|
|
|
use Wikimedia\Bcp47Code\Bcp47CodeValue;
|
2024-04-02 21:56:24 +00:00
|
|
|
|
use Wikimedia\Message\MessageValue;
|
2022-02-07 16:28:23 +00:00
|
|
|
|
use Wikimedia\Parsoid\Core\ContentMetadataCollector;
|
2022-02-11 23:07:47 +00:00
|
|
|
|
use Wikimedia\Parsoid\Core\ContentMetadataCollectorCompat;
|
2023-12-07 15:59:28 +00:00
|
|
|
|
use Wikimedia\Parsoid\Core\LinkTarget as ParsoidLinkTarget;
|
2022-09-01 23:07:29 +00:00
|
|
|
|
use Wikimedia\Parsoid\Core\TOCData;
|
2020-11-05 17:04:37 +00:00
|
|
|
|
use Wikimedia\Reflection\GhostFieldAccessTrait;
|
2020-09-23 15:30:40 +00:00
|
|
|
|
|
2010-08-22 14:31:05 +00:00
|
|
|
|
/**
|
2024-02-15 15:44:04 +00:00
|
|
|
|
* ParserOutput is a rendering of a Content object or a message.
|
|
|
|
|
|
* Content objects and messages often contain wikitext, but not always.
|
2010-08-22 14:31:05 +00:00
|
|
|
|
*
|
2024-02-15 15:44:04 +00:00
|
|
|
|
* `ParserOutput` object combine the HTML rendering of Content objects
|
|
|
|
|
|
* or messages, available via `::getRawText()`, with various bits of
|
|
|
|
|
|
* metadata generated during rendering, which may include categories,
|
|
|
|
|
|
* links, page properties, and extension data, among others.
|
2012-04-30 09:22:16 +00:00
|
|
|
|
*
|
2024-02-15 15:44:04 +00:00
|
|
|
|
* `ParserOutput` objects corresponding to the content of page revisions
|
|
|
|
|
|
* are created by the `ParserOutputAccess` service, which
|
|
|
|
|
|
* automatically caches them via `ParserCache` where appropriate and
|
|
|
|
|
|
* produces new output via `ContentHandler` as needed.
|
|
|
|
|
|
*
|
|
|
|
|
|
* In addition, wikitext from system messages as well as odd bits of
|
|
|
|
|
|
* wikitext rendered to create special pages and other UX elements are
|
|
|
|
|
|
* rendered to `ParserOutput` objects. In these cases the metadata
|
|
|
|
|
|
* from the `ParserOutput` is generally discarded and the
|
|
|
|
|
|
* `ParserOutput` is not cached. These bits of wikitext are generally
|
|
|
|
|
|
* rendered with `ParserOptions::setInterfaceMessage(true)` when
|
|
|
|
|
|
* content is intended to be in the user interface language, but
|
|
|
|
|
|
* sometimes rendered to the content language and displayed in the
|
|
|
|
|
|
* content area instead.
|
|
|
|
|
|
*
|
|
|
|
|
|
* A `ParserOutput` object corresponding to a given revision may be a
|
|
|
|
|
|
* combination of the renderings of multiple "slots":
|
|
|
|
|
|
* the Multi-Content Revisions (MCR) work allows articles to be
|
|
|
|
|
|
* composed from multiple `Content` objects. Each `Content` renders
|
|
|
|
|
|
* to a `ParserOutput`, and those `ParserOutput`s are merged by
|
|
|
|
|
|
* `RevisionRenderer::combineSlotOutput()` to create the final article
|
|
|
|
|
|
* output.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Similarly, `OutputPage` maintains metadata overlapping
|
|
|
|
|
|
* with the metadata kept by `ParserOutput` (T301020) and may merge
|
|
|
|
|
|
* several `ParserOutput`s using `OutputPage::addParserOutput()` to
|
|
|
|
|
|
* create the final output page. Parsoid parses certain transclusions
|
|
|
|
|
|
* in independent top-level contexts using
|
|
|
|
|
|
* `Parser::parseExtensionTagAsTopLevelDoc()` and these also result in
|
|
|
|
|
|
* `ParserOutput`s which are merged via
|
|
|
|
|
|
* `ParserOutput::collectMetadata()`.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Future plans for incremental parsing and asynchronous rendering may
|
|
|
|
|
|
* result in several of these component `ParserOutput` objects being
|
|
|
|
|
|
* cached independently and then recombined asynchronously, so
|
|
|
|
|
|
* operations on `ParserOutput` objects should be compatible with that
|
|
|
|
|
|
* model (T300979).
|
2012-04-30 09:22:16 +00:00
|
|
|
|
*
|
2010-08-22 14:31:05 +00:00
|
|
|
|
* @ingroup Parser
|
|
|
|
|
|
*/
|
2022-02-11 23:07:47 +00:00
|
|
|
|
class ParserOutput extends CacheTime implements ContentMetadataCollector {
|
2020-11-05 17:04:37 +00:00
|
|
|
|
use GhostFieldAccessTrait;
|
2024-05-04 08:58:57 +00:00
|
|
|
|
use JsonDeserializableTrait;
|
2022-02-11 23:07:47 +00:00
|
|
|
|
// This is used to break cyclic dependencies and allow a measure
|
|
|
|
|
|
// of compatibility when new methods are added to ContentMetadataCollector
|
|
|
|
|
|
// by Parsoid.
|
|
|
|
|
|
use ContentMetadataCollectorCompat;
|
2020-11-05 17:04:37 +00:00
|
|
|
|
|
2017-11-22 20:07:51 +00:00
|
|
|
|
/**
|
2017-12-22 18:32:49 +00:00
|
|
|
|
* Feature flags to indicate to extensions that MediaWiki core supports and
|
2017-11-22 20:07:51 +00:00
|
|
|
|
* uses getText() stateless transforms.
|
2019-06-20 14:33:00 +00:00
|
|
|
|
*
|
|
|
|
|
|
* @since 1.31
|
2017-11-22 20:07:51 +00:00
|
|
|
|
*/
|
2020-05-15 22:40:17 +00:00
|
|
|
|
public const SUPPORTS_STATELESS_TRANSFORMS = 1;
|
2019-06-20 14:33:00 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @since 1.31
|
|
|
|
|
|
*/
|
2020-05-15 22:40:17 +00:00
|
|
|
|
public const SUPPORTS_UNWRAP_TRANSFORM = 1;
|
2017-11-22 20:07:51 +00:00
|
|
|
|
|
2022-01-27 19:44:35 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @internal
|
|
|
|
|
|
* @since 1.38
|
|
|
|
|
|
*/
|
2022-02-07 16:28:23 +00:00
|
|
|
|
public const MW_MERGE_STRATEGY_KEY = '_mw-strategy';
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Merge strategy to use for ParserOutput accumulators: "union"
|
|
|
|
|
|
* means that values are strings, stored as a set, and exposed as
|
|
|
|
|
|
* a PHP associative array mapping from values to `true`.
|
|
|
|
|
|
*
|
|
|
|
|
|
* This constant should be treated as @internal until we expose
|
|
|
|
|
|
* alternative merge strategies for external use.
|
|
|
|
|
|
* @internal
|
|
|
|
|
|
* @since 1.38
|
|
|
|
|
|
*/
|
|
|
|
|
|
public const MW_MERGE_STRATEGY_UNION = 'union';
|
2022-01-27 19:44:35 +00:00
|
|
|
|
|
2015-09-27 13:12:36 +00:00
|
|
|
|
/**
|
2020-04-07 21:38:17 +00:00
|
|
|
|
* @var string|null The output text
|
2015-09-27 13:12:36 +00:00
|
|
|
|
*/
|
2024-02-09 22:27:00 +00:00
|
|
|
|
private $mRawText = null;
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2024-06-14 03:54:52 +00:00
|
|
|
|
* @var array<string,string> Array mapping interwiki prefix to (non DB key) Titles (e.g. 'fr' => 'Test page')
|
2015-09-27 13:12:36 +00:00
|
|
|
|
*/
|
2024-06-14 03:54:52 +00:00
|
|
|
|
private $mLanguageLinkMap = [];
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2021-10-08 14:44:52 +00:00
|
|
|
|
* @var array<string,string> Map of category names to sort keys
|
2015-09-27 13:12:36 +00:00
|
|
|
|
*/
|
2020-11-10 01:47:09 +00:00
|
|
|
|
private $mCategories;
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2023-07-03 13:52:18 +00:00
|
|
|
|
* @var array<string,string> Page status indicators, usually displayed in top-right corner.
|
2015-09-27 13:12:36 +00:00
|
|
|
|
*/
|
2020-11-10 01:47:09 +00:00
|
|
|
|
private $mIndicators = [];
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2020-04-07 21:38:17 +00:00
|
|
|
|
* @var string Title text of the chosen language variant, as HTML.
|
2015-09-27 13:12:36 +00:00
|
|
|
|
*/
|
2020-11-10 01:47:09 +00:00
|
|
|
|
private $mTitleText;
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2023-07-03 13:52:18 +00:00
|
|
|
|
* @var array<int,array<string,int>> 2-D map of NS/DBK to ID for the links in the document.
|
2015-09-27 13:12:36 +00:00
|
|
|
|
* ID=zero for broken.
|
|
|
|
|
|
*/
|
2020-11-10 01:47:09 +00:00
|
|
|
|
private $mLinks = [];
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
preferences: Signature validation (lint errors, user links, nested subst)
Three new checks are now applied to user signatures in preferences:
* Disallow invalid HTML and lint errors (T140606)
Since 15e0e9bb4b we can rely on Parsoid to check the signature for
lint errors. (The old PHP Parser doesn't have this capability.)
Most importantly, this will disallow unclosed HTML tags. Unclosed
formatting tags like `<i>` (and also wikitext markup like `''`)
could affect the entire page with the bad markup.
New configuration variable $wgSignatureAllowedLintErrors is added
to allow ignoring some errors. The default value ignores the
'obsolete-tag' error (caused by HTML tags like `<font>` and `<tt>`.)
* Require a link to user page, talk page or contributions (T237700)
Various tools don't work correctly when such a link is missing. For
example, Echo notifications are not sent, DiscussionTools will not
allow replying to these comments, English Wikipedia's SineBot treats
these comments as unsigned.
Such requirement has been present for a long time in many Wikimedia
wikis' policies, but it was not enforced by software.
* Disallow "nested" substitution in signature (T230652)
Clever abuse of "subst" markup and tildes allows users to save edits
containing wikitext in which substitution occurs again when the page
is next saved. Disallow this in signatures, at least.
New configuration variable $wgSignatureValidation is added to control
what we do about the result of the validation described above. The
options are:
* 'warning':
Only displays a warning near the field on Special:Preferences if
the current signature is invalid. Signatures can still be changed
regardless of validity and will be used when signing comments.
* 'new':
In addition to the above, if a user tries to change their signature,
the new one must be valid. Existing invalid signatures are still
used when signing comments.
* 'disallow':
In addition to the above, existing invalid signatures are no longer
used when signing comments.
Bug: T140606
Bug: T237700
Bug: T230652
Change-Id: I07c575c2d9d2afe7a89c4847d16ac044417297bf
2019-11-09 00:15:51 +00:00
|
|
|
|
/**
|
2023-07-03 13:52:18 +00:00
|
|
|
|
* @var array<string,int> Keys are DBKs for the links to special pages in the document.
|
preferences: Signature validation (lint errors, user links, nested subst)
Three new checks are now applied to user signatures in preferences:
* Disallow invalid HTML and lint errors (T140606)
Since 15e0e9bb4b we can rely on Parsoid to check the signature for
lint errors. (The old PHP Parser doesn't have this capability.)
Most importantly, this will disallow unclosed HTML tags. Unclosed
formatting tags like `<i>` (and also wikitext markup like `''`)
could affect the entire page with the bad markup.
New configuration variable $wgSignatureAllowedLintErrors is added
to allow ignoring some errors. The default value ignores the
'obsolete-tag' error (caused by HTML tags like `<font>` and `<tt>`.)
* Require a link to user page, talk page or contributions (T237700)
Various tools don't work correctly when such a link is missing. For
example, Echo notifications are not sent, DiscussionTools will not
allow replying to these comments, English Wikipedia's SineBot treats
these comments as unsigned.
Such requirement has been present for a long time in many Wikimedia
wikis' policies, but it was not enforced by software.
* Disallow "nested" substitution in signature (T230652)
Clever abuse of "subst" markup and tildes allows users to save edits
containing wikitext in which substitution occurs again when the page
is next saved. Disallow this in signatures, at least.
New configuration variable $wgSignatureValidation is added to control
what we do about the result of the validation described above. The
options are:
* 'warning':
Only displays a warning near the field on Special:Preferences if
the current signature is invalid. Signatures can still be changed
regardless of validity and will be used when signing comments.
* 'new':
In addition to the above, if a user tries to change their signature,
the new one must be valid. Existing invalid signatures are still
used when signing comments.
* 'disallow':
In addition to the above, existing invalid signatures are no longer
used when signing comments.
Bug: T140606
Bug: T237700
Bug: T230652
Change-Id: I07c575c2d9d2afe7a89c4847d16ac044417297bf
2019-11-09 00:15:51 +00:00
|
|
|
|
* @since 1.35
|
|
|
|
|
|
*/
|
2020-11-10 01:47:09 +00:00
|
|
|
|
private $mLinksSpecial = [];
|
preferences: Signature validation (lint errors, user links, nested subst)
Three new checks are now applied to user signatures in preferences:
* Disallow invalid HTML and lint errors (T140606)
Since 15e0e9bb4b we can rely on Parsoid to check the signature for
lint errors. (The old PHP Parser doesn't have this capability.)
Most importantly, this will disallow unclosed HTML tags. Unclosed
formatting tags like `<i>` (and also wikitext markup like `''`)
could affect the entire page with the bad markup.
New configuration variable $wgSignatureAllowedLintErrors is added
to allow ignoring some errors. The default value ignores the
'obsolete-tag' error (caused by HTML tags like `<font>` and `<tt>`.)
* Require a link to user page, talk page or contributions (T237700)
Various tools don't work correctly when such a link is missing. For
example, Echo notifications are not sent, DiscussionTools will not
allow replying to these comments, English Wikipedia's SineBot treats
these comments as unsigned.
Such requirement has been present for a long time in many Wikimedia
wikis' policies, but it was not enforced by software.
* Disallow "nested" substitution in signature (T230652)
Clever abuse of "subst" markup and tildes allows users to save edits
containing wikitext in which substitution occurs again when the page
is next saved. Disallow this in signatures, at least.
New configuration variable $wgSignatureValidation is added to control
what we do about the result of the validation described above. The
options are:
* 'warning':
Only displays a warning near the field on Special:Preferences if
the current signature is invalid. Signatures can still be changed
regardless of validity and will be used when signing comments.
* 'new':
In addition to the above, if a user tries to change their signature,
the new one must be valid. Existing invalid signatures are still
used when signing comments.
* 'disallow':
In addition to the above, existing invalid signatures are no longer
used when signing comments.
Bug: T140606
Bug: T237700
Bug: T230652
Change-Id: I07c575c2d9d2afe7a89c4847d16ac044417297bf
2019-11-09 00:15:51 +00:00
|
|
|
|
|
2015-09-27 13:12:36 +00:00
|
|
|
|
/**
|
2023-07-03 13:52:18 +00:00
|
|
|
|
* @var array<int,array<string,int>> 2-D map of NS/DBK to ID for the template references.
|
2015-09-27 13:12:36 +00:00
|
|
|
|
* ID=zero for broken.
|
|
|
|
|
|
*/
|
2020-11-10 01:47:09 +00:00
|
|
|
|
private $mTemplates = [];
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2023-07-03 13:52:18 +00:00
|
|
|
|
* @var array<int,array<string,int>> 2-D map of NS/DBK to rev ID for the template references.
|
2015-09-27 13:12:36 +00:00
|
|
|
|
* ID=zero for broken.
|
|
|
|
|
|
*/
|
2020-11-10 01:47:09 +00:00
|
|
|
|
private $mTemplateIds = [];
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2023-07-03 13:52:18 +00:00
|
|
|
|
* @var array<string,int> DB keys of the images used, in the array key only
|
2015-09-27 13:12:36 +00:00
|
|
|
|
*/
|
2020-11-10 01:47:09 +00:00
|
|
|
|
private $mImages = [];
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2023-10-20 13:21:53 +00:00
|
|
|
|
* @var array<string,array<string,string>> DB keys of the images used mapped to sha1 and MW timestamp.
|
2015-09-27 13:12:36 +00:00
|
|
|
|
*/
|
2020-11-10 01:47:09 +00:00
|
|
|
|
private $mFileSearchOptions = [];
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2023-07-03 13:52:18 +00:00
|
|
|
|
* @var array<string,int> External link URLs, in the key only.
|
2015-09-27 13:12:36 +00:00
|
|
|
|
*/
|
2024-10-08 07:51:23 +00:00
|
|
|
|
private array $mExternalLinks = [];
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2023-07-03 13:52:18 +00:00
|
|
|
|
* @var array<string,array<string,int>> 2-D map of prefix/DBK (in keys only)
|
2015-09-27 13:12:36 +00:00
|
|
|
|
* for the inline interwiki links in the document.
|
|
|
|
|
|
*/
|
2020-11-10 01:47:09 +00:00
|
|
|
|
private $mInterwikiLinks = [];
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2020-04-07 21:38:17 +00:00
|
|
|
|
* @var bool Show a new section link?
|
2015-09-27 13:12:36 +00:00
|
|
|
|
*/
|
2020-11-10 01:47:09 +00:00
|
|
|
|
private $mNewSection = false;
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2020-04-07 21:38:17 +00:00
|
|
|
|
* @var bool Hide the new section link?
|
2015-09-27 13:12:36 +00:00
|
|
|
|
*/
|
2020-11-10 01:47:09 +00:00
|
|
|
|
private $mHideNewSection = false;
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2020-04-07 21:38:17 +00:00
|
|
|
|
* @var bool No gallery on category page? (__NOGALLERY__).
|
2015-09-27 13:12:36 +00:00
|
|
|
|
*/
|
2020-11-10 01:47:09 +00:00
|
|
|
|
private $mNoGallery = false;
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2022-09-27 07:22:15 +00:00
|
|
|
|
* @var string[] Items to put in the <head> section
|
2015-09-27 13:12:36 +00:00
|
|
|
|
*/
|
2020-11-10 01:47:09 +00:00
|
|
|
|
private $mHeadItems = [];
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2023-11-22 19:33:05 +00:00
|
|
|
|
* @var array<string,true> Modules to be loaded by ResourceLoader
|
2015-09-27 13:12:36 +00:00
|
|
|
|
*/
|
2023-11-22 19:33:05 +00:00
|
|
|
|
private $mModuleSet = [];
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2023-11-22 19:33:05 +00:00
|
|
|
|
* @var array<string,true> Modules of which only the CSS will be loaded by ResourceLoader.
|
2015-09-27 13:12:36 +00:00
|
|
|
|
*/
|
2023-11-22 19:33:05 +00:00
|
|
|
|
private $mModuleStyleSet = [];
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2020-04-07 21:38:17 +00:00
|
|
|
|
* @var array JavaScript config variable for mw.config combined with this page.
|
2015-09-27 13:12:36 +00:00
|
|
|
|
*/
|
2020-11-10 01:47:09 +00:00
|
|
|
|
private $mJsConfigVars = [];
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2023-07-03 13:52:18 +00:00
|
|
|
|
* @var array<string,int> Warning text to be returned to the user.
|
2015-09-27 13:12:36 +00:00
|
|
|
|
* Wikitext formatted, in the key only.
|
|
|
|
|
|
*/
|
2020-11-10 01:47:09 +00:00
|
|
|
|
private $mWarnings = [];
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
2022-02-07 16:28:23 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @var array<string,array> *Unformatted* warning messages and
|
|
|
|
|
|
* arguments to be returned to the user. This is for internal use
|
|
|
|
|
|
* when merging ParserOutputs and are not serialized/deserialized.
|
|
|
|
|
|
*/
|
|
|
|
|
|
private $mWarningMsgs = [];
|
|
|
|
|
|
|
2015-09-27 13:12:36 +00:00
|
|
|
|
/**
|
2022-09-01 23:07:29 +00:00
|
|
|
|
* @var ?TOCData Table of contents data, or null if it hasn't been set.
|
2015-09-27 13:12:36 +00:00
|
|
|
|
*/
|
2022-09-01 23:07:29 +00:00
|
|
|
|
private $mTOCData;
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2020-04-07 21:38:17 +00:00
|
|
|
|
* @var array Name/value pairs to be cached in the DB.
|
2015-09-27 13:12:36 +00:00
|
|
|
|
*/
|
2020-11-10 01:47:09 +00:00
|
|
|
|
private $mProperties = [];
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2023-11-06 21:25:07 +00:00
|
|
|
|
* @var ?string Timestamp of the revision.
|
2015-09-27 13:12:36 +00:00
|
|
|
|
*/
|
2020-11-10 01:47:09 +00:00
|
|
|
|
private $mTimestamp;
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2020-04-07 21:38:17 +00:00
|
|
|
|
* @var bool Whether OOUI should be enabled.
|
2015-09-27 13:12:36 +00:00
|
|
|
|
*/
|
2020-11-10 01:47:09 +00:00
|
|
|
|
private $mEnableOOUI = false;
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2022-03-30 16:45:00 +00:00
|
|
|
|
* @var bool Whether the index policy has been set to 'index'.
|
2015-09-27 13:12:36 +00:00
|
|
|
|
*/
|
2022-03-30 16:45:00 +00:00
|
|
|
|
private $mIndexSet = false;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @var bool Whether the index policy has been set to 'noindex'.
|
|
|
|
|
|
*/
|
|
|
|
|
|
private $mNoIndexSet = false;
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2020-04-07 21:38:17 +00:00
|
|
|
|
* @var array extra data used by extensions.
|
2015-09-27 13:12:36 +00:00
|
|
|
|
*/
|
2016-02-17 09:09:32 +00:00
|
|
|
|
private $mExtensionData = [];
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
2016-11-08 21:03:21 +00:00
|
|
|
|
/**
|
2020-04-07 21:38:17 +00:00
|
|
|
|
* @var array Parser limit report data.
|
2016-11-08 21:03:21 +00:00
|
|
|
|
*/
|
2016-02-17 09:09:32 +00:00
|
|
|
|
private $mLimitReportData = [];
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
2016-11-10 20:29:27 +00:00
|
|
|
|
/** @var array Parser limit report data for JSON */
|
|
|
|
|
|
private $mLimitReportJSData = [];
|
|
|
|
|
|
|
2021-11-09 16:31:27 +00:00
|
|
|
|
/** @var string Debug message added by ParserCache */
|
|
|
|
|
|
private $mCacheMessage = '';
|
|
|
|
|
|
|
2015-09-27 13:12:36 +00:00
|
|
|
|
/**
|
2020-04-07 21:38:17 +00:00
|
|
|
|
* @var array Timestamps for getTimeSinceStart().
|
2015-09-27 13:12:36 +00:00
|
|
|
|
*/
|
2016-02-17 09:09:32 +00:00
|
|
|
|
private $mParseStartTime = [];
|
2015-09-27 13:12:36 +00:00
|
|
|
|
|
2023-09-28 15:03:33 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @var array Durations for getTimeProfile().
|
|
|
|
|
|
*/
|
|
|
|
|
|
private $mTimeProfile = [];
|
|
|
|
|
|
|
2015-09-27 13:12:36 +00:00
|
|
|
|
/**
|
2020-04-07 21:38:17 +00:00
|
|
|
|
* @var bool Whether to emit X-Frame-Options: DENY.
|
2022-02-09 21:33:39 +00:00
|
|
|
|
* This controls if anti-clickjacking / frame-breaking headers will
|
|
|
|
|
|
* be sent. This should be done for pages where edit actions are possible.
|
2015-09-27 13:12:36 +00:00
|
|
|
|
*/
|
|
|
|
|
|
private $mPreventClickjacking = false;
|
|
|
|
|
|
|
2020-02-03 09:50:14 +00:00
|
|
|
|
/**
|
2022-09-27 07:22:15 +00:00
|
|
|
|
* @var string[] Extra script-src for CSP
|
2020-02-03 09:50:14 +00:00
|
|
|
|
*/
|
|
|
|
|
|
private $mExtraScriptSrcs = [];
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2022-09-27 07:22:15 +00:00
|
|
|
|
* @var string[] Extra default-src for CSP [Everything but script and style]
|
2020-02-03 09:50:14 +00:00
|
|
|
|
*/
|
|
|
|
|
|
private $mExtraDefaultSrcs = [];
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2022-09-27 07:22:15 +00:00
|
|
|
|
* @var string[] Extra style-src for CSP
|
2020-02-03 09:50:14 +00:00
|
|
|
|
*/
|
|
|
|
|
|
private $mExtraStyleSrcs = [];
|
|
|
|
|
|
|
2015-09-27 13:12:36 +00:00
|
|
|
|
/**
|
2023-07-03 13:52:18 +00:00
|
|
|
|
* @var array<string,true> Generic flags.
|
2015-09-27 13:12:36 +00:00
|
|
|
|
*/
|
2016-02-17 09:09:32 +00:00
|
|
|
|
private $mFlags = [];
|
2008-02-20 08:53:12 +00:00
|
|
|
|
|
2019-10-16 01:24:50 +00:00
|
|
|
|
private const SPECULATIVE_FIELDS = [
|
2019-06-27 04:30:35 +00:00
|
|
|
|
'speculativePageIdUsed',
|
2019-07-31 02:16:07 +00:00
|
|
|
|
'mSpeculativeRevId',
|
2022-05-17 15:13:33 +00:00
|
|
|
|
'revisionTimestampUsed',
|
2019-06-27 04:30:35 +00:00
|
|
|
|
];
|
2019-07-26 06:39:16 +00:00
|
|
|
|
|
2017-08-20 11:20:59 +00:00
|
|
|
|
/** @var int|null Assumed rev ID for {{REVISIONID}} if no revision is set */
|
2019-07-31 02:16:07 +00:00
|
|
|
|
private $mSpeculativeRevId;
|
2019-06-27 04:30:35 +00:00
|
|
|
|
/** @var int|null Assumed page ID for {{PAGEID}} if no revision is set */
|
|
|
|
|
|
private $speculativePageIdUsed;
|
2022-03-11 16:41:01 +00:00
|
|
|
|
/** @var string|null Assumed rev timestamp for {{REVISIONTIMESTAMP}} if no revision is set */
|
2019-04-19 01:36:40 +00:00
|
|
|
|
private $revisionTimestampUsed;
|
|
|
|
|
|
|
2019-07-04 10:01:31 +00:00
|
|
|
|
/** @var string|null SHA-1 base 36 hash of any self-transclusion */
|
|
|
|
|
|
private $revisionUsedSha1Base36;
|
|
|
|
|
|
|
2018-08-28 16:48:10 +00:00
|
|
|
|
/** string CSS classes to use for the wrapping div, stored in the array keys.
|
|
|
|
|
|
* If no class is given, no wrapper is added.
|
2024-09-07 20:46:08 +00:00
|
|
|
|
* @var array<string,true>
|
2018-08-28 16:48:10 +00:00
|
|
|
|
*/
|
|
|
|
|
|
private $mWrapperDivClasses = [];
|
|
|
|
|
|
|
2017-08-20 11:20:59 +00:00
|
|
|
|
/** @var int Upper bound of expiry based on parse duration */
|
2016-08-30 19:35:08 +00:00
|
|
|
|
private $mMaxAdaptiveExpiry = INF;
|
|
|
|
|
|
|
|
|
|
|
|
// finalizeAdaptiveCacheExpiry() uses TTL = MAX( m * PARSE_TIME + b, MIN_AR_TTL)
|
|
|
|
|
|
// Current values imply that m=3933.333333 and b=-333.333333
|
|
|
|
|
|
// See https://www.nngroup.com/articles/website-response-times/
|
2020-05-15 22:40:17 +00:00
|
|
|
|
private const PARSE_FAST_SEC = 0.100; // perceived "fast" page parse
|
|
|
|
|
|
private const PARSE_SLOW_SEC = 1.0; // perceived "slow" page parse
|
|
|
|
|
|
private const FAST_AR_TTL = 60; // adaptive TTL for "fast" pages
|
|
|
|
|
|
private const SLOW_AR_TTL = 3600; // adaptive TTL for "slow" pages
|
2021-11-19 23:19:42 +00:00
|
|
|
|
private const MIN_AR_TTL = 15; // min adaptive TTL (for pool counter, and edit stashing)
|
2016-08-30 19:35:08 +00:00
|
|
|
|
|
2018-08-13 20:33:31 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @param string|null $text HTML. Use null to indicate that this ParserOutput contains only
|
|
|
|
|
|
* meta-data, and the HTML output is undetermined, as opposed to empty. Passing null
|
2022-04-22 05:47:40 +00:00
|
|
|
|
* here causes hasText() to return false. In 1.39 the default value changed from ''
|
|
|
|
|
|
* to null.
|
2018-08-13 20:33:31 +00:00
|
|
|
|
* @param array $languageLinks
|
|
|
|
|
|
* @param array $categoryLinks
|
|
|
|
|
|
* @param bool $unused
|
|
|
|
|
|
* @param string $titletext
|
|
|
|
|
|
*/
|
2022-04-22 05:47:40 +00:00
|
|
|
|
public function __construct( $text = null, $languageLinks = [], $categoryLinks = [],
|
2015-02-15 22:41:49 +00:00
|
|
|
|
$unused = false, $titletext = ''
|
2013-12-01 20:39:00 +00:00
|
|
|
|
) {
|
2024-02-09 22:27:00 +00:00
|
|
|
|
$this->mRawText = $text;
|
2007-01-20 12:50:56 +00:00
|
|
|
|
$this->mCategories = $categoryLinks;
|
|
|
|
|
|
$this->mTitleText = $titletext;
|
2024-10-02 18:53:34 +00:00
|
|
|
|
if ( $languageLinks === null ) { // T376323
|
|
|
|
|
|
wfDeprecated( __METHOD__ . ' with null $languageLinks', '1.43' );
|
|
|
|
|
|
}
|
|
|
|
|
|
foreach ( ( $languageLinks ?? [] ) as $ll ) {
|
2024-06-14 03:54:52 +00:00
|
|
|
|
$this->addLanguageLink( $ll );
|
|
|
|
|
|
}
|
2024-10-03 22:56:04 +00:00
|
|
|
|
// If the content handler does not specify an alternative (by
|
|
|
|
|
|
// calling ::resetParseStartTime() at a later point) then use
|
|
|
|
|
|
// the creation of the ParserOutput as the "start of parse" time.
|
|
|
|
|
|
$this->resetParseStartTime();
|
2007-01-20 12:50:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-08-13 20:33:31 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Returns true if text was passed to the constructor, or set using setText(). Returns false
|
|
|
|
|
|
* if null was passed to the $text parameter of the constructor to indicate that this
|
|
|
|
|
|
* ParserOutput only contains meta-data, and the HTML output is undetermined.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @since 1.32
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return bool Whether this ParserOutput contains rendered text. If this returns false, the
|
|
|
|
|
|
* ParserOutput contains meta-data only.
|
|
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function hasText(): bool {
|
2024-02-09 22:27:00 +00:00
|
|
|
|
return ( $this->mRawText !== null );
|
2018-08-13 20:33:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-01-25 03:44:36 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Get the cacheable text with <mw:editsection> markers still in it. The
|
|
|
|
|
|
* return value is suitable for writing back via setText() but is not valid
|
|
|
|
|
|
* for display to the user.
|
|
|
|
|
|
*
|
2017-09-09 20:47:04 +00:00
|
|
|
|
* @return string
|
2016-01-25 03:44:36 +00:00
|
|
|
|
* @since 1.27
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function getRawText() {
|
2024-02-09 22:27:00 +00:00
|
|
|
|
if ( $this->mRawText === null ) {
|
2018-08-13 20:33:31 +00:00
|
|
|
|
throw new LogicException( 'This ParserOutput contains no text!' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-02-09 22:27:00 +00:00
|
|
|
|
return $this->mRawText;
|
2016-01-25 03:44:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-11-22 18:12:23 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Get the output HTML
|
|
|
|
|
|
*
|
2024-08-19 07:17:23 +00:00
|
|
|
|
* T293512: in the future, ParserOutput::getText() will be deprecated in favor of invoking
|
|
|
|
|
|
* the OutputTransformPipeline directly on a ParserOutput.
|
2017-11-22 18:12:23 +00:00
|
|
|
|
* @param array $options (since 1.31) Transformations to apply to the HTML
|
2024-09-02 07:37:35 +00:00
|
|
|
|
* - allowClone: (bool) Whether to clone the ParserOutput before
|
|
|
|
|
|
* applying transformations. Default is false.
|
2017-11-22 18:12:23 +00:00
|
|
|
|
* - allowTOC: (bool) Show the TOC, assuming there were enough headings
|
|
|
|
|
|
* to generate one and `__NOTOC__` wasn't used. Default is true,
|
|
|
|
|
|
* but might be statefully overridden.
|
2021-09-15 01:00:06 +00:00
|
|
|
|
* - injectTOC: (bool) Replace the TOC_PLACEHOLDER with TOC contents;
|
|
|
|
|
|
* otherwise the marker will be left in the article (and the skin
|
|
|
|
|
|
* will be responsible for replacing or removing it). Default is
|
|
|
|
|
|
* true.
|
2017-11-22 18:12:23 +00:00
|
|
|
|
* - enableSectionEditLinks: (bool) Include section edit links, assuming
|
2017-12-22 18:32:49 +00:00
|
|
|
|
* section edit link tokens are present in the HTML. Default is true,
|
2017-11-22 18:12:23 +00:00
|
|
|
|
* but might be statefully overridden.
|
2023-01-19 20:37:16 +00:00
|
|
|
|
* - userLang: (Language) Language object used for localizing UX messages,
|
|
|
|
|
|
* for example the heading of the table of contents. If omitted, will
|
|
|
|
|
|
* use the language of the main request context.
|
2019-10-08 18:23:54 +00:00
|
|
|
|
* - skin: (Skin) Skin object used for transforming section edit links.
|
2018-08-28 16:48:10 +00:00
|
|
|
|
* - unwrap: (bool) Return text without a wrapper div. Default is false,
|
|
|
|
|
|
* meaning a wrapper div will be added if getWrapperDivClass() returns
|
|
|
|
|
|
* a non-empty string.
|
|
|
|
|
|
* - wrapperDivClass: (string) Wrap the output in a div and apply the given
|
|
|
|
|
|
* CSS class to that div. This overrides the output of getWrapperDivClass().
|
|
|
|
|
|
* Setting this to an empty string has the same effect as 'unwrap' => true.
|
2017-11-24 19:22:25 +00:00
|
|
|
|
* - deduplicateStyles: (bool) When true, which is the default, `<style>`
|
|
|
|
|
|
* tags with the `data-mw-deduplicate` attribute set are deduplicated by
|
|
|
|
|
|
* value of the attribute: all but the first will be replaced by `<link
|
|
|
|
|
|
* rel="mw-deduplicated-inline-style" href="mw-data:..."/>` tags, where
|
|
|
|
|
|
* the scheme-specific-part of the href is the (percent-encoded) value
|
|
|
|
|
|
* of the `data-mw-deduplicate` attribute.
|
2021-09-21 14:34:30 +00:00
|
|
|
|
* - absoluteURLs: (bool) use absolute URLs in all links. Default: false
|
2021-11-09 16:31:27 +00:00
|
|
|
|
* - includeDebugInfo: (bool) render PP limit report in HTML. Default: false
|
2017-11-22 18:12:23 +00:00
|
|
|
|
* @return string HTML
|
2018-08-31 15:55:44 +00:00
|
|
|
|
* @return-taint escaped
|
2023-12-12 15:25:03 +00:00
|
|
|
|
* @deprecated since 1.42, this method has side-effects on the ParserOutput
|
|
|
|
|
|
* (see T353257) and so should be avoided in favor of directly invoking
|
2024-08-19 07:17:23 +00:00
|
|
|
|
* the default output pipeline on a ParserOutput; for now, use of
|
|
|
|
|
|
* ::runOutputPipeline() is preferred to ensure that ParserOptions are
|
|
|
|
|
|
* available.
|
2017-11-22 18:12:23 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function getText( $options = [] ) {
|
2024-02-09 22:27:00 +00:00
|
|
|
|
$oldText = $this->mRawText; // T353257
|
2024-09-02 07:37:35 +00:00
|
|
|
|
$options += [ 'allowClone' => false ];
|
|
|
|
|
|
$po = $this->runPipelineInternal( null, $options );
|
|
|
|
|
|
$newText = $po->getContentHolderText();
|
|
|
|
|
|
// T353257: for back-compat only mutations to metadata performed by
|
|
|
|
|
|
// the pipeline should be preserved; mutations to $mText should be
|
|
|
|
|
|
// discarded.
|
|
|
|
|
|
$this->setRawText( $oldText );
|
|
|
|
|
|
return $newText;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @unstable This method is transitional and will be replaced by a method
|
|
|
|
|
|
* in another class, maybe ContentRenderer. It allows us to break our
|
|
|
|
|
|
* porting work into two steps; in the first we bring ParserOptions to
|
|
|
|
|
|
* to each ::getText() callsite to ensure it is made available to the
|
|
|
|
|
|
* postprocessing pipeline. In the second we move this functionality
|
|
|
|
|
|
* into the Content hierarchy and out of ParserOutput, which should become
|
|
|
|
|
|
* a pure value object.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param ParserOptions $popts
|
|
|
|
|
|
* @param array $options (since 1.31) Transformations to apply to the HTML
|
|
|
|
|
|
* - allowClone: (bool) Whether to clone the ParserOutput before
|
|
|
|
|
|
* applying transformations. Default is true.
|
|
|
|
|
|
* - allowTOC: (bool) Show the TOC, assuming there were enough headings
|
|
|
|
|
|
* to generate one and `__NOTOC__` wasn't used. Default is true,
|
|
|
|
|
|
* but might be statefully overridden.
|
|
|
|
|
|
* - injectTOC: (bool) Replace the TOC_PLACEHOLDER with TOC contents;
|
|
|
|
|
|
* otherwise the marker will be left in the article (and the skin
|
|
|
|
|
|
* will be responsible for replacing or removing it). Default is
|
|
|
|
|
|
* true.
|
|
|
|
|
|
* - enableSectionEditLinks: (bool) Include section edit links, assuming
|
|
|
|
|
|
* section edit link tokens are present in the HTML. Default is true,
|
|
|
|
|
|
* but might be statefully overridden.
|
|
|
|
|
|
* - userLang: (Language) Language object used for localizing UX messages,
|
|
|
|
|
|
* for example the heading of the table of contents. If omitted, will
|
|
|
|
|
|
* use the language of the main request context.
|
|
|
|
|
|
* - skin: (Skin) Skin object used for transforming section edit links.
|
|
|
|
|
|
* - unwrap: (bool) Return text without a wrapper div. Default is false,
|
|
|
|
|
|
* meaning a wrapper div will be added if getWrapperDivClass() returns
|
|
|
|
|
|
* a non-empty string.
|
|
|
|
|
|
* - wrapperDivClass: (string) Wrap the output in a div and apply the given
|
|
|
|
|
|
* CSS class to that div. This overrides the output of getWrapperDivClass().
|
|
|
|
|
|
* Setting this to an empty string has the same effect as 'unwrap' => true.
|
|
|
|
|
|
* - deduplicateStyles: (bool) When true, which is the default, `<style>`
|
|
|
|
|
|
* tags with the `data-mw-deduplicate` attribute set are deduplicated by
|
|
|
|
|
|
* value of the attribute: all but the first will be replaced by `<link
|
|
|
|
|
|
* rel="mw-deduplicated-inline-style" href="mw-data:..."/>` tags, where
|
|
|
|
|
|
* the scheme-specific-part of the href is the (percent-encoded) value
|
|
|
|
|
|
* of the `data-mw-deduplicate` attribute.
|
|
|
|
|
|
* - absoluteURLs: (bool) use absolute URLs in all links. Default: false
|
|
|
|
|
|
* - includeDebugInfo: (bool) render PP limit report in HTML. Default: false
|
|
|
|
|
|
* It is planned to eventually deprecate this $options array and to be able to
|
|
|
|
|
|
* pass its content in the $popts ParserOptions.
|
|
|
|
|
|
* @return ParserOutput
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function runOutputPipeline( ParserOptions $popts, array $options = [] ): ParserOutput {
|
|
|
|
|
|
return $this->runPipelineInternal( $popts, $options );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Temporary helper method to allow running the pipeline with null $popts for now, although
|
|
|
|
|
|
* passing a null ParserOptions is a temporary backward-compatibility hack and will be deprecated.
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function runPipelineInternal( ?ParserOptions $popts, array $options = [] ): ParserOutput {
|
|
|
|
|
|
$pipeline = MediaWikiServices::getInstance()->getDefaultOutputPipeline();
|
2023-10-20 13:21:53 +00:00
|
|
|
|
$options += [
|
2024-09-02 07:37:35 +00:00
|
|
|
|
'allowClone' => true,
|
2023-10-20 13:21:53 +00:00
|
|
|
|
'allowTOC' => true,
|
|
|
|
|
|
'injectTOC' => true,
|
2023-11-06 20:43:31 +00:00
|
|
|
|
'enableSectionEditLinks' => !$this->getOutputFlag( ParserOutputFlags::NO_SECTION_EDIT_LINKS ),
|
2023-10-20 13:21:53 +00:00
|
|
|
|
'userLang' => null,
|
|
|
|
|
|
'skin' => null,
|
|
|
|
|
|
'unwrap' => false,
|
|
|
|
|
|
'wrapperDivClass' => $this->getWrapperDivClass(),
|
|
|
|
|
|
'deduplicateStyles' => true,
|
|
|
|
|
|
'absoluteURLs' => false,
|
|
|
|
|
|
'includeDebugInfo' => false,
|
|
|
|
|
|
'isParsoidContent' => PageBundleParserOutputConverter::hasPageBundle( $this ),
|
|
|
|
|
|
];
|
2024-10-21 16:34:09 +00:00
|
|
|
|
return $pipeline->run( $this, $popts, $options );
|
2011-01-03 20:17:20 +00:00
|
|
|
|
}
|
2011-08-01 15:40:02 +00:00
|
|
|
|
|
2020-09-23 21:31:14 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Adds a comment notice about cache state to the text of the page
|
|
|
|
|
|
* @param string $msg
|
|
|
|
|
|
* @internal used by ParserCache
|
|
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function addCacheMessage( string $msg ): void {
|
2021-11-09 16:31:27 +00:00
|
|
|
|
$this->mCacheMessage .= $msg;
|
2020-09-23 21:31:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-08-28 16:48:10 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Add a CSS class to use for the wrapping div. If no class is given, no wrapper is added.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param string $class
|
|
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function addWrapperDivClass( $class ): void {
|
2018-08-28 16:48:10 +00:00
|
|
|
|
$this->mWrapperDivClasses[$class] = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Clears the CSS class to use for the wrapping div, effectively disabling the wrapper div
|
|
|
|
|
|
* until addWrapperDivClass() is called.
|
|
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function clearWrapperDivClass(): void {
|
2018-08-28 16:48:10 +00:00
|
|
|
|
$this->mWrapperDivClasses = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2022-05-09 09:09:00 +00:00
|
|
|
|
* Returns the class (or classes) to be used with the wrapper div for this output.
|
2018-08-28 16:48:10 +00:00
|
|
|
|
* If there is no wrapper class given, no wrapper div should be added.
|
|
|
|
|
|
* The wrapper div is added automatically by getText().
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return string
|
|
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function getWrapperDivClass(): string {
|
2018-08-28 16:48:10 +00:00
|
|
|
|
return implode( ' ', array_keys( $this->mWrapperDivClasses ) );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-06-19 05:30:21 +00:00
|
|
|
|
/**
|
2017-08-20 11:20:59 +00:00
|
|
|
|
* @param int $id
|
2016-06-19 05:30:21 +00:00
|
|
|
|
* @since 1.28
|
|
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function setSpeculativeRevIdUsed( $id ): void {
|
2019-07-31 02:16:07 +00:00
|
|
|
|
$this->mSpeculativeRevId = $id;
|
2016-06-19 05:30:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-09-09 20:47:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @return int|null
|
|
|
|
|
|
* @since 1.28
|
|
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function getSpeculativeRevIdUsed(): ?int {
|
2019-07-31 02:16:07 +00:00
|
|
|
|
return $this->mSpeculativeRevId;
|
2019-06-27 04:30:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @param int $id
|
|
|
|
|
|
* @since 1.34
|
|
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function setSpeculativePageIdUsed( $id ): void {
|
2019-06-27 04:30:35 +00:00
|
|
|
|
$this->speculativePageIdUsed = $id;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @return int|null
|
|
|
|
|
|
* @since 1.34
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function getSpeculativePageIdUsed() {
|
|
|
|
|
|
return $this->speculativePageIdUsed;
|
2016-06-19 05:30:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-04-19 01:36:40 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @param string $timestamp TS_MW timestamp
|
|
|
|
|
|
* @since 1.34
|
|
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function setRevisionTimestampUsed( $timestamp ): void {
|
2019-04-19 01:36:40 +00:00
|
|
|
|
$this->revisionTimestampUsed = $timestamp;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @return string|null TS_MW timestamp or null if not used
|
|
|
|
|
|
* @since 1.34
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function getRevisionTimestampUsed() {
|
|
|
|
|
|
return $this->revisionTimestampUsed;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-07-04 10:01:31 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @param string $hash Lowercase SHA-1 base 36 hash
|
|
|
|
|
|
* @since 1.34
|
|
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function setRevisionUsedSha1Base36( $hash ): void {
|
2019-07-04 10:01:31 +00:00
|
|
|
|
if ( $hash === null ) {
|
|
|
|
|
|
return; // e.g. RevisionRecord::getSha1() returned null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
|
$this->revisionUsedSha1Base36 !== null &&
|
|
|
|
|
|
$this->revisionUsedSha1Base36 !== $hash
|
|
|
|
|
|
) {
|
|
|
|
|
|
$this->revisionUsedSha1Base36 = ''; // mismatched
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$this->revisionUsedSha1Base36 = $hash;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @return string|null Lowercase SHA-1 base 36 hash, null if unused, or "" on inconsistency
|
|
|
|
|
|
* @since 1.34
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function getRevisionUsedSha1Base36() {
|
|
|
|
|
|
return $this->revisionUsedSha1Base36;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-06-14 03:54:52 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @return string[]
|
|
|
|
|
|
* @note Before 1.43, this function returned an array reference.
|
2024-08-30 23:03:08 +00:00
|
|
|
|
* @deprecated since 1.43, use ::getLinkList(ParserOutputLinkTypes::LANGUAGE)
|
2024-06-14 03:54:52 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function getLanguageLinks() {
|
|
|
|
|
|
$result = [];
|
|
|
|
|
|
foreach ( $this->mLanguageLinkMap as $lang => $title ) {
|
|
|
|
|
|
// T374736: Back-compat with empty prefix; see ::addLanguageLink()
|
|
|
|
|
|
$result[] = $title === '|' ? "$lang" : "$lang:$title";
|
|
|
|
|
|
}
|
|
|
|
|
|
return $result;
|
2014-05-10 23:03:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-08-30 23:03:08 +00:00
|
|
|
|
/** @deprecated since 1.43, use ::getLinkList(ParserOutputLinkTypes::INTERWIKI) */
|
2014-08-11 20:24:54 +00:00
|
|
|
|
public function getInterwikiLinks() {
|
2014-05-10 23:03:45 +00:00
|
|
|
|
return $this->mInterwikiLinks;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-08 14:44:52 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Return the names of the categories on this page.
|
|
|
|
|
|
* Unlike ::getCategories(), sort keys are *not* included in the
|
|
|
|
|
|
* return value.
|
|
|
|
|
|
* @return array<string> The names of the categories
|
|
|
|
|
|
* @since 1.38
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function getCategoryNames(): array {
|
2023-03-09 16:18:07 +00:00
|
|
|
|
# Note that numeric category names get converted to 'int' when
|
|
|
|
|
|
# stored as array keys; stringify the keys to ensure they
|
|
|
|
|
|
# return to original string form so as not to confuse callers.
|
|
|
|
|
|
return array_map( 'strval', array_keys( $this->mCategories ) );
|
2014-05-10 23:03:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-09-21 17:06:50 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Return category names and sort keys as a map.
|
|
|
|
|
|
*
|
|
|
|
|
|
* BEWARE that numeric category names get converted to 'int' when stored
|
|
|
|
|
|
* as array keys. Because of this, use of this method is not recommended
|
|
|
|
|
|
* in new code; using ::getCategoryNames() and ::getCategorySortKey() will
|
|
|
|
|
|
* be less error-prone.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return array<string|int,string>
|
|
|
|
|
|
* @internal
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function getCategoryMap(): array {
|
2014-05-10 23:03:45 +00:00
|
|
|
|
return $this->mCategories;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-03-10 14:32:36 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Return the sort key for a given category name, or `null` if the
|
2023-12-15 14:57:45 +00:00
|
|
|
|
* category is not present in this ParserOutput. Returns the
|
|
|
|
|
|
* empty string if the category is to use the default sort key.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @note The effective sort key in the database may vary from what
|
|
|
|
|
|
* is returned here; see note in ParserOutput::addCategory().
|
|
|
|
|
|
*
|
2023-03-10 14:32:36 +00:00
|
|
|
|
* @param string $name The category name
|
|
|
|
|
|
* @return ?string The sort key for the category, or `null` if the
|
|
|
|
|
|
* category is not present in this ParserOutput
|
|
|
|
|
|
* @since 1.40
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function getCategorySortKey( string $name ): ?string {
|
|
|
|
|
|
// This API avoids exposing the fact that numeric string category
|
|
|
|
|
|
// names are going to be converted to 'int' when used as array
|
|
|
|
|
|
// keys for the `mCategories` field.
|
|
|
|
|
|
return $this->mCategories[$name] ?? null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Implement page status indicators
Page status indicators are icons (or short text snippets) usually
displayed in the top-right corner of the page, outside of the main
content. Basically, <indicator name="foo">[[File:Foo.svg|20px]]</indicator>
may be used on a page to place the icon in the indicator area. They
are also known as top icons, page icons, heading icons or title icons.
I found the discussion on bug 23796 highly illuminating. I suggest
that everyone read it before suggesting different design choices.
I spent some time with a thesaurus pondering the name. "Emblems" and
"badges" were also considered, but the former has a much more limited
meaning and the latter is already taken by Wikidata, with a similar
but subtly different feature set. I am not aware of any naming
conflicts ;) besides new talk page message "indicator" (used by core
and Echo in some documents) and OOjs UI indicators (tiny icons like
the arrow on a dropdown form element), which shouldn't be confusing.
Potential use cases include:
* "Lock" indicators for page protection levels
* Featured/good article indicators
* Redirect shortcuts display ("WP:VPT")
* Links to help/manual for special pages
* Coordinates?… or globe icon for inline pop-up maps
Design features:
* Skin-customizable. Skins can fully control where and how indicators
are shown, or may just do <?php echo $this->getIndicators(); ?> to
output the default structure. By default they are not shown at all.
* Extension-customizable. Extensions can call ParserOutput::addIndicator()
to insert an indicator from one of the numerous parser hooks.
* Wiki-customizable. In addition to just using the parser functions,
on-wiki styles and scripts can use the provided classes and ids
(.mw-indicator, #mw-indicator-<name>) to customize their display.
Design limitations:
* Every indicator must have a unique identifier (name). It's not
possible to create arrays, or to have several indicators with the
same name. In case of duplicates, the latest occurrence of the
parser function wins.
* Indicators are displayed ordered by their names (and not occurrence
order). This ensures consistency across pages and provides a simple
means of ordering or grouping them.
* Indicators are not stored, tracked or accessible outside of
ParserOutput (in particular they're not in the page_props table).
They are intended to merely reflect the content or metadata that is
already present on the page, and not be data themselves. If you ever
think you need to list pages with a given status indicator, instead
figure out what it means and use the appropriate tracking category,
special page report, already existing page_prop, or other means.
Corresponding patch in Vector: I90a8ae15ac8275d084ea5f47b6b2684d5e6c7412.
I'll implement support in the other three skins included in the tarball
and document it on mediawiki.org after this is merged.
Bug: 23796
Change-Id: I2389ff9a5332a2b1d033eb75f0946e5241cfaaf4
2014-09-24 10:44:16 +00:00
|
|
|
|
/**
|
2022-09-27 07:22:15 +00:00
|
|
|
|
* @return string[]
|
Implement page status indicators
Page status indicators are icons (or short text snippets) usually
displayed in the top-right corner of the page, outside of the main
content. Basically, <indicator name="foo">[[File:Foo.svg|20px]]</indicator>
may be used on a page to place the icon in the indicator area. They
are also known as top icons, page icons, heading icons or title icons.
I found the discussion on bug 23796 highly illuminating. I suggest
that everyone read it before suggesting different design choices.
I spent some time with a thesaurus pondering the name. "Emblems" and
"badges" were also considered, but the former has a much more limited
meaning and the latter is already taken by Wikidata, with a similar
but subtly different feature set. I am not aware of any naming
conflicts ;) besides new talk page message "indicator" (used by core
and Echo in some documents) and OOjs UI indicators (tiny icons like
the arrow on a dropdown form element), which shouldn't be confusing.
Potential use cases include:
* "Lock" indicators for page protection levels
* Featured/good article indicators
* Redirect shortcuts display ("WP:VPT")
* Links to help/manual for special pages
* Coordinates?… or globe icon for inline pop-up maps
Design features:
* Skin-customizable. Skins can fully control where and how indicators
are shown, or may just do <?php echo $this->getIndicators(); ?> to
output the default structure. By default they are not shown at all.
* Extension-customizable. Extensions can call ParserOutput::addIndicator()
to insert an indicator from one of the numerous parser hooks.
* Wiki-customizable. In addition to just using the parser functions,
on-wiki styles and scripts can use the provided classes and ids
(.mw-indicator, #mw-indicator-<name>) to customize their display.
Design limitations:
* Every indicator must have a unique identifier (name). It's not
possible to create arrays, or to have several indicators with the
same name. In case of duplicates, the latest occurrence of the
parser function wins.
* Indicators are displayed ordered by their names (and not occurrence
order). This ensures consistency across pages and provides a simple
means of ordering or grouping them.
* Indicators are not stored, tracked or accessible outside of
ParserOutput (in particular they're not in the page_props table).
They are intended to merely reflect the content or metadata that is
already present on the page, and not be data themselves. If you ever
think you need to list pages with a given status indicator, instead
figure out what it means and use the appropriate tracking category,
special page report, already existing page_prop, or other means.
Corresponding patch in Vector: I90a8ae15ac8275d084ea5f47b6b2684d5e6c7412.
I'll implement support in the other three skins included in the tarball
and document it on mediawiki.org after this is merged.
Bug: 23796
Change-Id: I2389ff9a5332a2b1d033eb75f0946e5241cfaaf4
2014-09-24 10:44:16 +00:00
|
|
|
|
* @since 1.25
|
|
|
|
|
|
*/
|
2023-05-27 06:08:56 +00:00
|
|
|
|
public function getIndicators(): array {
|
Implement page status indicators
Page status indicators are icons (or short text snippets) usually
displayed in the top-right corner of the page, outside of the main
content. Basically, <indicator name="foo">[[File:Foo.svg|20px]]</indicator>
may be used on a page to place the icon in the indicator area. They
are also known as top icons, page icons, heading icons or title icons.
I found the discussion on bug 23796 highly illuminating. I suggest
that everyone read it before suggesting different design choices.
I spent some time with a thesaurus pondering the name. "Emblems" and
"badges" were also considered, but the former has a much more limited
meaning and the latter is already taken by Wikidata, with a similar
but subtly different feature set. I am not aware of any naming
conflicts ;) besides new talk page message "indicator" (used by core
and Echo in some documents) and OOjs UI indicators (tiny icons like
the arrow on a dropdown form element), which shouldn't be confusing.
Potential use cases include:
* "Lock" indicators for page protection levels
* Featured/good article indicators
* Redirect shortcuts display ("WP:VPT")
* Links to help/manual for special pages
* Coordinates?… or globe icon for inline pop-up maps
Design features:
* Skin-customizable. Skins can fully control where and how indicators
are shown, or may just do <?php echo $this->getIndicators(); ?> to
output the default structure. By default they are not shown at all.
* Extension-customizable. Extensions can call ParserOutput::addIndicator()
to insert an indicator from one of the numerous parser hooks.
* Wiki-customizable. In addition to just using the parser functions,
on-wiki styles and scripts can use the provided classes and ids
(.mw-indicator, #mw-indicator-<name>) to customize their display.
Design limitations:
* Every indicator must have a unique identifier (name). It's not
possible to create arrays, or to have several indicators with the
same name. In case of duplicates, the latest occurrence of the
parser function wins.
* Indicators are displayed ordered by their names (and not occurrence
order). This ensures consistency across pages and provides a simple
means of ordering or grouping them.
* Indicators are not stored, tracked or accessible outside of
ParserOutput (in particular they're not in the page_props table).
They are intended to merely reflect the content or metadata that is
already present on the page, and not be data themselves. If you ever
think you need to list pages with a given status indicator, instead
figure out what it means and use the appropriate tracking category,
special page report, already existing page_prop, or other means.
Corresponding patch in Vector: I90a8ae15ac8275d084ea5f47b6b2684d5e6c7412.
I'll implement support in the other three skins included in the tarball
and document it on mediawiki.org after this is merged.
Bug: 23796
Change-Id: I2389ff9a5332a2b1d033eb75f0946e5241cfaaf4
2014-09-24 10:44:16 +00:00
|
|
|
|
return $this->mIndicators;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-08-11 20:24:54 +00:00
|
|
|
|
public function getTitleText() {
|
2014-05-10 23:03:45 +00:00
|
|
|
|
return $this->mTitleText;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-15 01:00:06 +00:00
|
|
|
|
/**
|
2022-09-01 23:07:29 +00:00
|
|
|
|
* @return ?TOCData the table of contents data, or null if it hasn't been
|
|
|
|
|
|
* set.
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function getTOCData(): ?TOCData {
|
|
|
|
|
|
return $this->mTOCData;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-10-17 11:47:01 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @internal
|
|
|
|
|
|
* @return string
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function getCacheMessage(): string {
|
|
|
|
|
|
return $this->mCacheMessage;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-09-01 23:07:29 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @internal
|
2021-09-15 01:00:06 +00:00
|
|
|
|
* @return array
|
|
|
|
|
|
*/
|
2022-09-01 23:07:29 +00:00
|
|
|
|
public function getSections(): array {
|
|
|
|
|
|
if ( $this->mTOCData !== null ) {
|
|
|
|
|
|
return $this->mTOCData->toLegacy();
|
|
|
|
|
|
}
|
|
|
|
|
|
// For compatibility
|
|
|
|
|
|
return [];
|
2014-05-10 23:03:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-08-30 23:03:08 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Get a list of links of the given type.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Provides a uniform interface to various lists of links stored in
|
|
|
|
|
|
* the metadata.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Each element of the returned array has a LinkTarget as the 'link'
|
|
|
|
|
|
* property. Local and template links also have 'pageid' set.
|
|
|
|
|
|
* Template links have 'revid' set. Category links have 'sort' set.
|
|
|
|
|
|
* Media links optionally have 'time' and 'sha1' set.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param string $linkType A link type, which should be a constant from
|
|
|
|
|
|
* ParserOutputLinkTypes.
|
|
|
|
|
|
* @return list<array{link:ParsoidLinkTarget,pageid?:int,revid?:int,sort?:string,time?:string|false,sha1?:string|false}>
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function getLinkList( string $linkType ): array {
|
|
|
|
|
|
# Note that fragments are dropped for everything except language links
|
|
|
|
|
|
$result = [];
|
|
|
|
|
|
switch ( $linkType ) {
|
|
|
|
|
|
case ParserOutputLinkTypes::CATEGORY:
|
|
|
|
|
|
foreach ( $this->mCategories as $dbkey => $sort ) {
|
|
|
|
|
|
$result[] = [
|
|
|
|
|
|
'link' => new TitleValue( NS_CATEGORY, (string)$dbkey ),
|
|
|
|
|
|
'sort' => $sort,
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case ParserOutputLinkTypes::INTERWIKI:
|
|
|
|
|
|
foreach ( $this->mInterwikiLinks as $prefix => $arr ) {
|
|
|
|
|
|
foreach ( $arr as $dbkey => $ignore ) {
|
|
|
|
|
|
$result[] = [
|
|
|
|
|
|
'link' => new TitleValue( NS_MAIN, (string)$dbkey, '', (string)$prefix ),
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case ParserOutputLinkTypes::LANGUAGE:
|
|
|
|
|
|
foreach ( $this->mLanguageLinkMap as $lang => $title ) {
|
|
|
|
|
|
if ( $title === '|' ) {
|
|
|
|
|
|
continue; // T374736
|
|
|
|
|
|
}
|
|
|
|
|
|
# language links can have fragments!
|
|
|
|
|
|
[ $title, $frag ] = array_pad( explode( '#', $title, 2 ), 2, '' );
|
|
|
|
|
|
$result[] = [
|
|
|
|
|
|
'link' => new TitleValue( NS_MAIN, $title, $frag, (string)$lang ),
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case ParserOutputLinkTypes::LOCAL:
|
|
|
|
|
|
foreach ( $this->mLinks as $ns => $arr ) {
|
|
|
|
|
|
foreach ( $arr as $dbkey => $id ) {
|
|
|
|
|
|
$result[] = [
|
|
|
|
|
|
'link' => new TitleValue( $ns, (string)$dbkey ),
|
|
|
|
|
|
'pageid' => $id,
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case ParserOutputLinkTypes::MEDIA:
|
|
|
|
|
|
foreach ( $this->mImages as $dbkey => $ignore ) {
|
|
|
|
|
|
$extra = $this->mFileSearchOptions[$dbkey] ?? [];
|
|
|
|
|
|
$extra['link'] = new TitleValue( NS_FILE, (string)$dbkey );
|
|
|
|
|
|
$result[] = $extra;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case ParserOutputLinkTypes::SPECIAL:
|
|
|
|
|
|
foreach ( $this->mLinksSpecial as $dbkey => $ignore ) {
|
|
|
|
|
|
$result[] = [
|
|
|
|
|
|
'link' => new TitleValue( NS_SPECIAL, (string)$dbkey ),
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case ParserOutputLinkTypes::TEMPLATE:
|
|
|
|
|
|
foreach ( $this->mTemplates as $ns => $arr ) {
|
|
|
|
|
|
foreach ( $arr as $dbkey => $pageid ) {
|
|
|
|
|
|
$result[] = [
|
|
|
|
|
|
'link' => new TitleValue( $ns, (string)$dbkey ),
|
|
|
|
|
|
'pageid' => $pageid,
|
|
|
|
|
|
'revid' => $this->mTemplateIds[$ns][$dbkey],
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw new UnexpectedValueException( "Unknown link type $linkType" );
|
|
|
|
|
|
}
|
|
|
|
|
|
return $result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @deprecated since 1.43, use ::getLinkList(ParserOutputLinkTypes::LOCAL) */
|
2014-08-11 20:24:54 +00:00
|
|
|
|
public function &getLinks() {
|
2014-05-10 23:03:45 +00:00
|
|
|
|
return $this->mLinks;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
preferences: Signature validation (lint errors, user links, nested subst)
Three new checks are now applied to user signatures in preferences:
* Disallow invalid HTML and lint errors (T140606)
Since 15e0e9bb4b we can rely on Parsoid to check the signature for
lint errors. (The old PHP Parser doesn't have this capability.)
Most importantly, this will disallow unclosed HTML tags. Unclosed
formatting tags like `<i>` (and also wikitext markup like `''`)
could affect the entire page with the bad markup.
New configuration variable $wgSignatureAllowedLintErrors is added
to allow ignoring some errors. The default value ignores the
'obsolete-tag' error (caused by HTML tags like `<font>` and `<tt>`.)
* Require a link to user page, talk page or contributions (T237700)
Various tools don't work correctly when such a link is missing. For
example, Echo notifications are not sent, DiscussionTools will not
allow replying to these comments, English Wikipedia's SineBot treats
these comments as unsigned.
Such requirement has been present for a long time in many Wikimedia
wikis' policies, but it was not enforced by software.
* Disallow "nested" substitution in signature (T230652)
Clever abuse of "subst" markup and tildes allows users to save edits
containing wikitext in which substitution occurs again when the page
is next saved. Disallow this in signatures, at least.
New configuration variable $wgSignatureValidation is added to control
what we do about the result of the validation described above. The
options are:
* 'warning':
Only displays a warning near the field on Special:Preferences if
the current signature is invalid. Signatures can still be changed
regardless of validity and will be used when signing comments.
* 'new':
In addition to the above, if a user tries to change their signature,
the new one must be valid. Existing invalid signatures are still
used when signing comments.
* 'disallow':
In addition to the above, existing invalid signatures are no longer
used when signing comments.
Bug: T140606
Bug: T237700
Bug: T230652
Change-Id: I07c575c2d9d2afe7a89c4847d16ac044417297bf
2019-11-09 00:15:51 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @return array Keys are DBKs for the links to special pages in the document
|
|
|
|
|
|
* @since 1.35
|
2024-08-30 23:03:08 +00:00
|
|
|
|
* @deprecated since 1.43, use ::getLinkList(ParserOutputLinkTypes::SPECIAL)
|
preferences: Signature validation (lint errors, user links, nested subst)
Three new checks are now applied to user signatures in preferences:
* Disallow invalid HTML and lint errors (T140606)
Since 15e0e9bb4b we can rely on Parsoid to check the signature for
lint errors. (The old PHP Parser doesn't have this capability.)
Most importantly, this will disallow unclosed HTML tags. Unclosed
formatting tags like `<i>` (and also wikitext markup like `''`)
could affect the entire page with the bad markup.
New configuration variable $wgSignatureAllowedLintErrors is added
to allow ignoring some errors. The default value ignores the
'obsolete-tag' error (caused by HTML tags like `<font>` and `<tt>`.)
* Require a link to user page, talk page or contributions (T237700)
Various tools don't work correctly when such a link is missing. For
example, Echo notifications are not sent, DiscussionTools will not
allow replying to these comments, English Wikipedia's SineBot treats
these comments as unsigned.
Such requirement has been present for a long time in many Wikimedia
wikis' policies, but it was not enforced by software.
* Disallow "nested" substitution in signature (T230652)
Clever abuse of "subst" markup and tildes allows users to save edits
containing wikitext in which substitution occurs again when the page
is next saved. Disallow this in signatures, at least.
New configuration variable $wgSignatureValidation is added to control
what we do about the result of the validation described above. The
options are:
* 'warning':
Only displays a warning near the field on Special:Preferences if
the current signature is invalid. Signatures can still be changed
regardless of validity and will be used when signing comments.
* 'new':
In addition to the above, if a user tries to change their signature,
the new one must be valid. Existing invalid signatures are still
used when signing comments.
* 'disallow':
In addition to the above, existing invalid signatures are no longer
used when signing comments.
Bug: T140606
Bug: T237700
Bug: T230652
Change-Id: I07c575c2d9d2afe7a89c4847d16ac044417297bf
2019-11-09 00:15:51 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function &getLinksSpecial() {
|
|
|
|
|
|
return $this->mLinksSpecial;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-08-30 23:03:08 +00:00
|
|
|
|
/** @deprecated since 1.43, use ::getLinkList(ParserOutputLinkTypes::TEMPLATE) */
|
2014-08-11 20:24:54 +00:00
|
|
|
|
public function &getTemplates() {
|
2014-05-10 23:03:45 +00:00
|
|
|
|
return $this->mTemplates;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-08-30 23:03:08 +00:00
|
|
|
|
/** @deprecated since 1.43, use ::getLinkList(ParserOutputLinkTypes::TEMPLATE) */
|
2014-08-11 20:24:54 +00:00
|
|
|
|
public function &getTemplateIds() {
|
2014-05-10 23:03:45 +00:00
|
|
|
|
return $this->mTemplateIds;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-08-30 23:03:08 +00:00
|
|
|
|
/** @deprecated since 1.43, use ::getLinkList(ParserOutputLinkTypes::MEDIA) */
|
2014-08-11 20:24:54 +00:00
|
|
|
|
public function &getImages() {
|
2014-05-10 23:03:45 +00:00
|
|
|
|
return $this->mImages;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-08-30 23:03:08 +00:00
|
|
|
|
/** @deprecated since 1.43, use ::getLinkList(ParserOutputLinkTypes::MEDIA) */
|
2014-08-11 20:24:54 +00:00
|
|
|
|
public function &getFileSearchOptions() {
|
2014-05-10 23:03:45 +00:00
|
|
|
|
return $this->mFileSearchOptions;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-21 16:41:27 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @note Use of the reference returned by this method has been
|
|
|
|
|
|
* deprecated since 1.43. In a future release this will return a
|
|
|
|
|
|
* normal array. Use ::addExternalLink() to modify the set of
|
|
|
|
|
|
* external links stored in this ParserOutput.
|
|
|
|
|
|
*/
|
2024-10-08 07:51:23 +00:00
|
|
|
|
public function &getExternalLinks(): array {
|
2014-05-10 23:03:45 +00:00
|
|
|
|
return $this->mExternalLinks;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function setNoGallery( $value ): void {
|
2018-08-07 16:52:40 +00:00
|
|
|
|
$this->mNoGallery = (bool)$value;
|
|
|
|
|
|
}
|
2019-05-11 01:17:43 +00:00
|
|
|
|
|
2014-08-11 20:24:54 +00:00
|
|
|
|
public function getNoGallery() {
|
2014-05-10 23:03:45 +00:00
|
|
|
|
return $this->mNoGallery;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-08-11 20:24:54 +00:00
|
|
|
|
public function getHeadItems() {
|
2014-05-10 23:03:45 +00:00
|
|
|
|
return $this->mHeadItems;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-08-11 20:24:54 +00:00
|
|
|
|
public function getModules() {
|
2023-11-22 19:33:05 +00:00
|
|
|
|
return array_keys( $this->mModuleSet );
|
2014-05-10 23:03:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2014-08-11 20:24:54 +00:00
|
|
|
|
public function getModuleStyles() {
|
2023-11-22 19:33:05 +00:00
|
|
|
|
return array_keys( $this->mModuleStyleSet );
|
2014-05-10 23:03:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2017-09-09 20:47:04 +00:00
|
|
|
|
/**
|
2022-03-09 22:04:41 +00:00
|
|
|
|
* @param bool $showStrategyKeys Defaults to false; if set to true will
|
|
|
|
|
|
* expose the internal `MW_MERGE_STRATEGY_KEY` in the result. This
|
|
|
|
|
|
* should only be used internally to allow safe merge of config vars.
|
2017-09-09 20:47:04 +00:00
|
|
|
|
* @return array
|
|
|
|
|
|
* @since 1.23
|
|
|
|
|
|
*/
|
2022-03-09 22:04:41 +00:00
|
|
|
|
public function getJsConfigVars( bool $showStrategyKeys = false ) {
|
2022-01-27 19:44:35 +00:00
|
|
|
|
$result = $this->mJsConfigVars;
|
|
|
|
|
|
// Don't expose the internal strategy key
|
2022-09-21 19:05:03 +00:00
|
|
|
|
foreach ( $result as &$value ) {
|
2022-03-09 22:04:41 +00:00
|
|
|
|
if ( is_array( $value ) && !$showStrategyKeys ) {
|
2022-02-07 16:28:23 +00:00
|
|
|
|
unset( $value[self::MW_MERGE_STRATEGY_KEY] );
|
2022-01-27 19:44:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return $result;
|
2014-05-10 23:03:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function getWarnings(): array {
|
2014-05-10 23:03:45 +00:00
|
|
|
|
return array_keys( $this->mWarnings );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function getIndexPolicy(): string {
|
2022-03-30 16:45:00 +00:00
|
|
|
|
// 'noindex' wins if both are set. (T16899)
|
|
|
|
|
|
if ( $this->mNoIndexSet ) {
|
|
|
|
|
|
return 'noindex';
|
|
|
|
|
|
} elseif ( $this->mIndexSet ) {
|
|
|
|
|
|
return 'index';
|
|
|
|
|
|
}
|
|
|
|
|
|
return '';
|
2014-05-10 23:03:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-04-27 22:43:38 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @return string|null TS_MW timestamp of the revision content
|
|
|
|
|
|
*/
|
2023-11-06 21:25:07 +00:00
|
|
|
|
public function getRevisionTimestamp(): ?string {
|
2014-05-10 23:03:45 +00:00
|
|
|
|
return $this->mTimestamp;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-11-06 21:25:07 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @return string|null TS_MW timestamp of the revision content
|
|
|
|
|
|
* @deprecated since 1.42; use ::getRevisionTimestamp() instead
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function getTimestamp() {
|
|
|
|
|
|
return $this->getRevisionTimestamp();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-08-11 20:24:54 +00:00
|
|
|
|
public function getLimitReportData() {
|
2014-05-10 23:03:45 +00:00
|
|
|
|
return $this->mLimitReportData;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-11-10 20:29:27 +00:00
|
|
|
|
public function getLimitReportJSData() {
|
|
|
|
|
|
return $this->mLimitReportJSData;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-07-25 15:32:08 +00:00
|
|
|
|
public function getEnableOOUI() {
|
|
|
|
|
|
return $this->mEnableOOUI;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-02-03 09:50:14 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Get extra Content-Security-Policy 'default-src' directives
|
|
|
|
|
|
* @since 1.35
|
2022-09-27 07:22:15 +00:00
|
|
|
|
* @return string[]
|
2020-02-03 09:50:14 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function getExtraCSPDefaultSrcs() {
|
|
|
|
|
|
return $this->mExtraDefaultSrcs;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Get extra Content-Security-Policy 'script-src' directives
|
|
|
|
|
|
* @since 1.35
|
2022-09-27 07:22:15 +00:00
|
|
|
|
* @return string[]
|
2020-02-03 09:50:14 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function getExtraCSPScriptSrcs() {
|
|
|
|
|
|
return $this->mExtraScriptSrcs;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Get extra Content-Security-Policy 'style-src' directives
|
|
|
|
|
|
* @since 1.35
|
2022-09-27 07:22:15 +00:00
|
|
|
|
* @return string[]
|
2020-02-03 09:50:14 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function getExtraCSPStyleSrcs() {
|
|
|
|
|
|
return $this->mExtraStyleSrcs;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-04-22 05:47:40 +00:00
|
|
|
|
/**
|
2024-02-09 22:27:00 +00:00
|
|
|
|
* Set the raw text of the ParserOutput.
|
|
|
|
|
|
*
|
|
|
|
|
|
* If you did not generate html, pass null to mark it as such.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @since 1.42
|
|
|
|
|
|
* @param string|null $text HTML content of ParserOutput or null if not generated
|
|
|
|
|
|
* @param-taint $text exec_html
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function setRawText( ?string $text ): void {
|
|
|
|
|
|
$this->mRawText = $text;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Set the raw text of the ParserOutput.
|
2022-04-22 05:47:40 +00:00
|
|
|
|
*
|
|
|
|
|
|
* If you did not generate html, pass null to mark it as such.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @since 1.39 You can now pass null to this function
|
|
|
|
|
|
* @param string|null $text HTML content of ParserOutput or null if not generated
|
2023-10-04 19:28:02 +00:00
|
|
|
|
* @param-taint $text exec_html
|
2024-02-09 22:27:00 +00:00
|
|
|
|
* @return string|null Previous value of ParserOutput's raw text
|
|
|
|
|
|
* @deprecated since 1.42; use ::setRawText() which matches the getter ::getRawText()
|
2022-04-22 05:47:40 +00:00
|
|
|
|
*/
|
2014-08-11 20:24:54 +00:00
|
|
|
|
public function setText( $text ) {
|
2024-02-09 22:27:00 +00:00
|
|
|
|
return wfSetVar( $this->mRawText, $text, true );
|
2014-05-10 23:03:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-10-26 20:04:31 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @deprecated since 1.42, use ::addLanguageLink() instead.
|
|
|
|
|
|
*/
|
2014-08-11 20:24:54 +00:00
|
|
|
|
public function setLanguageLinks( $ll ) {
|
2024-06-14 03:54:52 +00:00
|
|
|
|
$old = $this->getLanguageLinks();
|
|
|
|
|
|
$this->mLanguageLinkMap = [];
|
2024-10-02 18:53:34 +00:00
|
|
|
|
if ( $ll === null ) { // T376323
|
|
|
|
|
|
wfDeprecated( __METHOD__ . ' with null argument', '1.43' );
|
|
|
|
|
|
}
|
|
|
|
|
|
foreach ( ( $ll ?? [] ) as $l ) {
|
2024-06-14 03:54:52 +00:00
|
|
|
|
$this->addLanguageLink( $l );
|
|
|
|
|
|
}
|
|
|
|
|
|
return $old;
|
2014-05-10 23:03:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2014-08-11 20:24:54 +00:00
|
|
|
|
public function setTitleText( $t ) {
|
2014-05-10 23:03:45 +00:00
|
|
|
|
return wfSetVar( $this->mTitleText, $t );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-09-01 23:07:29 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @param TOCData $tocData Table of contents data for the page
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function setTOCData( TOCData $tocData ): void {
|
|
|
|
|
|
$this->mTOCData = $tocData;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @param array $sectionArray
|
|
|
|
|
|
* @return array Previous value of ::getSections()
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function setSections( array $sectionArray ) {
|
|
|
|
|
|
$oldValue = $this->getSections();
|
|
|
|
|
|
$this->setTOCData( TOCData::fromLegacy( $sectionArray ) );
|
|
|
|
|
|
return $oldValue;
|
2014-05-10 23:03:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-18 14:38:03 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Update the index policy of the robots meta tag.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Note that calling this method does not guarantee
|
|
|
|
|
|
* that {@link self::getIndexPolicy()} will return the given policy –
|
|
|
|
|
|
* if different calls set the index policy to 'index' and 'noindex',
|
|
|
|
|
|
* then 'noindex' always wins (T16899), even if the 'index' call happened later.
|
|
|
|
|
|
* If this is not what you want,
|
|
|
|
|
|
* you can reset {@link ParserOutputFlags::NO_INDEX_POLICY} with {@link self::setOutputFlag()}.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param string $policy 'index' or 'noindex'.
|
|
|
|
|
|
* @return string The previous policy.
|
|
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function setIndexPolicy( $policy ): string {
|
2022-03-30 16:45:00 +00:00
|
|
|
|
$old = $this->getIndexPolicy();
|
|
|
|
|
|
if ( $policy === 'noindex' ) {
|
|
|
|
|
|
$this->mNoIndexSet = true;
|
|
|
|
|
|
} elseif ( $policy === 'index' ) {
|
|
|
|
|
|
$this->mIndexSet = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return $old;
|
2014-05-10 23:03:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-11-06 21:25:07 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @param ?string $timestamp TS_MW timestamp of the revision content
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function setRevisionTimestamp( ?string $timestamp ): void {
|
|
|
|
|
|
$this->mTimestamp = $timestamp;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @param ?string $timestamp TS_MW timestamp of the revision content
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return ?string The previous value of the timestamp
|
|
|
|
|
|
* @deprecated since 1.42; use ::setRevisionTimestamp() instead
|
|
|
|
|
|
*/
|
2014-08-11 20:24:54 +00:00
|
|
|
|
public function setTimestamp( $timestamp ) {
|
2014-05-10 23:03:45 +00:00
|
|
|
|
return wfSetVar( $this->mTimestamp, $timestamp );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-08 14:44:52 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Add a category.
|
2023-12-15 14:57:45 +00:00
|
|
|
|
*
|
|
|
|
|
|
* Although ParserOutput::getCategorySortKey() will return exactly
|
|
|
|
|
|
* the sort key you specify here, before storing in the database
|
|
|
|
|
|
* all sort keys will be language converted, HTML entities will be
|
|
|
|
|
|
* decoded, newlines stripped, and then they will be truncated to
|
|
|
|
|
|
* 255 bytes. Thus the "effective" sort key in the DB may be different
|
|
|
|
|
|
* from what is passed to `$sort` here and returned by
|
|
|
|
|
|
* ::getCategorySortKey().
|
|
|
|
|
|
*
|
2023-12-07 15:59:28 +00:00
|
|
|
|
* @param string|ParsoidLinkTarget $c The category name
|
2023-12-15 14:57:45 +00:00
|
|
|
|
* @param string $sort The sort key; an empty string indicates
|
|
|
|
|
|
* that the default sort key for the page should be used.
|
2021-10-08 14:44:52 +00:00
|
|
|
|
*/
|
2022-02-11 23:07:47 +00:00
|
|
|
|
public function addCategory( $c, $sort = '' ): void {
|
2023-12-07 15:59:28 +00:00
|
|
|
|
if ( $c instanceof ParsoidLinkTarget ) {
|
|
|
|
|
|
$c = $c->getDBkey();
|
|
|
|
|
|
}
|
2014-05-10 23:03:45 +00:00
|
|
|
|
$this->mCategories[$c] = $sort;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-08 14:44:52 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Overwrite the category map.
|
|
|
|
|
|
* @param array<string,string> $c Map of category names to sort keys
|
|
|
|
|
|
* @since 1.38
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function setCategories( array $c ): void {
|
|
|
|
|
|
$this->mCategories = $c;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Implement page status indicators
Page status indicators are icons (or short text snippets) usually
displayed in the top-right corner of the page, outside of the main
content. Basically, <indicator name="foo">[[File:Foo.svg|20px]]</indicator>
may be used on a page to place the icon in the indicator area. They
are also known as top icons, page icons, heading icons or title icons.
I found the discussion on bug 23796 highly illuminating. I suggest
that everyone read it before suggesting different design choices.
I spent some time with a thesaurus pondering the name. "Emblems" and
"badges" were also considered, but the former has a much more limited
meaning and the latter is already taken by Wikidata, with a similar
but subtly different feature set. I am not aware of any naming
conflicts ;) besides new talk page message "indicator" (used by core
and Echo in some documents) and OOjs UI indicators (tiny icons like
the arrow on a dropdown form element), which shouldn't be confusing.
Potential use cases include:
* "Lock" indicators for page protection levels
* Featured/good article indicators
* Redirect shortcuts display ("WP:VPT")
* Links to help/manual for special pages
* Coordinates?… or globe icon for inline pop-up maps
Design features:
* Skin-customizable. Skins can fully control where and how indicators
are shown, or may just do <?php echo $this->getIndicators(); ?> to
output the default structure. By default they are not shown at all.
* Extension-customizable. Extensions can call ParserOutput::addIndicator()
to insert an indicator from one of the numerous parser hooks.
* Wiki-customizable. In addition to just using the parser functions,
on-wiki styles and scripts can use the provided classes and ids
(.mw-indicator, #mw-indicator-<name>) to customize their display.
Design limitations:
* Every indicator must have a unique identifier (name). It's not
possible to create arrays, or to have several indicators with the
same name. In case of duplicates, the latest occurrence of the
parser function wins.
* Indicators are displayed ordered by their names (and not occurrence
order). This ensures consistency across pages and provides a simple
means of ordering or grouping them.
* Indicators are not stored, tracked or accessible outside of
ParserOutput (in particular they're not in the page_props table).
They are intended to merely reflect the content or metadata that is
already present on the page, and not be data themselves. If you ever
think you need to list pages with a given status indicator, instead
figure out what it means and use the appropriate tracking category,
special page report, already existing page_prop, or other means.
Corresponding patch in Vector: I90a8ae15ac8275d084ea5f47b6b2684d5e6c7412.
I'll implement support in the other three skins included in the tarball
and document it on mediawiki.org after this is merged.
Bug: 23796
Change-Id: I2389ff9a5332a2b1d033eb75f0946e5241cfaaf4
2014-09-24 10:44:16 +00:00
|
|
|
|
/**
|
2017-09-09 20:47:04 +00:00
|
|
|
|
* @param string $id
|
|
|
|
|
|
* @param string $content
|
2023-10-04 19:28:02 +00:00
|
|
|
|
* @param-taint $content exec_html
|
Implement page status indicators
Page status indicators are icons (or short text snippets) usually
displayed in the top-right corner of the page, outside of the main
content. Basically, <indicator name="foo">[[File:Foo.svg|20px]]</indicator>
may be used on a page to place the icon in the indicator area. They
are also known as top icons, page icons, heading icons or title icons.
I found the discussion on bug 23796 highly illuminating. I suggest
that everyone read it before suggesting different design choices.
I spent some time with a thesaurus pondering the name. "Emblems" and
"badges" were also considered, but the former has a much more limited
meaning and the latter is already taken by Wikidata, with a similar
but subtly different feature set. I am not aware of any naming
conflicts ;) besides new talk page message "indicator" (used by core
and Echo in some documents) and OOjs UI indicators (tiny icons like
the arrow on a dropdown form element), which shouldn't be confusing.
Potential use cases include:
* "Lock" indicators for page protection levels
* Featured/good article indicators
* Redirect shortcuts display ("WP:VPT")
* Links to help/manual for special pages
* Coordinates?… or globe icon for inline pop-up maps
Design features:
* Skin-customizable. Skins can fully control where and how indicators
are shown, or may just do <?php echo $this->getIndicators(); ?> to
output the default structure. By default they are not shown at all.
* Extension-customizable. Extensions can call ParserOutput::addIndicator()
to insert an indicator from one of the numerous parser hooks.
* Wiki-customizable. In addition to just using the parser functions,
on-wiki styles and scripts can use the provided classes and ids
(.mw-indicator, #mw-indicator-<name>) to customize their display.
Design limitations:
* Every indicator must have a unique identifier (name). It's not
possible to create arrays, or to have several indicators with the
same name. In case of duplicates, the latest occurrence of the
parser function wins.
* Indicators are displayed ordered by their names (and not occurrence
order). This ensures consistency across pages and provides a simple
means of ordering or grouping them.
* Indicators are not stored, tracked or accessible outside of
ParserOutput (in particular they're not in the page_props table).
They are intended to merely reflect the content or metadata that is
already present on the page, and not be data themselves. If you ever
think you need to list pages with a given status indicator, instead
figure out what it means and use the appropriate tracking category,
special page report, already existing page_prop, or other means.
Corresponding patch in Vector: I90a8ae15ac8275d084ea5f47b6b2684d5e6c7412.
I'll implement support in the other three skins included in the tarball
and document it on mediawiki.org after this is merged.
Bug: 23796
Change-Id: I2389ff9a5332a2b1d033eb75f0946e5241cfaaf4
2014-09-24 10:44:16 +00:00
|
|
|
|
* @since 1.25
|
|
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function setIndicator( $id, $content ): void {
|
Implement page status indicators
Page status indicators are icons (or short text snippets) usually
displayed in the top-right corner of the page, outside of the main
content. Basically, <indicator name="foo">[[File:Foo.svg|20px]]</indicator>
may be used on a page to place the icon in the indicator area. They
are also known as top icons, page icons, heading icons or title icons.
I found the discussion on bug 23796 highly illuminating. I suggest
that everyone read it before suggesting different design choices.
I spent some time with a thesaurus pondering the name. "Emblems" and
"badges" were also considered, but the former has a much more limited
meaning and the latter is already taken by Wikidata, with a similar
but subtly different feature set. I am not aware of any naming
conflicts ;) besides new talk page message "indicator" (used by core
and Echo in some documents) and OOjs UI indicators (tiny icons like
the arrow on a dropdown form element), which shouldn't be confusing.
Potential use cases include:
* "Lock" indicators for page protection levels
* Featured/good article indicators
* Redirect shortcuts display ("WP:VPT")
* Links to help/manual for special pages
* Coordinates?… or globe icon for inline pop-up maps
Design features:
* Skin-customizable. Skins can fully control where and how indicators
are shown, or may just do <?php echo $this->getIndicators(); ?> to
output the default structure. By default they are not shown at all.
* Extension-customizable. Extensions can call ParserOutput::addIndicator()
to insert an indicator from one of the numerous parser hooks.
* Wiki-customizable. In addition to just using the parser functions,
on-wiki styles and scripts can use the provided classes and ids
(.mw-indicator, #mw-indicator-<name>) to customize their display.
Design limitations:
* Every indicator must have a unique identifier (name). It's not
possible to create arrays, or to have several indicators with the
same name. In case of duplicates, the latest occurrence of the
parser function wins.
* Indicators are displayed ordered by their names (and not occurrence
order). This ensures consistency across pages and provides a simple
means of ordering or grouping them.
* Indicators are not stored, tracked or accessible outside of
ParserOutput (in particular they're not in the page_props table).
They are intended to merely reflect the content or metadata that is
already present on the page, and not be data themselves. If you ever
think you need to list pages with a given status indicator, instead
figure out what it means and use the appropriate tracking category,
special page report, already existing page_prop, or other means.
Corresponding patch in Vector: I90a8ae15ac8275d084ea5f47b6b2684d5e6c7412.
I'll implement support in the other three skins included in the tarball
and document it on mediawiki.org after this is merged.
Bug: 23796
Change-Id: I2389ff9a5332a2b1d033eb75f0946e5241cfaaf4
2014-09-24 10:44:16 +00:00
|
|
|
|
$this->mIndicators[$id] = $content;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-07-25 15:32:08 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Enables OOUI, if true, in any OutputPage instance this ParserOutput
|
|
|
|
|
|
* object is added to.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @since 1.26
|
|
|
|
|
|
* @param bool $enable If OOUI should be enabled or not
|
|
|
|
|
|
*/
|
2022-02-09 17:42:44 +00:00
|
|
|
|
public function setEnableOOUI( bool $enable = false ): void {
|
2015-07-25 15:32:08 +00:00
|
|
|
|
$this->mEnableOOUI = $enable;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-07 15:59:28 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Add a language link.
|
|
|
|
|
|
* @param ParsoidLinkTarget|string $t
|
|
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function addLanguageLink( $t ): void {
|
2024-09-06 15:59:58 +00:00
|
|
|
|
# Note that fragments are preserved
|
2023-12-07 15:59:28 +00:00
|
|
|
|
if ( $t instanceof ParsoidLinkTarget ) {
|
2024-10-01 23:06:01 +00:00
|
|
|
|
// Language links are unusual in using 'text' rather than 'db key'
|
|
|
|
|
|
// Note that fragments are preserved.
|
|
|
|
|
|
$lang = $t->getInterwiki();
|
|
|
|
|
|
$title = $t->getText();
|
|
|
|
|
|
if ( $t->hasFragment() ) {
|
|
|
|
|
|
$title .= '#' . $t->getFragment();
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
[ $lang, $title ] = array_pad( explode( ':', $t, 2 ), -2, '' );
|
2023-12-07 15:59:28 +00:00
|
|
|
|
}
|
2024-10-01 23:06:01 +00:00
|
|
|
|
if ( $lang === '' ) {
|
2024-06-14 03:54:52 +00:00
|
|
|
|
// T374736: For backward compatibility with test cases only!
|
|
|
|
|
|
wfDeprecated( __METHOD__ . ' without prefix', '1.43' );
|
2024-10-01 23:06:01 +00:00
|
|
|
|
[ $lang, $title ] = [ $title, '|' ]; // | can not occur in valid title
|
2024-06-14 03:54:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
$this->mLanguageLinkMap[$lang] ??= $title;
|
2014-05-10 23:03:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-04-02 21:56:24 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Add a warning to the output for this page.
|
|
|
|
|
|
* @param MessageValue $mv Note that the parameters must be serializable/deserializable
|
|
|
|
|
|
* with JsonCodec; see the @note on ParserOutput::setExtensionData(). MessageValue guarantees
|
|
|
|
|
|
* that unless the deprecated ParamType::OBJECT or the ->objectParams() method is used.
|
|
|
|
|
|
* @since 1.43
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function addWarningMsgVal( MessageValue $mv ) {
|
|
|
|
|
|
$m = ( new Converter() )->convertMessageValue( $mv );
|
|
|
|
|
|
// These can eventually be stored as MessageValue instead of converting to Message.
|
|
|
|
|
|
$this->addWarningMsg( $m->getKey(), ...$m->getParams() );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-15 19:42:40 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Add a warning to the output for this page.
|
|
|
|
|
|
* @param string $msg The localization message key for the warning
|
2024-05-04 08:58:57 +00:00
|
|
|
|
* @param mixed|JsonDeserializable ...$args Optional arguments for the
|
|
|
|
|
|
* message. These arguments must be serializable/deserializable with
|
2023-07-28 21:20:28 +00:00
|
|
|
|
* JsonCodec; see the @note on ParserOutput::setExtensionData()
|
2021-10-15 19:42:40 +00:00
|
|
|
|
* @since 1.38
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function addWarningMsg( string $msg, ...$args ): void {
|
2024-04-02 21:56:24 +00:00
|
|
|
|
// MessageValue objects are defined in core and thus not visible
|
|
|
|
|
|
// to Parsoid or to its ContentMetadataCollector interface.
|
|
|
|
|
|
// Eventually this method (defined in ContentMetadataCollector) should
|
|
|
|
|
|
// call ::addWarningMsgVal() instead of the other way around.
|
|
|
|
|
|
|
2022-02-07 16:28:23 +00:00
|
|
|
|
// preserve original arguments in $mWarningMsgs to allow merge
|
2024-05-04 08:58:57 +00:00
|
|
|
|
// @todo: these aren't serialized/deserialized yet -- before we
|
2023-07-28 21:20:28 +00:00
|
|
|
|
// turn on serialization of $this->mWarningMsgs we need to ensure
|
|
|
|
|
|
// callers aren't passing nonserializable arguments: T343048.
|
|
|
|
|
|
$jsonCodec = MediaWikiServices::getInstance()->getJsonCodec();
|
|
|
|
|
|
$path = $jsonCodec->detectNonSerializableData( $args, true );
|
|
|
|
|
|
if ( $path !== null ) {
|
|
|
|
|
|
wfDeprecatedMsg(
|
|
|
|
|
|
"ParserOutput::addWarningMsg() called with nonserializable arguments: $path",
|
|
|
|
|
|
'1.41'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2022-02-07 16:28:23 +00:00
|
|
|
|
$this->mWarningMsgs[$msg] = $args;
|
2021-10-15 19:42:40 +00:00
|
|
|
|
$s = wfMessage( $msg, ...$args )
|
2022-07-09 20:19:27 +00:00
|
|
|
|
// some callers set the title here?
|
|
|
|
|
|
->inContentLanguage() // because this ends up in cache
|
|
|
|
|
|
->text();
|
2021-10-15 19:42:40 +00:00
|
|
|
|
$this->mWarnings[$s] = 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function setNewSection( $value ): void {
|
2007-01-20 12:50:56 +00:00
|
|
|
|
$this->mNewSection = (bool)$value;
|
|
|
|
|
|
}
|
2019-05-11 01:17:43 +00:00
|
|
|
|
|
2021-09-29 20:48:33 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @param bool $value Hide the new section link?
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function setHideNewSection( bool $value ): void {
|
|
|
|
|
|
$this->mHideNewSection = $value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function getHideNewSection(): bool {
|
2009-02-19 22:14:59 +00:00
|
|
|
|
return (bool)$this->mHideNewSection;
|
|
|
|
|
|
}
|
2019-05-11 01:17:43 +00:00
|
|
|
|
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function getNewSection(): bool {
|
2007-01-20 12:50:56 +00:00
|
|
|
|
return (bool)$this->mNewSection;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2012-09-29 10:36:33 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Checks, if a url is pointing to the own server
|
|
|
|
|
|
*
|
2014-04-21 23:38:39 +00:00
|
|
|
|
* @param string $internal The server to check against
|
|
|
|
|
|
* @param string $url The url to check
|
2012-09-29 10:36:33 +00:00
|
|
|
|
* @return bool
|
2021-09-29 20:35:25 +00:00
|
|
|
|
* @internal
|
2012-09-29 10:36:33 +00:00
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public static function isLinkInternal( $internal, $url ): bool {
|
2012-09-29 10:36:33 +00:00
|
|
|
|
return (bool)preg_match( '/^' .
|
|
|
|
|
|
# If server is proto relative, check also for http/https links
|
|
|
|
|
|
( substr( $internal, 0, 2 ) === '//' ? '(?:https?:)?' : '' ) .
|
|
|
|
|
|
preg_quote( $internal, '/' ) .
|
|
|
|
|
|
# check for query/path/anchor or end of link in each case
|
|
|
|
|
|
'(?:[\?\/\#]|$)/i',
|
|
|
|
|
|
$url
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function addExternalLink( $url ): void {
|
2009-07-11 08:04:31 +00:00
|
|
|
|
# We don't register links pointing to our own server, unless... :-)
|
2022-01-06 18:44:56 +00:00
|
|
|
|
$config = MediaWikiServices::getInstance()->getMainConfig();
|
2022-04-10 15:34:45 +00:00
|
|
|
|
$server = $config->get( MainConfigNames::Server );
|
|
|
|
|
|
$registerInternalExternals = $config->get( MainConfigNames::RegisterInternalExternals );
|
2016-03-11 01:08:06 +00:00
|
|
|
|
# Replace unnecessary URL escape codes with the referenced character
|
|
|
|
|
|
# This prevents spammers from hiding links from the filters
|
2017-12-15 10:58:11 +00:00
|
|
|
|
$url = Parser::normalizeLinkUrl( $url );
|
2016-03-11 01:08:06 +00:00
|
|
|
|
|
2012-09-29 10:36:33 +00:00
|
|
|
|
$registerExternalLink = true;
|
2022-01-06 18:44:56 +00:00
|
|
|
|
if ( !$registerInternalExternals ) {
|
|
|
|
|
|
$registerExternalLink = !self::isLinkInternal( $server, $url );
|
2012-09-29 10:36:33 +00:00
|
|
|
|
}
|
2013-04-20 15:38:24 +00:00
|
|
|
|
if ( $registerExternalLink ) {
|
2011-08-01 15:40:02 +00:00
|
|
|
|
$this->mExternalLinks[$url] = 1;
|
2012-09-29 10:36:33 +00:00
|
|
|
|
}
|
2009-07-11 08:04:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2010-04-16 01:40:05 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Record a local or interwiki inline link for saving in future link tables.
|
|
|
|
|
|
*
|
2023-12-07 15:59:28 +00:00
|
|
|
|
* @param ParsoidLinkTarget $link (used to require Title until 1.38)
|
2014-04-21 23:38:39 +00:00
|
|
|
|
* @param int|null $id Optional known page_id so we can skip the lookup
|
2010-04-16 01:40:05 +00:00
|
|
|
|
*/
|
2023-12-07 15:59:28 +00:00
|
|
|
|
public function addLink( ParsoidLinkTarget $link, $id = null ): void {
|
2021-10-12 09:39:32 +00:00
|
|
|
|
if ( $link->isExternal() ) {
|
2009-07-18 22:49:31 +00:00
|
|
|
|
// Don't record interwikis in pagelinks
|
2021-10-12 09:39:32 +00:00
|
|
|
|
$this->addInterwikiLink( $link );
|
2009-07-18 22:49:31 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2021-10-12 09:39:32 +00:00
|
|
|
|
$ns = $link->getNamespace();
|
|
|
|
|
|
$dbk = $link->getDBkey();
|
2020-07-22 17:29:48 +00:00
|
|
|
|
if ( $ns === NS_MEDIA ) {
|
2009-01-01 00:05:08 +00:00
|
|
|
|
// Normalize this pseudo-alias if it makes it down here...
|
|
|
|
|
|
$ns = NS_FILE;
|
2020-07-22 17:29:48 +00:00
|
|
|
|
} elseif ( $ns === NS_SPECIAL ) {
|
preferences: Signature validation (lint errors, user links, nested subst)
Three new checks are now applied to user signatures in preferences:
* Disallow invalid HTML and lint errors (T140606)
Since 15e0e9bb4b we can rely on Parsoid to check the signature for
lint errors. (The old PHP Parser doesn't have this capability.)
Most importantly, this will disallow unclosed HTML tags. Unclosed
formatting tags like `<i>` (and also wikitext markup like `''`)
could affect the entire page with the bad markup.
New configuration variable $wgSignatureAllowedLintErrors is added
to allow ignoring some errors. The default value ignores the
'obsolete-tag' error (caused by HTML tags like `<font>` and `<tt>`.)
* Require a link to user page, talk page or contributions (T237700)
Various tools don't work correctly when such a link is missing. For
example, Echo notifications are not sent, DiscussionTools will not
allow replying to these comments, English Wikipedia's SineBot treats
these comments as unsigned.
Such requirement has been present for a long time in many Wikimedia
wikis' policies, but it was not enforced by software.
* Disallow "nested" substitution in signature (T230652)
Clever abuse of "subst" markup and tildes allows users to save edits
containing wikitext in which substitution occurs again when the page
is next saved. Disallow this in signatures, at least.
New configuration variable $wgSignatureValidation is added to control
what we do about the result of the validation described above. The
options are:
* 'warning':
Only displays a warning near the field on Special:Preferences if
the current signature is invalid. Signatures can still be changed
regardless of validity and will be used when signing comments.
* 'new':
In addition to the above, if a user tries to change their signature,
the new one must be valid. Existing invalid signatures are still
used when signing comments.
* 'disallow':
In addition to the above, existing invalid signatures are no longer
used when signing comments.
Bug: T140606
Bug: T237700
Bug: T230652
Change-Id: I07c575c2d9d2afe7a89c4847d16ac044417297bf
2019-11-09 00:15:51 +00:00
|
|
|
|
// We don't want to record Special: links in the database, so put them in a separate place.
|
2009-01-01 00:05:08 +00:00
|
|
|
|
// It might actually be wise to, but we'd need to do some normalization.
|
preferences: Signature validation (lint errors, user links, nested subst)
Three new checks are now applied to user signatures in preferences:
* Disallow invalid HTML and lint errors (T140606)
Since 15e0e9bb4b we can rely on Parsoid to check the signature for
lint errors. (The old PHP Parser doesn't have this capability.)
Most importantly, this will disallow unclosed HTML tags. Unclosed
formatting tags like `<i>` (and also wikitext markup like `''`)
could affect the entire page with the bad markup.
New configuration variable $wgSignatureAllowedLintErrors is added
to allow ignoring some errors. The default value ignores the
'obsolete-tag' error (caused by HTML tags like `<font>` and `<tt>`.)
* Require a link to user page, talk page or contributions (T237700)
Various tools don't work correctly when such a link is missing. For
example, Echo notifications are not sent, DiscussionTools will not
allow replying to these comments, English Wikipedia's SineBot treats
these comments as unsigned.
Such requirement has been present for a long time in many Wikimedia
wikis' policies, but it was not enforced by software.
* Disallow "nested" substitution in signature (T230652)
Clever abuse of "subst" markup and tildes allows users to save edits
containing wikitext in which substitution occurs again when the page
is next saved. Disallow this in signatures, at least.
New configuration variable $wgSignatureValidation is added to control
what we do about the result of the validation described above. The
options are:
* 'warning':
Only displays a warning near the field on Special:Preferences if
the current signature is invalid. Signatures can still be changed
regardless of validity and will be used when signing comments.
* 'new':
In addition to the above, if a user tries to change their signature,
the new one must be valid. Existing invalid signatures are still
used when signing comments.
* 'disallow':
In addition to the above, existing invalid signatures are no longer
used when signing comments.
Bug: T140606
Bug: T237700
Bug: T230652
Change-Id: I07c575c2d9d2afe7a89c4847d16ac044417297bf
2019-11-09 00:15:51 +00:00
|
|
|
|
$this->mLinksSpecial[$dbk] = 1;
|
2009-01-01 00:05:08 +00:00
|
|
|
|
return;
|
2013-04-20 15:38:24 +00:00
|
|
|
|
} elseif ( $dbk === '' ) {
|
2009-01-11 03:42:13 +00:00
|
|
|
|
// Don't record self links - [[#Foo]]
|
|
|
|
|
|
return;
|
2009-01-01 00:05:08 +00:00
|
|
|
|
}
|
2020-01-09 23:48:34 +00:00
|
|
|
|
if ( $id === null ) {
|
2024-02-08 19:03:12 +00:00
|
|
|
|
// T357048: This actually kills performance; we should batch these.
|
2021-10-12 09:39:32 +00:00
|
|
|
|
$page = MediaWikiServices::getInstance()->getPageStore()->getPageForLink( $link );
|
|
|
|
|
|
$id = $page->getId();
|
2007-01-20 12:50:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
$this->mLinks[$ns][$dbk] = $id;
|
|
|
|
|
|
}
|
2008-04-14 07:45:50 +00:00
|
|
|
|
|
2011-03-23 17:35:40 +00:00
|
|
|
|
/**
|
2011-04-04 01:22:08 +00:00
|
|
|
|
* Register a file dependency for this output
|
2023-12-07 15:59:28 +00:00
|
|
|
|
* @param string|ParsoidLinkTarget $name Title dbKey
|
2018-04-18 12:31:13 +00:00
|
|
|
|
* @param string|false|null $timestamp MW timestamp of file creation (or false if non-existing)
|
|
|
|
|
|
* @param string|false|null $sha1 Base 36 SHA-1 of file (or false if non-existing)
|
2011-03-23 17:35:40 +00:00
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function addImage( $name, $timestamp = null, $sha1 = null ): void {
|
2023-12-07 15:59:28 +00:00
|
|
|
|
if ( $name instanceof ParsoidLinkTarget ) {
|
|
|
|
|
|
$name = $name->getDBkey();
|
|
|
|
|
|
}
|
2007-05-31 16:01:26 +00:00
|
|
|
|
$this->mImages[$name] = 1;
|
2011-04-04 01:22:08 +00:00
|
|
|
|
if ( $timestamp !== null && $sha1 !== null ) {
|
2016-02-17 09:09:32 +00:00
|
|
|
|
$this->mFileSearchOptions[$name] = [ 'time' => $timestamp, 'sha1' => $sha1 ];
|
2011-04-04 01:22:08 +00:00
|
|
|
|
}
|
2007-05-31 16:01:26 +00:00
|
|
|
|
}
|
2007-01-20 12:50:56 +00:00
|
|
|
|
|
2011-02-19 01:02:56 +00:00
|
|
|
|
/**
|
2011-04-04 01:22:08 +00:00
|
|
|
|
* Register a template dependency for this output
|
2021-10-12 09:39:32 +00:00
|
|
|
|
*
|
2023-12-07 15:59:28 +00:00
|
|
|
|
* @param ParsoidLinkTarget $link (used to require Title until 1.38)
|
2014-04-21 23:38:39 +00:00
|
|
|
|
* @param int $page_id
|
|
|
|
|
|
* @param int $rev_id
|
2011-02-19 01:02:56 +00:00
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function addTemplate( $link, $page_id, $rev_id ): void {
|
2024-04-04 14:37:13 +00:00
|
|
|
|
if ( $link->isExternal() ) {
|
|
|
|
|
|
// Will throw an InvalidArgumentException in a future release.
|
|
|
|
|
|
wfDeprecated( __METHOD__ . " with interwiki link", '1.42' );
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2021-10-12 09:39:32 +00:00
|
|
|
|
$ns = $link->getNamespace();
|
|
|
|
|
|
$dbk = $link->getDBkey();
|
2024-02-08 19:03:12 +00:00
|
|
|
|
// T357048: Parsoid doesn't have page_id
|
2007-05-31 16:01:26 +00:00
|
|
|
|
$this->mTemplates[$ns][$dbk] = $page_id;
|
|
|
|
|
|
$this->mTemplateIds[$ns][$dbk] = $rev_id; // For versioning
|
2007-01-20 12:50:56 +00:00
|
|
|
|
}
|
2011-08-01 15:40:02 +00:00
|
|
|
|
|
2010-04-16 01:40:05 +00:00
|
|
|
|
/**
|
2023-12-07 15:59:28 +00:00
|
|
|
|
* @param ParsoidLinkTarget $link must be an interwiki link
|
2021-10-12 09:39:32 +00:00
|
|
|
|
* (used to require Title until 1.38).
|
2010-04-16 01:40:05 +00:00
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function addInterwikiLink( $link ): void {
|
2021-10-12 09:39:32 +00:00
|
|
|
|
if ( !$link->isExternal() ) {
|
2023-06-09 22:25:07 +00:00
|
|
|
|
throw new InvalidArgumentException( 'Non-interwiki link passed, internal parser error.' );
|
2010-04-16 01:40:05 +00:00
|
|
|
|
}
|
2021-10-12 09:39:32 +00:00
|
|
|
|
$prefix = $link->getInterwiki();
|
|
|
|
|
|
$this->mInterwikiLinks[$prefix][$link->getDBkey()] = 1;
|
2010-04-16 01:40:05 +00:00
|
|
|
|
}
|
2007-01-20 12:50:56 +00:00
|
|
|
|
|
2007-04-03 21:58:18 +00:00
|
|
|
|
/**
|
2012-07-10 12:48:06 +00:00
|
|
|
|
* Add some text to the "<head>".
|
2008-04-14 07:45:50 +00:00
|
|
|
|
* If $tag is set, the section with that tag will only be included once
|
2007-04-03 21:58:18 +00:00
|
|
|
|
* in a given page.
|
2014-04-21 23:38:39 +00:00
|
|
|
|
* @param string $section
|
2022-07-31 00:02:18 +00:00
|
|
|
|
* @param string|false $tag
|
2007-04-03 21:58:18 +00:00
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function addHeadItem( $section, $tag = false ): void {
|
2007-04-03 21:58:18 +00:00
|
|
|
|
if ( $tag !== false ) {
|
|
|
|
|
|
$this->mHeadItems[$tag] = $section;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$this->mHeadItems[] = $section;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2011-08-01 15:40:02 +00:00
|
|
|
|
|
2018-03-02 01:21:32 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @see OutputPage::addModules
|
2022-01-11 20:15:36 +00:00
|
|
|
|
* @param string[] $modules
|
2018-03-02 01:21:32 +00:00
|
|
|
|
*/
|
2023-07-31 22:43:42 +00:00
|
|
|
|
public function addModules( array $modules ): void {
|
2023-11-22 19:33:05 +00:00
|
|
|
|
$modules = array_fill_keys( $modules, true );
|
|
|
|
|
|
$this->mModuleSet = array_merge( $this->mModuleSet, $modules );
|
2010-09-04 04:00:09 +00:00
|
|
|
|
}
|
2008-04-14 07:45:50 +00:00
|
|
|
|
|
2018-03-02 01:21:32 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @see OutputPage::addModuleStyles
|
2022-01-11 20:15:36 +00:00
|
|
|
|
* @param string[] $modules
|
2018-03-02 01:21:32 +00:00
|
|
|
|
*/
|
2023-07-31 22:43:42 +00:00
|
|
|
|
public function addModuleStyles( array $modules ): void {
|
2023-11-22 19:33:05 +00:00
|
|
|
|
$modules = array_fill_keys( $modules, true );
|
|
|
|
|
|
$this->mModuleStyleSet = array_merge( $this->mModuleStyleSet, $modules );
|
2011-08-02 15:40:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2014-01-19 15:39:46 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Add one or more variables to be set in mw.config in JavaScript.
|
|
|
|
|
|
*
|
2014-04-21 23:38:39 +00:00
|
|
|
|
* @param string|array $keys Key or array of key/value pairs.
|
2018-06-26 21:14:43 +00:00
|
|
|
|
* @param mixed|null $value [optional] Value of the configuration variable.
|
2014-01-19 15:39:46 +00:00
|
|
|
|
* @since 1.23
|
2022-01-27 19:44:35 +00:00
|
|
|
|
* @deprecated since 1.38, use ::setJsConfigVar() or ::appendJsConfigVar()
|
2023-07-31 22:38:16 +00:00
|
|
|
|
* which ensures compatibility with asynchronous parsing; emitting warnings
|
|
|
|
|
|
* since 1.43.
|
2014-01-19 15:39:46 +00:00
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function addJsConfigVars( $keys, $value = null ): void {
|
2023-07-31 22:38:16 +00:00
|
|
|
|
wfDeprecated( __METHOD__, '1.38' );
|
2014-01-19 15:39:46 +00:00
|
|
|
|
if ( is_array( $keys ) ) {
|
|
|
|
|
|
foreach ( $keys as $key => $value ) {
|
|
|
|
|
|
$this->mJsConfigVars[$key] = $value;
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$this->mJsConfigVars[$keys] = $value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-01-27 19:44:35 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Add a variable to be set in mw.config in JavaScript.
|
|
|
|
|
|
*
|
|
|
|
|
|
* In order to ensure the result is independent of the parse order, the values
|
|
|
|
|
|
* set here must be unique -- that is, you can pass the same $key
|
|
|
|
|
|
* multiple times but ONLY if the $value is identical each time.
|
|
|
|
|
|
* If you want to collect multiple pieces of data under a single key,
|
|
|
|
|
|
* use ::appendJsConfigVar().
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param string $key Key to use under mw.config
|
|
|
|
|
|
* @param mixed|null $value Value of the configuration variable.
|
|
|
|
|
|
* @since 1.38
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function setJsConfigVar( string $key, $value ): void {
|
|
|
|
|
|
if (
|
|
|
|
|
|
array_key_exists( $key, $this->mJsConfigVars ) &&
|
|
|
|
|
|
$this->mJsConfigVars[$key] !== $value
|
|
|
|
|
|
) {
|
|
|
|
|
|
// Ensure that a key is mapped to only a single value in order
|
|
|
|
|
|
// to prevent the resulting array from varying if content
|
|
|
|
|
|
// is parsed in a different order.
|
|
|
|
|
|
throw new InvalidArgumentException( "Multiple conflicting values given for $key" );
|
|
|
|
|
|
}
|
|
|
|
|
|
$this->mJsConfigVars[$key] = $value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Append a value to a variable to be set in mw.config in JavaScript.
|
|
|
|
|
|
*
|
|
|
|
|
|
* In order to ensure the result is independent of the parse order,
|
|
|
|
|
|
* the value of this key will be an associative array, mapping all of
|
|
|
|
|
|
* the values set under that key to true. (The array is implicitly
|
|
|
|
|
|
* ordered in PHP, but you should treat it as unordered.)
|
|
|
|
|
|
* If you want a non-array type for the key, and can ensure that only
|
|
|
|
|
|
* a single value will be set, you should use ::setJsConfigVar() instead.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param string $key Key to use under mw.config
|
|
|
|
|
|
* @param string $value Value to append to the configuration variable.
|
2022-02-07 16:28:23 +00:00
|
|
|
|
* @param string $strategy Merge strategy:
|
|
|
|
|
|
* only MW_MERGE_STRATEGY_UNION is currently supported and external callers
|
|
|
|
|
|
* should treat this parameter as @internal at this time and omit it.
|
2022-01-27 19:44:35 +00:00
|
|
|
|
* @since 1.38
|
|
|
|
|
|
*/
|
2022-02-07 16:28:23 +00:00
|
|
|
|
public function appendJsConfigVar(
|
|
|
|
|
|
string $key,
|
|
|
|
|
|
string $value,
|
|
|
|
|
|
string $strategy = self::MW_MERGE_STRATEGY_UNION
|
|
|
|
|
|
): void {
|
|
|
|
|
|
if ( $strategy !== self::MW_MERGE_STRATEGY_UNION ) {
|
|
|
|
|
|
throw new InvalidArgumentException( "Unknown merge strategy $strategy." );
|
|
|
|
|
|
}
|
2022-01-27 19:44:35 +00:00
|
|
|
|
if ( !array_key_exists( $key, $this->mJsConfigVars ) ) {
|
|
|
|
|
|
$this->mJsConfigVars[$key] = [
|
|
|
|
|
|
// Indicate how these values are to be merged.
|
2022-02-07 16:28:23 +00:00
|
|
|
|
self::MW_MERGE_STRATEGY_KEY => $strategy,
|
2022-01-27 19:44:35 +00:00
|
|
|
|
];
|
|
|
|
|
|
} elseif ( !is_array( $this->mJsConfigVars[$key] ) ) {
|
|
|
|
|
|
throw new InvalidArgumentException( "Mixing set and append for $key" );
|
2022-02-07 16:28:23 +00:00
|
|
|
|
} elseif ( ( $this->mJsConfigVars[$key][self::MW_MERGE_STRATEGY_KEY] ?? null ) !== $strategy ) {
|
|
|
|
|
|
throw new InvalidArgumentException( "Conflicting merge strategies for $key" );
|
2022-01-27 19:44:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
$this->mJsConfigVars[$key][$value] = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2011-08-02 15:40:03 +00:00
|
|
|
|
/**
|
2022-05-09 09:09:00 +00:00
|
|
|
|
* Accommodate very basic transcluding of a temporary OutputPage object into parser output.
|
2011-08-02 15:40:03 +00:00
|
|
|
|
*
|
2021-11-05 02:26:12 +00:00
|
|
|
|
* This is a fragile method that cannot be relied upon in any meaningful way.
|
|
|
|
|
|
* It exists solely to support the wikitext feature of transcluding a SpecialPage, and
|
|
|
|
|
|
* only has to work for that use case to ensure relevant styles are loaded, and that
|
|
|
|
|
|
* essential config vars needed between SpecialPage and a JS feature are added.
|
|
|
|
|
|
*
|
|
|
|
|
|
* This relies on there being no overlap between modules or config vars added by
|
|
|
|
|
|
* the SpecialPage and those added by parser extensions. If there is overlap,
|
|
|
|
|
|
* then arise and break one or both sides. This is expected and unsupported.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @internal For use by Parser for basic special page transclusion
|
2014-04-21 23:38:39 +00:00
|
|
|
|
* @param OutputPage $out
|
2011-08-02 15:40:03 +00:00
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function addOutputPageMetadata( OutputPage $out ): void {
|
2022-01-27 19:44:35 +00:00
|
|
|
|
// This should eventually use the same merge mechanism used
|
|
|
|
|
|
// internally to merge ParserOutputs together.
|
2022-02-09 21:33:39 +00:00
|
|
|
|
// (ie: $this->mergeHtmlMetaDataFrom( $out->getMetadata() )
|
|
|
|
|
|
// once preventClickjacking, moduleStyles, modules, jsconfigvars,
|
|
|
|
|
|
// and head items are moved to OutputPage::$metadata)
|
2021-11-05 02:26:12 +00:00
|
|
|
|
|
|
|
|
|
|
// Take the strictest click-jacking policy. This is to ensure any one-click features
|
|
|
|
|
|
// such as patrol or rollback on the transcluded special page will result in the wiki page
|
|
|
|
|
|
// disallowing embedding in cross-origin iframes. Articles are generally allowed to be
|
|
|
|
|
|
// embedded. Pages that transclude special pages are expected to be user pages or
|
|
|
|
|
|
// other non-content pages that content re-users won't discover or care about.
|
|
|
|
|
|
$this->mPreventClickjacking = $this->mPreventClickjacking || $out->getPreventClickjacking();
|
|
|
|
|
|
|
2011-08-02 15:40:03 +00:00
|
|
|
|
$this->addModuleStyles( $out->getModuleStyles() );
|
2021-11-05 02:26:12 +00:00
|
|
|
|
|
|
|
|
|
|
// TODO: Figure out if style modules suffice, or whether the below is needed as well.
|
|
|
|
|
|
// Are there special pages that permit transcluding/including and also have JS modules
|
|
|
|
|
|
// that should be activate on the host page?
|
|
|
|
|
|
$this->addModules( $out->getModules() );
|
2022-01-27 19:44:35 +00:00
|
|
|
|
$this->mJsConfigVars = self::mergeMapStrategy(
|
|
|
|
|
|
$this->mJsConfigVars, $out->getJsConfigVars()
|
|
|
|
|
|
);
|
2011-08-02 15:40:03 +00:00
|
|
|
|
$this->mHeadItems = array_merge( $this->mHeadItems, $out->getHeadItemsArray() );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2007-06-25 15:51:09 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Override the title to be used for display
|
2015-10-16 18:03:33 +00:00
|
|
|
|
*
|
|
|
|
|
|
* @note this is assumed to have been validated
|
2007-06-25 15:51:09 +00:00
|
|
|
|
* (check equal normalisation, etc.)
|
|
|
|
|
|
*
|
2015-10-16 18:03:33 +00:00
|
|
|
|
* @note this is expected to be safe HTML,
|
|
|
|
|
|
* ready to be served to the client.
|
|
|
|
|
|
*
|
2014-07-24 17:43:25 +00:00
|
|
|
|
* @param string $text Desired title text
|
2007-06-25 15:51:09 +00:00
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function setDisplayTitle( $text ): void {
|
2010-01-15 19:14:23 +00:00
|
|
|
|
$this->setTitleText( $text );
|
2021-10-07 16:13:46 +00:00
|
|
|
|
$this->setPageProperty( 'displaytitle', $text );
|
2007-06-25 15:51:09 +00:00
|
|
|
|
}
|
2008-12-31 16:49:38 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2015-10-16 18:03:33 +00:00
|
|
|
|
* Get the title to be used for display.
|
|
|
|
|
|
*
|
|
|
|
|
|
* As per the contract of setDisplayTitle(), this is safe HTML,
|
|
|
|
|
|
* ready to be served to the client.
|
2008-12-31 16:49:38 +00:00
|
|
|
|
*
|
2021-10-04 18:40:24 +00:00
|
|
|
|
* @return string|false HTML
|
2008-12-31 16:49:38 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function getDisplayTitle() {
|
2010-12-28 09:54:25 +00:00
|
|
|
|
$t = $this->getTitleText();
|
2013-04-20 15:38:24 +00:00
|
|
|
|
if ( $t === '' ) {
|
2010-01-15 19:14:23 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2010-02-16 03:37:19 +00:00
|
|
|
|
return $t;
|
2007-06-25 15:51:09 +00:00
|
|
|
|
}
|
2008-04-14 07:45:50 +00:00
|
|
|
|
|
Add ParserOutput::getLanguage()
Provide a way for backend code to determine the primary language of a
ParserOutput, eg for setting the Content-Language header of an API
response.
This is read-only and backed by extension data at the moment for
transition purposes; if this API sticks we'll graduate it to a
"real" property in the future, with appropriate serialization
to/from JSON (T303329).
Similarly, this patch only includes the most basic code to handle
the various ParserOutput merge cases in
ParserOutput::merge{Internal,Html,Tracking}MetaDataFrom(),
ParserOutput::collectMetadata(), and
OutputPage::addParserOutput{Content,Metadata,Text,}(); mostly
inherited from the fact that the storage is backed by extension
data at the moment.
Generally only the "top-level" parser output gets to set the
primary language; we'll presumably need to ensure that the
language is consistent during merge.
Change-Id: I767daba22805a877d9b806fd77334e508902844b
2022-09-28 21:21:13 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Get the primary language code of the output.
|
|
|
|
|
|
*
|
parser: Move lang/dir and mw-content-ltr to ParserOutput::getText
== Skin::wrapHTML ==
Skin::wrapHTML no longer has to perform any guessing of the
ParserOutput language. Nor does it have to special wiki pages vs
special pages in this regard. Yay, code removal.
== ImagePage ==
On URLs like /wiki/File:Example.jpg, the main output handler is
ImagePage::view. This calls the parent Article::view to handle most of
its output. Article::view obtains the ParserOptions, and then fetches
ParserOutput, and then adds `<div class=mw-parser-output>` and its
metadata to OutputPage.
Before this change, ImagePage::view was creating a wrapper based
on "predicting" what language the ParserOutput will contain. It
couldn't call the new OutputPage::getContentLanguage or some
equivalent as Article::view wouldn't have populated that yet.
This leaky abstraction is fixed by this change as now the `<div>`
from ParserOutput no longer comes with a "please wrap it properly"
contract that Article subclasses couldn't possibly implement correctly
(it coudln't wrap it after the fact because Article::view writes to
OutputPage directly).
RECENT (T310445):
A special case was recently added for file pages about translated SVGs.
For those, we decide which language to use for the "fullMedia" thumb
atop the page. This was recently changed as part of T310445 from a
hardcoded $wgLanguageCode (site content lang) to new problematic
Title::getPageViewLanguage, which tries to guestimate the page
language of the rendered ParserOutput and then gets the preferred
variant for the current user. The motivation for this was to support
language variants but used Title::getPageViewLanguage as a kitchen
sink to achieve that minor side-effect. The only part of this
now-deprecated method that we actually need is
LanguageConverter::getPreferredVariant().
Test plan: Covered by ImagePageTest.
== Skin mainpage-title ==
RECENT (T331095, T298715):
A special case was added to Skin::getTemplateData that powers the
mainpage-title interface message feature. This is empty by default,
but when created via MediaWiki:mainpage-title allows interface admins
to replace the H1 with a custom and localised page heading.
A few months ago, in Ifc9f0a7174, Title::getPageViewLanguage was
applied here to support language variants. Replace with the same
fix as for ImagePage. Revert back to Message::inContentLanguage()
but refactor to inLanguage() via MediaWikiServices::getContentLanguage
so that LanguageConverter::getPreferredVariant can be applied.
== EditPage ==
This was doing similar "predicting" of the ParserOutput language to
create an empty preview placeholder for use by preview.js. Now that
ApiParse (via ParserOutput::getText) returns a usable element without
any secret "you magically know the right class, lang, and dir" contract,
this placeholder is no longer needed.
Test Plan:
* EditPage: Default preview
1. index.php?title=Main_Page&action=edit
2. Show preview
3. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
* EditPage: JS preview
1. Preferences > Editing > Show preview without reload
2. index.php?title=Main_Page&action=edit
3. Show preview
4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
5. Type something and 'Show preview' again
6. Assert old element gone, new text is shown, and new element
attributes are the same as the above.
== McrUndoAction ==
Same as EditPage basically, but without the JS preview use case.
== DifferenceEngine ==
Test:
1. Open /w/index.php?title=Main_Page&diff=0
(this shows the latest diff, can do manually by viewing
/wiki/Main_Page, click "View history", click "Compare selected revisions")
2. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
3. Open /w/index.php?title=Main_Page&diff=0&action=render
4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
== Special:ExpandTemplates ==
Test:
1. /wiki/Special:ExpandTemplates
2. Write "Hello".
3. "OK"
4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
Bug: T341244
Depends-On: Icd9c079f5896ee83d86b9c2699636dc81d25a14c
Depends-On: I4e7484b3b94f1cb6062e7cef9f20626b650bb4b1
Depends-On: I90b88f3b3a3bbeba4f48d118f92f54864997e105
Change-Id: Ib130a055e46764544af0f1a46d2bc2b3a7ee85b7
2023-10-04 04:45:07 +00:00
|
|
|
|
* This returns the primary language of the output, including
|
|
|
|
|
|
* any LanguageConverter variant applied.
|
Add ParserOutput::getLanguage()
Provide a way for backend code to determine the primary language of a
ParserOutput, eg for setting the Content-Language header of an API
response.
This is read-only and backed by extension data at the moment for
transition purposes; if this API sticks we'll graduate it to a
"real" property in the future, with appropriate serialization
to/from JSON (T303329).
Similarly, this patch only includes the most basic code to handle
the various ParserOutput merge cases in
ParserOutput::merge{Internal,Html,Tracking}MetaDataFrom(),
ParserOutput::collectMetadata(), and
OutputPage::addParserOutput{Content,Metadata,Text,}(); mostly
inherited from the fact that the storage is backed by extension
data at the moment.
Generally only the "top-level" parser output gets to set the
primary language; we'll presumably need to ensure that the
language is consistent during merge.
Change-Id: I767daba22805a877d9b806fd77334e508902844b
2022-09-28 21:21:13 +00:00
|
|
|
|
*
|
parser: Move lang/dir and mw-content-ltr to ParserOutput::getText
== Skin::wrapHTML ==
Skin::wrapHTML no longer has to perform any guessing of the
ParserOutput language. Nor does it have to special wiki pages vs
special pages in this regard. Yay, code removal.
== ImagePage ==
On URLs like /wiki/File:Example.jpg, the main output handler is
ImagePage::view. This calls the parent Article::view to handle most of
its output. Article::view obtains the ParserOptions, and then fetches
ParserOutput, and then adds `<div class=mw-parser-output>` and its
metadata to OutputPage.
Before this change, ImagePage::view was creating a wrapper based
on "predicting" what language the ParserOutput will contain. It
couldn't call the new OutputPage::getContentLanguage or some
equivalent as Article::view wouldn't have populated that yet.
This leaky abstraction is fixed by this change as now the `<div>`
from ParserOutput no longer comes with a "please wrap it properly"
contract that Article subclasses couldn't possibly implement correctly
(it coudln't wrap it after the fact because Article::view writes to
OutputPage directly).
RECENT (T310445):
A special case was recently added for file pages about translated SVGs.
For those, we decide which language to use for the "fullMedia" thumb
atop the page. This was recently changed as part of T310445 from a
hardcoded $wgLanguageCode (site content lang) to new problematic
Title::getPageViewLanguage, which tries to guestimate the page
language of the rendered ParserOutput and then gets the preferred
variant for the current user. The motivation for this was to support
language variants but used Title::getPageViewLanguage as a kitchen
sink to achieve that minor side-effect. The only part of this
now-deprecated method that we actually need is
LanguageConverter::getPreferredVariant().
Test plan: Covered by ImagePageTest.
== Skin mainpage-title ==
RECENT (T331095, T298715):
A special case was added to Skin::getTemplateData that powers the
mainpage-title interface message feature. This is empty by default,
but when created via MediaWiki:mainpage-title allows interface admins
to replace the H1 with a custom and localised page heading.
A few months ago, in Ifc9f0a7174, Title::getPageViewLanguage was
applied here to support language variants. Replace with the same
fix as for ImagePage. Revert back to Message::inContentLanguage()
but refactor to inLanguage() via MediaWikiServices::getContentLanguage
so that LanguageConverter::getPreferredVariant can be applied.
== EditPage ==
This was doing similar "predicting" of the ParserOutput language to
create an empty preview placeholder for use by preview.js. Now that
ApiParse (via ParserOutput::getText) returns a usable element without
any secret "you magically know the right class, lang, and dir" contract,
this placeholder is no longer needed.
Test Plan:
* EditPage: Default preview
1. index.php?title=Main_Page&action=edit
2. Show preview
3. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
* EditPage: JS preview
1. Preferences > Editing > Show preview without reload
2. index.php?title=Main_Page&action=edit
3. Show preview
4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
5. Type something and 'Show preview' again
6. Assert old element gone, new text is shown, and new element
attributes are the same as the above.
== McrUndoAction ==
Same as EditPage basically, but without the JS preview use case.
== DifferenceEngine ==
Test:
1. Open /w/index.php?title=Main_Page&diff=0
(this shows the latest diff, can do manually by viewing
/wiki/Main_Page, click "View history", click "Compare selected revisions")
2. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
3. Open /w/index.php?title=Main_Page&diff=0&action=render
4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
== Special:ExpandTemplates ==
Test:
1. /wiki/Special:ExpandTemplates
2. Write "Hello".
3. "OK"
4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
Bug: T341244
Depends-On: Icd9c079f5896ee83d86b9c2699636dc81d25a14c
Depends-On: I4e7484b3b94f1cb6062e7cef9f20626b650bb4b1
Depends-On: I90b88f3b3a3bbeba4f48d118f92f54864997e105
Change-Id: Ib130a055e46764544af0f1a46d2bc2b3a7ee85b7
2023-10-04 04:45:07 +00:00
|
|
|
|
* NOTE: This may differ from the wiki's default content language
|
|
|
|
|
|
* ($wgLanguageCode, MediaWikiServices::getContentLanguage), because
|
|
|
|
|
|
* each page may have its own "page language" set (PageStoreRecord,
|
|
|
|
|
|
* Title::getDbPageLanguageCode, ContentHandler::getPageLanguage).
|
Add ParserOutput::getLanguage()
Provide a way for backend code to determine the primary language of a
ParserOutput, eg for setting the Content-Language header of an API
response.
This is read-only and backed by extension data at the moment for
transition purposes; if this API sticks we'll graduate it to a
"real" property in the future, with appropriate serialization
to/from JSON (T303329).
Similarly, this patch only includes the most basic code to handle
the various ParserOutput merge cases in
ParserOutput::merge{Internal,Html,Tracking}MetaDataFrom(),
ParserOutput::collectMetadata(), and
OutputPage::addParserOutput{Content,Metadata,Text,}(); mostly
inherited from the fact that the storage is backed by extension
data at the moment.
Generally only the "top-level" parser output gets to set the
primary language; we'll presumably need to ensure that the
language is consistent during merge.
Change-Id: I767daba22805a877d9b806fd77334e508902844b
2022-09-28 21:21:13 +00:00
|
|
|
|
*
|
parser: Move lang/dir and mw-content-ltr to ParserOutput::getText
== Skin::wrapHTML ==
Skin::wrapHTML no longer has to perform any guessing of the
ParserOutput language. Nor does it have to special wiki pages vs
special pages in this regard. Yay, code removal.
== ImagePage ==
On URLs like /wiki/File:Example.jpg, the main output handler is
ImagePage::view. This calls the parent Article::view to handle most of
its output. Article::view obtains the ParserOptions, and then fetches
ParserOutput, and then adds `<div class=mw-parser-output>` and its
metadata to OutputPage.
Before this change, ImagePage::view was creating a wrapper based
on "predicting" what language the ParserOutput will contain. It
couldn't call the new OutputPage::getContentLanguage or some
equivalent as Article::view wouldn't have populated that yet.
This leaky abstraction is fixed by this change as now the `<div>`
from ParserOutput no longer comes with a "please wrap it properly"
contract that Article subclasses couldn't possibly implement correctly
(it coudln't wrap it after the fact because Article::view writes to
OutputPage directly).
RECENT (T310445):
A special case was recently added for file pages about translated SVGs.
For those, we decide which language to use for the "fullMedia" thumb
atop the page. This was recently changed as part of T310445 from a
hardcoded $wgLanguageCode (site content lang) to new problematic
Title::getPageViewLanguage, which tries to guestimate the page
language of the rendered ParserOutput and then gets the preferred
variant for the current user. The motivation for this was to support
language variants but used Title::getPageViewLanguage as a kitchen
sink to achieve that minor side-effect. The only part of this
now-deprecated method that we actually need is
LanguageConverter::getPreferredVariant().
Test plan: Covered by ImagePageTest.
== Skin mainpage-title ==
RECENT (T331095, T298715):
A special case was added to Skin::getTemplateData that powers the
mainpage-title interface message feature. This is empty by default,
but when created via MediaWiki:mainpage-title allows interface admins
to replace the H1 with a custom and localised page heading.
A few months ago, in Ifc9f0a7174, Title::getPageViewLanguage was
applied here to support language variants. Replace with the same
fix as for ImagePage. Revert back to Message::inContentLanguage()
but refactor to inLanguage() via MediaWikiServices::getContentLanguage
so that LanguageConverter::getPreferredVariant can be applied.
== EditPage ==
This was doing similar "predicting" of the ParserOutput language to
create an empty preview placeholder for use by preview.js. Now that
ApiParse (via ParserOutput::getText) returns a usable element without
any secret "you magically know the right class, lang, and dir" contract,
this placeholder is no longer needed.
Test Plan:
* EditPage: Default preview
1. index.php?title=Main_Page&action=edit
2. Show preview
3. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
* EditPage: JS preview
1. Preferences > Editing > Show preview without reload
2. index.php?title=Main_Page&action=edit
3. Show preview
4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
5. Type something and 'Show preview' again
6. Assert old element gone, new text is shown, and new element
attributes are the same as the above.
== McrUndoAction ==
Same as EditPage basically, but without the JS preview use case.
== DifferenceEngine ==
Test:
1. Open /w/index.php?title=Main_Page&diff=0
(this shows the latest diff, can do manually by viewing
/wiki/Main_Page, click "View history", click "Compare selected revisions")
2. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
3. Open /w/index.php?title=Main_Page&diff=0&action=render
4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
== Special:ExpandTemplates ==
Test:
1. /wiki/Special:ExpandTemplates
2. Write "Hello".
3. "OK"
4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
Bug: T341244
Depends-On: Icd9c079f5896ee83d86b9c2699636dc81d25a14c
Depends-On: I4e7484b3b94f1cb6062e7cef9f20626b650bb4b1
Depends-On: I90b88f3b3a3bbeba4f48d118f92f54864997e105
Change-Id: Ib130a055e46764544af0f1a46d2bc2b3a7ee85b7
2023-10-04 04:45:07 +00:00
|
|
|
|
* NOTE: This may differ from the "page language" when parsing
|
|
|
|
|
|
* user interface messages, in which case this reflects the user
|
|
|
|
|
|
* language (including any variant preference).
|
Add ParserOutput::getLanguage()
Provide a way for backend code to determine the primary language of a
ParserOutput, eg for setting the Content-Language header of an API
response.
This is read-only and backed by extension data at the moment for
transition purposes; if this API sticks we'll graduate it to a
"real" property in the future, with appropriate serialization
to/from JSON (T303329).
Similarly, this patch only includes the most basic code to handle
the various ParserOutput merge cases in
ParserOutput::merge{Internal,Html,Tracking}MetaDataFrom(),
ParserOutput::collectMetadata(), and
OutputPage::addParserOutput{Content,Metadata,Text,}(); mostly
inherited from the fact that the storage is backed by extension
data at the moment.
Generally only the "top-level" parser output gets to set the
primary language; we'll presumably need to ensure that the
language is consistent during merge.
Change-Id: I767daba22805a877d9b806fd77334e508902844b
2022-09-28 21:21:13 +00:00
|
|
|
|
*
|
parser: Move lang/dir and mw-content-ltr to ParserOutput::getText
== Skin::wrapHTML ==
Skin::wrapHTML no longer has to perform any guessing of the
ParserOutput language. Nor does it have to special wiki pages vs
special pages in this regard. Yay, code removal.
== ImagePage ==
On URLs like /wiki/File:Example.jpg, the main output handler is
ImagePage::view. This calls the parent Article::view to handle most of
its output. Article::view obtains the ParserOptions, and then fetches
ParserOutput, and then adds `<div class=mw-parser-output>` and its
metadata to OutputPage.
Before this change, ImagePage::view was creating a wrapper based
on "predicting" what language the ParserOutput will contain. It
couldn't call the new OutputPage::getContentLanguage or some
equivalent as Article::view wouldn't have populated that yet.
This leaky abstraction is fixed by this change as now the `<div>`
from ParserOutput no longer comes with a "please wrap it properly"
contract that Article subclasses couldn't possibly implement correctly
(it coudln't wrap it after the fact because Article::view writes to
OutputPage directly).
RECENT (T310445):
A special case was recently added for file pages about translated SVGs.
For those, we decide which language to use for the "fullMedia" thumb
atop the page. This was recently changed as part of T310445 from a
hardcoded $wgLanguageCode (site content lang) to new problematic
Title::getPageViewLanguage, which tries to guestimate the page
language of the rendered ParserOutput and then gets the preferred
variant for the current user. The motivation for this was to support
language variants but used Title::getPageViewLanguage as a kitchen
sink to achieve that minor side-effect. The only part of this
now-deprecated method that we actually need is
LanguageConverter::getPreferredVariant().
Test plan: Covered by ImagePageTest.
== Skin mainpage-title ==
RECENT (T331095, T298715):
A special case was added to Skin::getTemplateData that powers the
mainpage-title interface message feature. This is empty by default,
but when created via MediaWiki:mainpage-title allows interface admins
to replace the H1 with a custom and localised page heading.
A few months ago, in Ifc9f0a7174, Title::getPageViewLanguage was
applied here to support language variants. Replace with the same
fix as for ImagePage. Revert back to Message::inContentLanguage()
but refactor to inLanguage() via MediaWikiServices::getContentLanguage
so that LanguageConverter::getPreferredVariant can be applied.
== EditPage ==
This was doing similar "predicting" of the ParserOutput language to
create an empty preview placeholder for use by preview.js. Now that
ApiParse (via ParserOutput::getText) returns a usable element without
any secret "you magically know the right class, lang, and dir" contract,
this placeholder is no longer needed.
Test Plan:
* EditPage: Default preview
1. index.php?title=Main_Page&action=edit
2. Show preview
3. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
* EditPage: JS preview
1. Preferences > Editing > Show preview without reload
2. index.php?title=Main_Page&action=edit
3. Show preview
4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
5. Type something and 'Show preview' again
6. Assert old element gone, new text is shown, and new element
attributes are the same as the above.
== McrUndoAction ==
Same as EditPage basically, but without the JS preview use case.
== DifferenceEngine ==
Test:
1. Open /w/index.php?title=Main_Page&diff=0
(this shows the latest diff, can do manually by viewing
/wiki/Main_Page, click "View history", click "Compare selected revisions")
2. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
3. Open /w/index.php?title=Main_Page&diff=0&action=render
4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
== Special:ExpandTemplates ==
Test:
1. /wiki/Special:ExpandTemplates
2. Write "Hello".
3. "OK"
4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
Bug: T341244
Depends-On: Icd9c079f5896ee83d86b9c2699636dc81d25a14c
Depends-On: I4e7484b3b94f1cb6062e7cef9f20626b650bb4b1
Depends-On: I90b88f3b3a3bbeba4f48d118f92f54864997e105
Change-Id: Ib130a055e46764544af0f1a46d2bc2b3a7ee85b7
2023-10-04 04:45:07 +00:00
|
|
|
|
* NOTE: This may differ from the Parser's "target language" that was
|
|
|
|
|
|
* set while the Parser was parsing the page, because the final output
|
|
|
|
|
|
* is converted to the current user's preferred LanguageConverter variant
|
|
|
|
|
|
* (assuming this is a variant of the target language).
|
|
|
|
|
|
* See Parser::getTargetLanguageConverter()->getPreferredVariant(); use
|
|
|
|
|
|
* LanguageFactory::getParentLanguage() on the language code to obtain
|
|
|
|
|
|
* the base language code. LanguageConverter::getPreferredVariant()
|
|
|
|
|
|
* depends on the global RequestContext for the URL and the User
|
|
|
|
|
|
* language preference.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Finally, note that a single ParserOutput object may contain
|
|
|
|
|
|
* HTML content in multiple different languages and directions
|
|
|
|
|
|
* (T114640). Authors of wikitext and of parser extensions are
|
|
|
|
|
|
* expected to mark such subtrees with a `lang` attribute (set to
|
|
|
|
|
|
* a BCP-47 value, see Language::toBcp47Code()) and a corresponding
|
|
|
|
|
|
* `dir` attribute (see Language::getDir()). This method returns
|
|
|
|
|
|
* the language code for wrapper of the HTML content.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @see Parser::internalParseHalfParsed
|
Add ParserOutput::getLanguage()
Provide a way for backend code to determine the primary language of a
ParserOutput, eg for setting the Content-Language header of an API
response.
This is read-only and backed by extension data at the moment for
transition purposes; if this API sticks we'll graduate it to a
"real" property in the future, with appropriate serialization
to/from JSON (T303329).
Similarly, this patch only includes the most basic code to handle
the various ParserOutput merge cases in
ParserOutput::merge{Internal,Html,Tracking}MetaDataFrom(),
ParserOutput::collectMetadata(), and
OutputPage::addParserOutput{Content,Metadata,Text,}(); mostly
inherited from the fact that the storage is backed by extension
data at the moment.
Generally only the "top-level" parser output gets to set the
primary language; we'll presumably need to ensure that the
language is consistent during merge.
Change-Id: I767daba22805a877d9b806fd77334e508902844b
2022-09-28 21:21:13 +00:00
|
|
|
|
* @since 1.40
|
parser: Move lang/dir and mw-content-ltr to ParserOutput::getText
== Skin::wrapHTML ==
Skin::wrapHTML no longer has to perform any guessing of the
ParserOutput language. Nor does it have to special wiki pages vs
special pages in this regard. Yay, code removal.
== ImagePage ==
On URLs like /wiki/File:Example.jpg, the main output handler is
ImagePage::view. This calls the parent Article::view to handle most of
its output. Article::view obtains the ParserOptions, and then fetches
ParserOutput, and then adds `<div class=mw-parser-output>` and its
metadata to OutputPage.
Before this change, ImagePage::view was creating a wrapper based
on "predicting" what language the ParserOutput will contain. It
couldn't call the new OutputPage::getContentLanguage or some
equivalent as Article::view wouldn't have populated that yet.
This leaky abstraction is fixed by this change as now the `<div>`
from ParserOutput no longer comes with a "please wrap it properly"
contract that Article subclasses couldn't possibly implement correctly
(it coudln't wrap it after the fact because Article::view writes to
OutputPage directly).
RECENT (T310445):
A special case was recently added for file pages about translated SVGs.
For those, we decide which language to use for the "fullMedia" thumb
atop the page. This was recently changed as part of T310445 from a
hardcoded $wgLanguageCode (site content lang) to new problematic
Title::getPageViewLanguage, which tries to guestimate the page
language of the rendered ParserOutput and then gets the preferred
variant for the current user. The motivation for this was to support
language variants but used Title::getPageViewLanguage as a kitchen
sink to achieve that minor side-effect. The only part of this
now-deprecated method that we actually need is
LanguageConverter::getPreferredVariant().
Test plan: Covered by ImagePageTest.
== Skin mainpage-title ==
RECENT (T331095, T298715):
A special case was added to Skin::getTemplateData that powers the
mainpage-title interface message feature. This is empty by default,
but when created via MediaWiki:mainpage-title allows interface admins
to replace the H1 with a custom and localised page heading.
A few months ago, in Ifc9f0a7174, Title::getPageViewLanguage was
applied here to support language variants. Replace with the same
fix as for ImagePage. Revert back to Message::inContentLanguage()
but refactor to inLanguage() via MediaWikiServices::getContentLanguage
so that LanguageConverter::getPreferredVariant can be applied.
== EditPage ==
This was doing similar "predicting" of the ParserOutput language to
create an empty preview placeholder for use by preview.js. Now that
ApiParse (via ParserOutput::getText) returns a usable element without
any secret "you magically know the right class, lang, and dir" contract,
this placeholder is no longer needed.
Test Plan:
* EditPage: Default preview
1. index.php?title=Main_Page&action=edit
2. Show preview
3. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
* EditPage: JS preview
1. Preferences > Editing > Show preview without reload
2. index.php?title=Main_Page&action=edit
3. Show preview
4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
5. Type something and 'Show preview' again
6. Assert old element gone, new text is shown, and new element
attributes are the same as the above.
== McrUndoAction ==
Same as EditPage basically, but without the JS preview use case.
== DifferenceEngine ==
Test:
1. Open /w/index.php?title=Main_Page&diff=0
(this shows the latest diff, can do manually by viewing
/wiki/Main_Page, click "View history", click "Compare selected revisions")
2. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
3. Open /w/index.php?title=Main_Page&diff=0&action=render
4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
== Special:ExpandTemplates ==
Test:
1. /wiki/Special:ExpandTemplates
2. Write "Hello".
3. "OK"
4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
Bug: T341244
Depends-On: Icd9c079f5896ee83d86b9c2699636dc81d25a14c
Depends-On: I4e7484b3b94f1cb6062e7cef9f20626b650bb4b1
Depends-On: I90b88f3b3a3bbeba4f48d118f92f54864997e105
Change-Id: Ib130a055e46764544af0f1a46d2bc2b3a7ee85b7
2023-10-04 04:45:07 +00:00
|
|
|
|
* @return ?Bcp47Code The primary language for this output,
|
|
|
|
|
|
* or `null` if a language was not set.
|
Add ParserOutput::getLanguage()
Provide a way for backend code to determine the primary language of a
ParserOutput, eg for setting the Content-Language header of an API
response.
This is read-only and backed by extension data at the moment for
transition purposes; if this API sticks we'll graduate it to a
"real" property in the future, with appropriate serialization
to/from JSON (T303329).
Similarly, this patch only includes the most basic code to handle
the various ParserOutput merge cases in
ParserOutput::merge{Internal,Html,Tracking}MetaDataFrom(),
ParserOutput::collectMetadata(), and
OutputPage::addParserOutput{Content,Metadata,Text,}(); mostly
inherited from the fact that the storage is backed by extension
data at the moment.
Generally only the "top-level" parser output gets to set the
primary language; we'll presumably need to ensure that the
language is consistent during merge.
Change-Id: I767daba22805a877d9b806fd77334e508902844b
2022-09-28 21:21:13 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function getLanguage(): ?Bcp47Code {
|
|
|
|
|
|
// This information is temporarily stored in extension data (T303329)
|
|
|
|
|
|
$code = $this->getExtensionData( 'core:target-lang-variant' );
|
parser: Move lang/dir and mw-content-ltr to ParserOutput::getText
== Skin::wrapHTML ==
Skin::wrapHTML no longer has to perform any guessing of the
ParserOutput language. Nor does it have to special wiki pages vs
special pages in this regard. Yay, code removal.
== ImagePage ==
On URLs like /wiki/File:Example.jpg, the main output handler is
ImagePage::view. This calls the parent Article::view to handle most of
its output. Article::view obtains the ParserOptions, and then fetches
ParserOutput, and then adds `<div class=mw-parser-output>` and its
metadata to OutputPage.
Before this change, ImagePage::view was creating a wrapper based
on "predicting" what language the ParserOutput will contain. It
couldn't call the new OutputPage::getContentLanguage or some
equivalent as Article::view wouldn't have populated that yet.
This leaky abstraction is fixed by this change as now the `<div>`
from ParserOutput no longer comes with a "please wrap it properly"
contract that Article subclasses couldn't possibly implement correctly
(it coudln't wrap it after the fact because Article::view writes to
OutputPage directly).
RECENT (T310445):
A special case was recently added for file pages about translated SVGs.
For those, we decide which language to use for the "fullMedia" thumb
atop the page. This was recently changed as part of T310445 from a
hardcoded $wgLanguageCode (site content lang) to new problematic
Title::getPageViewLanguage, which tries to guestimate the page
language of the rendered ParserOutput and then gets the preferred
variant for the current user. The motivation for this was to support
language variants but used Title::getPageViewLanguage as a kitchen
sink to achieve that minor side-effect. The only part of this
now-deprecated method that we actually need is
LanguageConverter::getPreferredVariant().
Test plan: Covered by ImagePageTest.
== Skin mainpage-title ==
RECENT (T331095, T298715):
A special case was added to Skin::getTemplateData that powers the
mainpage-title interface message feature. This is empty by default,
but when created via MediaWiki:mainpage-title allows interface admins
to replace the H1 with a custom and localised page heading.
A few months ago, in Ifc9f0a7174, Title::getPageViewLanguage was
applied here to support language variants. Replace with the same
fix as for ImagePage. Revert back to Message::inContentLanguage()
but refactor to inLanguage() via MediaWikiServices::getContentLanguage
so that LanguageConverter::getPreferredVariant can be applied.
== EditPage ==
This was doing similar "predicting" of the ParserOutput language to
create an empty preview placeholder for use by preview.js. Now that
ApiParse (via ParserOutput::getText) returns a usable element without
any secret "you magically know the right class, lang, and dir" contract,
this placeholder is no longer needed.
Test Plan:
* EditPage: Default preview
1. index.php?title=Main_Page&action=edit
2. Show preview
3. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
* EditPage: JS preview
1. Preferences > Editing > Show preview without reload
2. index.php?title=Main_Page&action=edit
3. Show preview
4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
5. Type something and 'Show preview' again
6. Assert old element gone, new text is shown, and new element
attributes are the same as the above.
== McrUndoAction ==
Same as EditPage basically, but without the JS preview use case.
== DifferenceEngine ==
Test:
1. Open /w/index.php?title=Main_Page&diff=0
(this shows the latest diff, can do manually by viewing
/wiki/Main_Page, click "View history", click "Compare selected revisions")
2. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
3. Open /w/index.php?title=Main_Page&diff=0&action=render
4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
== Special:ExpandTemplates ==
Test:
1. /wiki/Special:ExpandTemplates
2. Write "Hello".
3. "OK"
4. Assert <div class="mw-content-ltr mw-parser-output" lang=en dir=ltr>
Bug: T341244
Depends-On: Icd9c079f5896ee83d86b9c2699636dc81d25a14c
Depends-On: I4e7484b3b94f1cb6062e7cef9f20626b650bb4b1
Depends-On: I90b88f3b3a3bbeba4f48d118f92f54864997e105
Change-Id: Ib130a055e46764544af0f1a46d2bc2b3a7ee85b7
2023-10-04 04:45:07 +00:00
|
|
|
|
// This is null if the ParserOutput was cached by MW 1.40 or earlier,
|
|
|
|
|
|
// or not constructed by Parser/ParserCache.
|
Add ParserOutput::getLanguage()
Provide a way for backend code to determine the primary language of a
ParserOutput, eg for setting the Content-Language header of an API
response.
This is read-only and backed by extension data at the moment for
transition purposes; if this API sticks we'll graduate it to a
"real" property in the future, with appropriate serialization
to/from JSON (T303329).
Similarly, this patch only includes the most basic code to handle
the various ParserOutput merge cases in
ParserOutput::merge{Internal,Html,Tracking}MetaDataFrom(),
ParserOutput::collectMetadata(), and
OutputPage::addParserOutput{Content,Metadata,Text,}(); mostly
inherited from the fact that the storage is backed by extension
data at the moment.
Generally only the "top-level" parser output gets to set the
primary language; we'll presumably need to ensure that the
language is consistent during merge.
Change-Id: I767daba22805a877d9b806fd77334e508902844b
2022-09-28 21:21:13 +00:00
|
|
|
|
return $code === null ? null : new Bcp47CodeValue( $code );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Set the primary language of the output.
|
|
|
|
|
|
*
|
|
|
|
|
|
* See the discussion and caveats in ::getLanguage().
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param Bcp47Code $lang The primary language for this output, including
|
|
|
|
|
|
* any variant specification.
|
|
|
|
|
|
* @since 1.40
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function setLanguage( Bcp47Code $lang ): void {
|
|
|
|
|
|
$this->setExtensionData( 'core:target-lang-variant', $lang->toBcp47Code() );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2007-11-15 02:54:28 +00:00
|
|
|
|
/**
|
2023-09-11 15:56:47 +00:00
|
|
|
|
* Return an HTML prefix to be applied on redirect pages, or null
|
|
|
|
|
|
* if this is not a redirect.
|
|
|
|
|
|
* @return ?string HTML to prepend to redirect pages, or null
|
|
|
|
|
|
* @internal
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function getRedirectHeader(): ?string {
|
|
|
|
|
|
return $this->getExtensionData( 'core:redirect-header' );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Set an HTML prefix to be applied on redirect pages.
|
|
|
|
|
|
* @param string $html HTML to prepend to redirect pages
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function setRedirectHeader( string $html ): void {
|
|
|
|
|
|
$this->setExtensionData( 'core:redirect-header', $html );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
Add ParserOutput::{get,set}RenderId() and set render id in ContentRenderer
Set the render ID for each parse stored into cache so that we are able
to identify a specific parse when there are dependencies (for example
in an edit based on that parse). This is recorded as a property added
to the ParserOutput, not the parent CacheTime interface. Even though
the render ID is /related/ to the CacheTime interface, CacheTime is
also used directly as a parser cache key, and the UUID should not be
part of the lookup key.
In general we are trying to move the location where these cache
properties are set as early as possible, so we check at each location
to ensure we don't overwrite a previously-set value. Eventually we
can convert most of these checks into assertions that the cache
properties have already been set (T350538). The primary location for
setting cache properties is the ContentRenderer.
Moved setting the revision timestamp into ContentRenderer as well, as
it was set along the same code paths. An extra parameter was added to
ContentRenderer::getParserOutput() to support this.
Added merge code to ParserOutput::mergeInternalMetaDataFrom() which
should ensure that cache time, revision, timestamp, and render id are
all set properly when multiple slots are combined together in MCR.
In order to ensure the render ID is set on all codepaths we needed to
plumb the GlobalIdGenerator service into ContentRenderer, ParserCache,
ParserCacheFactory, and RevisionOutputCache. Eventually (T350538) it
should only be necessary in the ContentRenderer.
Bug: T350538
Bug: T349868
Followup-To: Ic9b7cc0fcf365e772b7d080d76a065e3fd585f80
Change-Id: I72c5e6f86b7f081ab5ce7a56f5365d2f75067a78
2023-09-14 16:11:20 +00:00
|
|
|
|
* Store a unique rendering id for this ParserOutput. This is used
|
|
|
|
|
|
* whenever a client needs to record a dependency on a specific parse.
|
|
|
|
|
|
* It is typically set only when a parser output is cached.
|
2023-08-29 20:13:43 +00:00
|
|
|
|
*
|
Add ParserOutput::{get,set}RenderId() and set render id in ContentRenderer
Set the render ID for each parse stored into cache so that we are able
to identify a specific parse when there are dependencies (for example
in an edit based on that parse). This is recorded as a property added
to the ParserOutput, not the parent CacheTime interface. Even though
the render ID is /related/ to the CacheTime interface, CacheTime is
also used directly as a parser cache key, and the UUID should not be
part of the lookup key.
In general we are trying to move the location where these cache
properties are set as early as possible, so we check at each location
to ensure we don't overwrite a previously-set value. Eventually we
can convert most of these checks into assertions that the cache
properties have already been set (T350538). The primary location for
setting cache properties is the ContentRenderer.
Moved setting the revision timestamp into ContentRenderer as well, as
it was set along the same code paths. An extra parameter was added to
ContentRenderer::getParserOutput() to support this.
Added merge code to ParserOutput::mergeInternalMetaDataFrom() which
should ensure that cache time, revision, timestamp, and render id are
all set properly when multiple slots are combined together in MCR.
In order to ensure the render ID is set on all codepaths we needed to
plumb the GlobalIdGenerator service into ContentRenderer, ParserCache,
ParserCacheFactory, and RevisionOutputCache. Eventually (T350538) it
should only be necessary in the ContentRenderer.
Bug: T350538
Bug: T349868
Followup-To: Ic9b7cc0fcf365e772b7d080d76a065e3fd585f80
Change-Id: I72c5e6f86b7f081ab5ce7a56f5365d2f75067a78
2023-09-14 16:11:20 +00:00
|
|
|
|
* @param string $renderId a UUID identifying a specific parse
|
|
|
|
|
|
* @internal
|
2023-08-29 20:13:43 +00:00
|
|
|
|
*/
|
Add ParserOutput::{get,set}RenderId() and set render id in ContentRenderer
Set the render ID for each parse stored into cache so that we are able
to identify a specific parse when there are dependencies (for example
in an edit based on that parse). This is recorded as a property added
to the ParserOutput, not the parent CacheTime interface. Even though
the render ID is /related/ to the CacheTime interface, CacheTime is
also used directly as a parser cache key, and the UUID should not be
part of the lookup key.
In general we are trying to move the location where these cache
properties are set as early as possible, so we check at each location
to ensure we don't overwrite a previously-set value. Eventually we
can convert most of these checks into assertions that the cache
properties have already been set (T350538). The primary location for
setting cache properties is the ContentRenderer.
Moved setting the revision timestamp into ContentRenderer as well, as
it was set along the same code paths. An extra parameter was added to
ContentRenderer::getParserOutput() to support this.
Added merge code to ParserOutput::mergeInternalMetaDataFrom() which
should ensure that cache time, revision, timestamp, and render id are
all set properly when multiple slots are combined together in MCR.
In order to ensure the render ID is set on all codepaths we needed to
plumb the GlobalIdGenerator service into ContentRenderer, ParserCache,
ParserCacheFactory, and RevisionOutputCache. Eventually (T350538) it
should only be necessary in the ContentRenderer.
Bug: T350538
Bug: T349868
Followup-To: Ic9b7cc0fcf365e772b7d080d76a065e3fd585f80
Change-Id: I72c5e6f86b7f081ab5ce7a56f5365d2f75067a78
2023-09-14 16:11:20 +00:00
|
|
|
|
public function setRenderId( string $renderId ): void {
|
|
|
|
|
|
$this->setExtensionData( 'core:render-id', $renderId );
|
2023-08-29 20:13:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
Add ParserOutput::{get,set}RenderId() and set render id in ContentRenderer
Set the render ID for each parse stored into cache so that we are able
to identify a specific parse when there are dependencies (for example
in an edit based on that parse). This is recorded as a property added
to the ParserOutput, not the parent CacheTime interface. Even though
the render ID is /related/ to the CacheTime interface, CacheTime is
also used directly as a parser cache key, and the UUID should not be
part of the lookup key.
In general we are trying to move the location where these cache
properties are set as early as possible, so we check at each location
to ensure we don't overwrite a previously-set value. Eventually we
can convert most of these checks into assertions that the cache
properties have already been set (T350538). The primary location for
setting cache properties is the ContentRenderer.
Moved setting the revision timestamp into ContentRenderer as well, as
it was set along the same code paths. An extra parameter was added to
ContentRenderer::getParserOutput() to support this.
Added merge code to ParserOutput::mergeInternalMetaDataFrom() which
should ensure that cache time, revision, timestamp, and render id are
all set properly when multiple slots are combined together in MCR.
In order to ensure the render ID is set on all codepaths we needed to
plumb the GlobalIdGenerator service into ContentRenderer, ParserCache,
ParserCacheFactory, and RevisionOutputCache. Eventually (T350538) it
should only be necessary in the ContentRenderer.
Bug: T350538
Bug: T349868
Followup-To: Ic9b7cc0fcf365e772b7d080d76a065e3fd585f80
Change-Id: I72c5e6f86b7f081ab5ce7a56f5365d2f75067a78
2023-09-14 16:11:20 +00:00
|
|
|
|
* Return the unique rendering id for this ParserOutput. This is used
|
|
|
|
|
|
* whenever a client needs to record a dependency on a specific parse.
|
2023-09-11 15:56:47 +00:00
|
|
|
|
*
|
2023-08-29 20:13:43 +00:00
|
|
|
|
* @return string|null
|
Add ParserOutput::{get,set}RenderId() and set render id in ContentRenderer
Set the render ID for each parse stored into cache so that we are able
to identify a specific parse when there are dependencies (for example
in an edit based on that parse). This is recorded as a property added
to the ParserOutput, not the parent CacheTime interface. Even though
the render ID is /related/ to the CacheTime interface, CacheTime is
also used directly as a parser cache key, and the UUID should not be
part of the lookup key.
In general we are trying to move the location where these cache
properties are set as early as possible, so we check at each location
to ensure we don't overwrite a previously-set value. Eventually we
can convert most of these checks into assertions that the cache
properties have already been set (T350538). The primary location for
setting cache properties is the ContentRenderer.
Moved setting the revision timestamp into ContentRenderer as well, as
it was set along the same code paths. An extra parameter was added to
ContentRenderer::getParserOutput() to support this.
Added merge code to ParserOutput::mergeInternalMetaDataFrom() which
should ensure that cache time, revision, timestamp, and render id are
all set properly when multiple slots are combined together in MCR.
In order to ensure the render ID is set on all codepaths we needed to
plumb the GlobalIdGenerator service into ContentRenderer, ParserCache,
ParserCacheFactory, and RevisionOutputCache. Eventually (T350538) it
should only be necessary in the ContentRenderer.
Bug: T350538
Bug: T349868
Followup-To: Ic9b7cc0fcf365e772b7d080d76a065e3fd585f80
Change-Id: I72c5e6f86b7f081ab5ce7a56f5365d2f75067a78
2023-09-14 16:11:20 +00:00
|
|
|
|
* @internal
|
2023-08-29 20:13:43 +00:00
|
|
|
|
*/
|
Add ParserOutput::{get,set}RenderId() and set render id in ContentRenderer
Set the render ID for each parse stored into cache so that we are able
to identify a specific parse when there are dependencies (for example
in an edit based on that parse). This is recorded as a property added
to the ParserOutput, not the parent CacheTime interface. Even though
the render ID is /related/ to the CacheTime interface, CacheTime is
also used directly as a parser cache key, and the UUID should not be
part of the lookup key.
In general we are trying to move the location where these cache
properties are set as early as possible, so we check at each location
to ensure we don't overwrite a previously-set value. Eventually we
can convert most of these checks into assertions that the cache
properties have already been set (T350538). The primary location for
setting cache properties is the ContentRenderer.
Moved setting the revision timestamp into ContentRenderer as well, as
it was set along the same code paths. An extra parameter was added to
ContentRenderer::getParserOutput() to support this.
Added merge code to ParserOutput::mergeInternalMetaDataFrom() which
should ensure that cache time, revision, timestamp, and render id are
all set properly when multiple slots are combined together in MCR.
In order to ensure the render ID is set on all codepaths we needed to
plumb the GlobalIdGenerator service into ContentRenderer, ParserCache,
ParserCacheFactory, and RevisionOutputCache. Eventually (T350538) it
should only be necessary in the ContentRenderer.
Bug: T350538
Bug: T349868
Followup-To: Ic9b7cc0fcf365e772b7d080d76a065e3fd585f80
Change-Id: I72c5e6f86b7f081ab5ce7a56f5365d2f75067a78
2023-09-14 16:11:20 +00:00
|
|
|
|
public function getRenderId(): ?string {
|
|
|
|
|
|
// Backward-compatibility with old cache contents
|
|
|
|
|
|
// Can be removed after parser cache contents have expired
|
|
|
|
|
|
$old = $this->getExtensionData( 'parsoid-render-id' );
|
|
|
|
|
|
if ( $old !== null ) {
|
|
|
|
|
|
return ParsoidRenderId::newFromKey( $old )->getUniqueID();
|
|
|
|
|
|
}
|
|
|
|
|
|
return $this->getExtensionData( 'core:render-id' );
|
2023-08-29 20:13:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-05-29 17:55:57 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @return string[] List of flags signifying special cases
|
2021-10-13 19:41:26 +00:00
|
|
|
|
* @internal
|
2019-05-29 17:55:57 +00:00
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function getAllFlags(): array {
|
2019-05-29 17:55:57 +00:00
|
|
|
|
return array_keys( $this->mFlags );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-07 16:13:46 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Set a page property to be stored in the page_props database table.
|
2012-10-22 00:58:05 +00:00
|
|
|
|
*
|
2024-02-16 15:03:40 +00:00
|
|
|
|
* page_props is a key-value store indexed by the page ID. This allows
|
2012-10-22 00:58:05 +00:00
|
|
|
|
* the parser to set a property on a page which can then be quickly
|
|
|
|
|
|
* retrieved given the page ID or via a DB join when given the page
|
|
|
|
|
|
* title.
|
|
|
|
|
|
*
|
2014-03-31 11:00:28 +00:00
|
|
|
|
* Since 1.23, page_props are also indexed by numeric value, to allow
|
|
|
|
|
|
* for efficient "top k" queries of pages wrt a given property.
|
2024-02-16 15:03:40 +00:00
|
|
|
|
* This only works if the value is passed as a int, float, or
|
2024-04-12 16:29:24 +00:00
|
|
|
|
* bool. Since 1.42 you should use ::setNumericPageProperty()
|
2024-04-12 10:09:02 +00:00
|
|
|
|
* if you want your page property value to be indexed, which will ensure
|
2024-02-16 15:12:12 +00:00
|
|
|
|
* that the value is of the proper type.
|
2014-03-31 11:00:28 +00:00
|
|
|
|
*
|
2021-10-07 16:13:46 +00:00
|
|
|
|
* setPageProperty() is thus used to propagate properties from the parsed
|
2012-10-22 00:58:05 +00:00
|
|
|
|
* page to request contexts other than a page view of the currently parsed
|
|
|
|
|
|
* article.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Some applications examples:
|
|
|
|
|
|
*
|
|
|
|
|
|
* * To implement hidden categories, hiding pages from category listings
|
2021-10-07 16:13:46 +00:00
|
|
|
|
* by storing a page property.
|
2012-10-22 00:58:05 +00:00
|
|
|
|
*
|
2018-05-19 20:46:54 +00:00
|
|
|
|
* * Overriding the displayed article title (ParserOutput::setDisplayTitle()).
|
2012-10-22 00:58:05 +00:00
|
|
|
|
*
|
|
|
|
|
|
* * To implement image tagging, for example displaying an icon on an
|
|
|
|
|
|
* image thumbnail to indicate that it is listed for deletion on
|
|
|
|
|
|
* Wikimedia Commons.
|
|
|
|
|
|
* This is not actually implemented, yet but would be pretty cool.
|
|
|
|
|
|
*
|
2024-02-16 15:03:40 +00:00
|
|
|
|
* @note Use of non-scalar values (anything other than
|
2024-02-16 15:23:22 +00:00
|
|
|
|
* `string|int|float|bool`) has been deprecated in 1.42.
|
2022-03-31 15:10:03 +00:00
|
|
|
|
* Although any JSON-serializable value can be stored/fetched in
|
|
|
|
|
|
* ParserOutput, when the values are stored to the database
|
2024-02-16 15:03:40 +00:00
|
|
|
|
* (in `deferred/LinksUpdate/PagePropsTable.php`) they will be
|
|
|
|
|
|
* converted: booleans will be converted to '0' and '1', null
|
|
|
|
|
|
* will become '', and everything else will be cast to string
|
|
|
|
|
|
* (not JSON-serialized). Page properties obtained from the
|
|
|
|
|
|
* PageProps service will thus always be strings.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @note The sort key stored in the database *will be NULL* unless
|
|
|
|
|
|
* the value passed here is an `int|float|bool`. If you *do not*
|
2024-04-12 16:29:24 +00:00
|
|
|
|
* want your property *value* indexed and sorted (for example, the
|
2024-02-16 15:03:40 +00:00
|
|
|
|
* value is a title string which can be numeric but only
|
|
|
|
|
|
* incidentally, like when it gets retrieved from an array key)
|
2024-02-16 15:12:12 +00:00
|
|
|
|
* be sure to cast to string or use
|
2024-04-12 16:29:24 +00:00
|
|
|
|
* `::setUnsortedPageProperty()`. If you *do* want your property
|
|
|
|
|
|
* *value* indexed and sorted, you should use
|
|
|
|
|
|
* `::setNumericPageProperty()` instead as this will ensure the
|
|
|
|
|
|
* value type is correct. Note that either way it is possible to
|
|
|
|
|
|
* efficiently look up all the pages with a certain property; we
|
|
|
|
|
|
* are only talking about sorting the *values* assigned to the
|
|
|
|
|
|
* property, for example for a "top N values of the property"
|
|
|
|
|
|
* query.
|
2024-02-16 15:03:40 +00:00
|
|
|
|
*
|
|
|
|
|
|
* @note Note that `::getPageProperty()`/`::setPageProperty()` do
|
|
|
|
|
|
* not do any conversions themselves; you should therefore be
|
|
|
|
|
|
* careful to distinguish values returned from the PageProp
|
|
|
|
|
|
* service (always strings) from values retrieved from a
|
|
|
|
|
|
* ParserOutput.
|
2022-03-31 15:10:03 +00:00
|
|
|
|
*
|
2021-10-07 16:13:46 +00:00
|
|
|
|
* @note Do not use setPageProperty() to set a property which is only used
|
2013-01-22 11:59:23 +00:00
|
|
|
|
* in a context where the ParserOutput object itself is already available,
|
|
|
|
|
|
* for example a normal page view. There is no need to save such a property
|
2024-02-16 15:03:40 +00:00
|
|
|
|
* in the database since the text is already parsed; use
|
|
|
|
|
|
* ::setExtensionData() instead.
|
2013-01-22 11:59:23 +00:00
|
|
|
|
*
|
|
|
|
|
|
* @par Example:
|
|
|
|
|
|
* @code
|
|
|
|
|
|
* $parser->getOutput()->setExtensionData( 'my_ext_foo', '...' );
|
|
|
|
|
|
* @endcode
|
|
|
|
|
|
*
|
|
|
|
|
|
* And then later, in OutputPageParserOutput or similar:
|
|
|
|
|
|
*
|
|
|
|
|
|
* @par Example:
|
|
|
|
|
|
* @code
|
|
|
|
|
|
* $output->getExtensionData( 'my_ext_foo' );
|
|
|
|
|
|
* @endcode
|
|
|
|
|
|
*
|
2024-02-16 15:23:22 +00:00
|
|
|
|
* @note The use of `null` as a value is deprecated since 1.42; use
|
|
|
|
|
|
* the empty string instead if you need a placeholder value, or
|
|
|
|
|
|
* ::unsetPageProperty() if you mean to remove a page property.
|
|
|
|
|
|
*
|
2024-02-16 15:12:12 +00:00
|
|
|
|
* @note The use of non-string values is deprecated since 1.42; if you
|
2024-04-12 16:29:24 +00:00
|
|
|
|
* need an page property value with a sort index
|
|
|
|
|
|
* use ::setNumericPageProperty().
|
2024-02-16 15:12:12 +00:00
|
|
|
|
*
|
2017-09-09 20:47:04 +00:00
|
|
|
|
* @param string $name
|
2024-05-11 18:10:35 +00:00
|
|
|
|
* @param ?scalar $value
|
2021-10-07 16:13:46 +00:00
|
|
|
|
* @since 1.38
|
2008-02-20 08:53:12 +00:00
|
|
|
|
*/
|
2021-10-07 16:13:46 +00:00
|
|
|
|
public function setPageProperty( string $name, $value ): void {
|
2024-09-05 16:26:37 +00:00
|
|
|
|
if ( $value === null ) {
|
|
|
|
|
|
// Use an empty string instead.
|
|
|
|
|
|
wfDeprecated( __METHOD__ . " with null value for $name", '1.42' );
|
|
|
|
|
|
} elseif ( !is_scalar( $value ) ) {
|
|
|
|
|
|
// Use ::setExtensionData() instead.
|
|
|
|
|
|
wfDeprecated( __METHOD__ . " with non-scalar value for $name", '1.42' );
|
|
|
|
|
|
} elseif ( !is_string( $value ) ) {
|
|
|
|
|
|
// Use ::setNumericPageProperty() instead.
|
|
|
|
|
|
wfDeprecated( __METHOD__ . " with non-string value for $name", '1.42' );
|
2024-02-16 15:23:22 +00:00
|
|
|
|
}
|
2008-02-20 08:53:12 +00:00
|
|
|
|
$this->mProperties[$name] = $value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-02-16 15:12:12 +00:00
|
|
|
|
/**
|
2024-04-12 16:29:24 +00:00
|
|
|
|
* Set a numeric page property whose *value* is intended to be sorted
|
|
|
|
|
|
* and indexed. The sort key used for the property will be the value,
|
|
|
|
|
|
* coerced to a number.
|
2024-02-16 15:12:12 +00:00
|
|
|
|
*
|
|
|
|
|
|
* See `::setPageProperty()` for details.
|
|
|
|
|
|
*
|
|
|
|
|
|
* In the future, we may allow the value to be specified independent
|
|
|
|
|
|
* of sort key (T357783).
|
|
|
|
|
|
*
|
2024-04-12 16:29:24 +00:00
|
|
|
|
* @param string $propName The name of the page property
|
|
|
|
|
|
* @param int|float|string $numericValue the numeric value
|
2024-02-16 15:12:12 +00:00
|
|
|
|
* @since 1.42
|
|
|
|
|
|
*/
|
2024-04-12 16:29:24 +00:00
|
|
|
|
public function setNumericPageProperty( string $propName, $numericValue ): void {
|
|
|
|
|
|
if ( !is_numeric( $numericValue ) ) {
|
|
|
|
|
|
throw new \TypeError( __METHOD__ . " with non-numeric value" );
|
|
|
|
|
|
}
|
|
|
|
|
|
// Coerce numeric sort key to a number.
|
|
|
|
|
|
$this->mProperties[$propName] = 0 + $numericValue;
|
2024-02-16 15:12:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2024-04-12 16:29:24 +00:00
|
|
|
|
* Set a page property whose *value* is not intended to be sorted and
|
|
|
|
|
|
* indexed.
|
2024-02-16 15:12:12 +00:00
|
|
|
|
*
|
2024-04-11 15:58:13 +00:00
|
|
|
|
* See `::setPageProperty()` for details. It is recommended to
|
2024-04-12 16:29:24 +00:00
|
|
|
|
* use the empty string if you need a placeholder value (ie, if
|
|
|
|
|
|
* it is the *presence* of the property which is important, not
|
|
|
|
|
|
* the *value* the property is set to).
|
2024-02-16 15:12:12 +00:00
|
|
|
|
*
|
2024-04-12 16:29:24 +00:00
|
|
|
|
* It is still possible to efficiently look up all the pages with
|
|
|
|
|
|
* a certain property (the "presence" of it *is* indexed; see
|
|
|
|
|
|
* Special:PagesWithProp, list=pageswithprop).
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param string $propName The name of the page property
|
2024-04-11 15:58:13 +00:00
|
|
|
|
* @param string $value Optional value; defaults to the empty string.
|
2024-02-16 15:12:12 +00:00
|
|
|
|
* @since 1.42
|
|
|
|
|
|
*/
|
2024-04-12 16:29:24 +00:00
|
|
|
|
public function setUnsortedPageProperty( string $propName, string $value = '' ): void {
|
|
|
|
|
|
$this->mProperties[$propName] = $value;
|
2024-02-16 15:12:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2014-04-16 14:13:46 +00:00
|
|
|
|
/**
|
2021-10-07 16:13:46 +00:00
|
|
|
|
* Look up a page property.
|
|
|
|
|
|
* @param string $name The page property name to look up.
|
2024-05-11 18:10:35 +00:00
|
|
|
|
* @return ?scalar The value previously set using
|
2024-04-12 16:29:24 +00:00
|
|
|
|
* ::setPageProperty(), ::setUnsortedPageProperty(), or
|
|
|
|
|
|
* ::setNumericPageProperty().
|
2022-02-16 22:03:26 +00:00
|
|
|
|
* Returns null if no value was set for the given property name.
|
2014-04-16 14:13:46 +00:00
|
|
|
|
*
|
2022-03-31 15:10:03 +00:00
|
|
|
|
* @note You would need to use ::getPageProperties() to test for an
|
|
|
|
|
|
* explicitly-set null value; but see the note in ::setPageProperty()
|
2024-02-16 15:23:22 +00:00
|
|
|
|
* deprecating the use of null values.
|
2021-10-07 16:13:46 +00:00
|
|
|
|
* @since 1.38
|
2014-04-16 14:13:46 +00:00
|
|
|
|
*/
|
2021-10-07 16:13:46 +00:00
|
|
|
|
public function getPageProperty( string $name ) {
|
2022-02-16 22:03:26 +00:00
|
|
|
|
return $this->mProperties[$name] ?? null;
|
2008-02-20 08:53:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-07 16:13:46 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Remove a page property.
|
|
|
|
|
|
* @param string $name The page property name.
|
|
|
|
|
|
* @since 1.38
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function unsetPageProperty( string $name ): void {
|
2014-04-16 09:31:29 +00:00
|
|
|
|
unset( $this->mProperties[$name] );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-07 16:13:46 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Return all the page properties set on this ParserOutput.
|
2024-05-11 18:10:35 +00:00
|
|
|
|
* @return array<string,?scalar>
|
2021-10-07 16:13:46 +00:00
|
|
|
|
* @since 1.38
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function getPageProperties(): array {
|
2008-02-20 08:53:12 +00:00
|
|
|
|
if ( !isset( $this->mProperties ) ) {
|
2016-02-17 09:09:32 +00:00
|
|
|
|
$this->mProperties = [];
|
2008-02-20 08:53:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
return $this->mProperties;
|
|
|
|
|
|
}
|
2011-08-01 15:40:02 +00:00
|
|
|
|
|
Add new ParserOutput::{get,set}OutputFlag() interface
This is a uniform mechanism to access a number of bespoke boolean
flags in ParserOutput. It allows extensibility in core (by adding new
field names to ParserOutputFlags) without exposing new getter/setter
methods to Parsoid. It replaces the ParserOutput::{get,set}Flag()
interface which (a) doesn't allow access to certain flags, and (b) is
typically called with a string rather than a constant, and (c) has a
very generic name. (Note that Parser::setOutputFlag() already called
these "output flags".)
In the future we might unify the representation so that we store
everything in $mFlags and don't have explicit properties in
ParserOutput, but those representation details should be invisible to
the clients of this API. (We might also use a proper enumeration
for ParserOutputFlags, when PHP supports this.)
There is some overlap with ParserOutput::{get,set}ExtensionData(), but
I've left those methods as-is because (a) they allow for non-boolean
data, unlike the *Flag() methods, and (b) it seems worthwhile to
distingush properties set by extensions from properties used by core.
Code search:
https://codesearch.wmcloud.org/search/?q=%5BOo%5Dut%28put%29%3F%28%5C%28%5C%29%29%3F-%3E%28g%7Cs%29etFlag%5C%28&i=nope&files=&excludeFiles=&repos=
Bug: T292868
Change-Id: I39bc58d207836df6f328c54be9e3330719cebbeb
2021-10-08 20:04:37 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Provides a uniform interface to various boolean flags stored
|
|
|
|
|
|
* in the ParserOutput. Flags internal to MediaWiki core should
|
|
|
|
|
|
* have names which are constants in ParserOutputFlags. Extensions
|
|
|
|
|
|
* should use ::setExtensionData() rather than creating new flags
|
|
|
|
|
|
* with ::setOutputFlag() in order to prevent namespace conflicts.
|
|
|
|
|
|
*
|
2022-03-30 16:45:00 +00:00
|
|
|
|
* Flags are always combined with OR. That is, the flag is set in
|
|
|
|
|
|
* the resulting ParserOutput if the flag is set in *any* of the
|
|
|
|
|
|
* fragments composing the ParserOutput.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @note The combination policy means that a ParserOutput may end
|
|
|
|
|
|
* up with both INDEX_POLICY and NO_INDEX_POLICY set. It is
|
|
|
|
|
|
* expected that NO_INDEX_POLICY "wins" in that case. (T16899)
|
|
|
|
|
|
* (This resolution is implemented in ::getIndexPolicy().)
|
|
|
|
|
|
*
|
Add new ParserOutput::{get,set}OutputFlag() interface
This is a uniform mechanism to access a number of bespoke boolean
flags in ParserOutput. It allows extensibility in core (by adding new
field names to ParserOutputFlags) without exposing new getter/setter
methods to Parsoid. It replaces the ParserOutput::{get,set}Flag()
interface which (a) doesn't allow access to certain flags, and (b) is
typically called with a string rather than a constant, and (c) has a
very generic name. (Note that Parser::setOutputFlag() already called
these "output flags".)
In the future we might unify the representation so that we store
everything in $mFlags and don't have explicit properties in
ParserOutput, but those representation details should be invisible to
the clients of this API. (We might also use a proper enumeration
for ParserOutputFlags, when PHP supports this.)
There is some overlap with ParserOutput::{get,set}ExtensionData(), but
I've left those methods as-is because (a) they allow for non-boolean
data, unlike the *Flag() methods, and (b) it seems worthwhile to
distingush properties set by extensions from properties used by core.
Code search:
https://codesearch.wmcloud.org/search/?q=%5BOo%5Dut%28put%29%3F%28%5C%28%5C%29%29%3F-%3E%28g%7Cs%29etFlag%5C%28&i=nope&files=&excludeFiles=&repos=
Bug: T292868
Change-Id: I39bc58d207836df6f328c54be9e3330719cebbeb
2021-10-08 20:04:37 +00:00
|
|
|
|
* @param string $name A flag name
|
|
|
|
|
|
* @param bool $val
|
|
|
|
|
|
* @since 1.38
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function setOutputFlag( string $name, bool $val = true ): void {
|
|
|
|
|
|
switch ( $name ) {
|
2024-03-10 22:26:24 +00:00
|
|
|
|
case ParserOutputFlags::NO_GALLERY:
|
|
|
|
|
|
$this->setNoGallery( $val );
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case ParserOutputFlags::ENABLE_OOUI:
|
|
|
|
|
|
$this->setEnableOOUI( $val );
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case ParserOutputFlags::NO_INDEX_POLICY:
|
|
|
|
|
|
$this->mNoIndexSet = $val;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case ParserOutputFlags::INDEX_POLICY:
|
|
|
|
|
|
$this->mIndexSet = $val;
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case ParserOutputFlags::NEW_SECTION:
|
|
|
|
|
|
$this->setNewSection( $val );
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case ParserOutputFlags::HIDE_NEW_SECTION:
|
|
|
|
|
|
$this->setHideNewSection( $val );
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case ParserOutputFlags::PREVENT_CLICKJACKING:
|
|
|
|
|
|
$this->setPreventClickjacking( $val );
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
if ( $val ) {
|
|
|
|
|
|
$this->mFlags[$name] = true;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
unset( $this->mFlags[$name] );
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
Add new ParserOutput::{get,set}OutputFlag() interface
This is a uniform mechanism to access a number of bespoke boolean
flags in ParserOutput. It allows extensibility in core (by adding new
field names to ParserOutputFlags) without exposing new getter/setter
methods to Parsoid. It replaces the ParserOutput::{get,set}Flag()
interface which (a) doesn't allow access to certain flags, and (b) is
typically called with a string rather than a constant, and (c) has a
very generic name. (Note that Parser::setOutputFlag() already called
these "output flags".)
In the future we might unify the representation so that we store
everything in $mFlags and don't have explicit properties in
ParserOutput, but those representation details should be invisible to
the clients of this API. (We might also use a proper enumeration
for ParserOutputFlags, when PHP supports this.)
There is some overlap with ParserOutput::{get,set}ExtensionData(), but
I've left those methods as-is because (a) they allow for non-boolean
data, unlike the *Flag() methods, and (b) it seems worthwhile to
distingush properties set by extensions from properties used by core.
Code search:
https://codesearch.wmcloud.org/search/?q=%5BOo%5Dut%28put%29%3F%28%5C%28%5C%29%29%3F-%3E%28g%7Cs%29etFlag%5C%28&i=nope&files=&excludeFiles=&repos=
Bug: T292868
Change-Id: I39bc58d207836df6f328c54be9e3330719cebbeb
2021-10-08 20:04:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Provides a uniform interface to various boolean flags stored
|
|
|
|
|
|
* in the ParserOutput. Flags internal to MediaWiki core should
|
|
|
|
|
|
* have names which are constants in ParserOutputFlags. Extensions
|
|
|
|
|
|
* should only use ::getOutputFlag() to query flags defined in
|
|
|
|
|
|
* ParserOutputFlags in core; they should use ::getExtensionData()
|
|
|
|
|
|
* to define their own flags.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param string $name A flag name
|
|
|
|
|
|
* @return bool The flag value
|
|
|
|
|
|
* @since 1.38
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function getOutputFlag( string $name ): bool {
|
|
|
|
|
|
switch ( $name ) {
|
2024-03-10 22:26:24 +00:00
|
|
|
|
case ParserOutputFlags::NO_GALLERY:
|
|
|
|
|
|
return $this->getNoGallery();
|
Add new ParserOutput::{get,set}OutputFlag() interface
This is a uniform mechanism to access a number of bespoke boolean
flags in ParserOutput. It allows extensibility in core (by adding new
field names to ParserOutputFlags) without exposing new getter/setter
methods to Parsoid. It replaces the ParserOutput::{get,set}Flag()
interface which (a) doesn't allow access to certain flags, and (b) is
typically called with a string rather than a constant, and (c) has a
very generic name. (Note that Parser::setOutputFlag() already called
these "output flags".)
In the future we might unify the representation so that we store
everything in $mFlags and don't have explicit properties in
ParserOutput, but those representation details should be invisible to
the clients of this API. (We might also use a proper enumeration
for ParserOutputFlags, when PHP supports this.)
There is some overlap with ParserOutput::{get,set}ExtensionData(), but
I've left those methods as-is because (a) they allow for non-boolean
data, unlike the *Flag() methods, and (b) it seems worthwhile to
distingush properties set by extensions from properties used by core.
Code search:
https://codesearch.wmcloud.org/search/?q=%5BOo%5Dut%28put%29%3F%28%5C%28%5C%29%29%3F-%3E%28g%7Cs%29etFlag%5C%28&i=nope&files=&excludeFiles=&repos=
Bug: T292868
Change-Id: I39bc58d207836df6f328c54be9e3330719cebbeb
2021-10-08 20:04:37 +00:00
|
|
|
|
|
2024-03-10 22:26:24 +00:00
|
|
|
|
case ParserOutputFlags::ENABLE_OOUI:
|
|
|
|
|
|
return $this->getEnableOOUI();
|
Add new ParserOutput::{get,set}OutputFlag() interface
This is a uniform mechanism to access a number of bespoke boolean
flags in ParserOutput. It allows extensibility in core (by adding new
field names to ParserOutputFlags) without exposing new getter/setter
methods to Parsoid. It replaces the ParserOutput::{get,set}Flag()
interface which (a) doesn't allow access to certain flags, and (b) is
typically called with a string rather than a constant, and (c) has a
very generic name. (Note that Parser::setOutputFlag() already called
these "output flags".)
In the future we might unify the representation so that we store
everything in $mFlags and don't have explicit properties in
ParserOutput, but those representation details should be invisible to
the clients of this API. (We might also use a proper enumeration
for ParserOutputFlags, when PHP supports this.)
There is some overlap with ParserOutput::{get,set}ExtensionData(), but
I've left those methods as-is because (a) they allow for non-boolean
data, unlike the *Flag() methods, and (b) it seems worthwhile to
distingush properties set by extensions from properties used by core.
Code search:
https://codesearch.wmcloud.org/search/?q=%5BOo%5Dut%28put%29%3F%28%5C%28%5C%29%29%3F-%3E%28g%7Cs%29etFlag%5C%28&i=nope&files=&excludeFiles=&repos=
Bug: T292868
Change-Id: I39bc58d207836df6f328c54be9e3330719cebbeb
2021-10-08 20:04:37 +00:00
|
|
|
|
|
2024-03-10 22:26:24 +00:00
|
|
|
|
case ParserOutputFlags::INDEX_POLICY:
|
|
|
|
|
|
return $this->mIndexSet;
|
Add new ParserOutput::{get,set}OutputFlag() interface
This is a uniform mechanism to access a number of bespoke boolean
flags in ParserOutput. It allows extensibility in core (by adding new
field names to ParserOutputFlags) without exposing new getter/setter
methods to Parsoid. It replaces the ParserOutput::{get,set}Flag()
interface which (a) doesn't allow access to certain flags, and (b) is
typically called with a string rather than a constant, and (c) has a
very generic name. (Note that Parser::setOutputFlag() already called
these "output flags".)
In the future we might unify the representation so that we store
everything in $mFlags and don't have explicit properties in
ParserOutput, but those representation details should be invisible to
the clients of this API. (We might also use a proper enumeration
for ParserOutputFlags, when PHP supports this.)
There is some overlap with ParserOutput::{get,set}ExtensionData(), but
I've left those methods as-is because (a) they allow for non-boolean
data, unlike the *Flag() methods, and (b) it seems worthwhile to
distingush properties set by extensions from properties used by core.
Code search:
https://codesearch.wmcloud.org/search/?q=%5BOo%5Dut%28put%29%3F%28%5C%28%5C%29%29%3F-%3E%28g%7Cs%29etFlag%5C%28&i=nope&files=&excludeFiles=&repos=
Bug: T292868
Change-Id: I39bc58d207836df6f328c54be9e3330719cebbeb
2021-10-08 20:04:37 +00:00
|
|
|
|
|
2024-03-10 22:26:24 +00:00
|
|
|
|
case ParserOutputFlags::NO_INDEX_POLICY:
|
|
|
|
|
|
return $this->mNoIndexSet;
|
Add new ParserOutput::{get,set}OutputFlag() interface
This is a uniform mechanism to access a number of bespoke boolean
flags in ParserOutput. It allows extensibility in core (by adding new
field names to ParserOutputFlags) without exposing new getter/setter
methods to Parsoid. It replaces the ParserOutput::{get,set}Flag()
interface which (a) doesn't allow access to certain flags, and (b) is
typically called with a string rather than a constant, and (c) has a
very generic name. (Note that Parser::setOutputFlag() already called
these "output flags".)
In the future we might unify the representation so that we store
everything in $mFlags and don't have explicit properties in
ParserOutput, but those representation details should be invisible to
the clients of this API. (We might also use a proper enumeration
for ParserOutputFlags, when PHP supports this.)
There is some overlap with ParserOutput::{get,set}ExtensionData(), but
I've left those methods as-is because (a) they allow for non-boolean
data, unlike the *Flag() methods, and (b) it seems worthwhile to
distingush properties set by extensions from properties used by core.
Code search:
https://codesearch.wmcloud.org/search/?q=%5BOo%5Dut%28put%29%3F%28%5C%28%5C%29%29%3F-%3E%28g%7Cs%29etFlag%5C%28&i=nope&files=&excludeFiles=&repos=
Bug: T292868
Change-Id: I39bc58d207836df6f328c54be9e3330719cebbeb
2021-10-08 20:04:37 +00:00
|
|
|
|
|
2024-03-10 22:26:24 +00:00
|
|
|
|
case ParserOutputFlags::NEW_SECTION:
|
|
|
|
|
|
return $this->getNewSection();
|
Add new ParserOutput::{get,set}OutputFlag() interface
This is a uniform mechanism to access a number of bespoke boolean
flags in ParserOutput. It allows extensibility in core (by adding new
field names to ParserOutputFlags) without exposing new getter/setter
methods to Parsoid. It replaces the ParserOutput::{get,set}Flag()
interface which (a) doesn't allow access to certain flags, and (b) is
typically called with a string rather than a constant, and (c) has a
very generic name. (Note that Parser::setOutputFlag() already called
these "output flags".)
In the future we might unify the representation so that we store
everything in $mFlags and don't have explicit properties in
ParserOutput, but those representation details should be invisible to
the clients of this API. (We might also use a proper enumeration
for ParserOutputFlags, when PHP supports this.)
There is some overlap with ParserOutput::{get,set}ExtensionData(), but
I've left those methods as-is because (a) they allow for non-boolean
data, unlike the *Flag() methods, and (b) it seems worthwhile to
distingush properties set by extensions from properties used by core.
Code search:
https://codesearch.wmcloud.org/search/?q=%5BOo%5Dut%28put%29%3F%28%5C%28%5C%29%29%3F-%3E%28g%7Cs%29etFlag%5C%28&i=nope&files=&excludeFiles=&repos=
Bug: T292868
Change-Id: I39bc58d207836df6f328c54be9e3330719cebbeb
2021-10-08 20:04:37 +00:00
|
|
|
|
|
2024-03-10 22:26:24 +00:00
|
|
|
|
case ParserOutputFlags::HIDE_NEW_SECTION:
|
|
|
|
|
|
return $this->getHideNewSection();
|
Add new ParserOutput::{get,set}OutputFlag() interface
This is a uniform mechanism to access a number of bespoke boolean
flags in ParserOutput. It allows extensibility in core (by adding new
field names to ParserOutputFlags) without exposing new getter/setter
methods to Parsoid. It replaces the ParserOutput::{get,set}Flag()
interface which (a) doesn't allow access to certain flags, and (b) is
typically called with a string rather than a constant, and (c) has a
very generic name. (Note that Parser::setOutputFlag() already called
these "output flags".)
In the future we might unify the representation so that we store
everything in $mFlags and don't have explicit properties in
ParserOutput, but those representation details should be invisible to
the clients of this API. (We might also use a proper enumeration
for ParserOutputFlags, when PHP supports this.)
There is some overlap with ParserOutput::{get,set}ExtensionData(), but
I've left those methods as-is because (a) they allow for non-boolean
data, unlike the *Flag() methods, and (b) it seems worthwhile to
distingush properties set by extensions from properties used by core.
Code search:
https://codesearch.wmcloud.org/search/?q=%5BOo%5Dut%28put%29%3F%28%5C%28%5C%29%29%3F-%3E%28g%7Cs%29etFlag%5C%28&i=nope&files=&excludeFiles=&repos=
Bug: T292868
Change-Id: I39bc58d207836df6f328c54be9e3330719cebbeb
2021-10-08 20:04:37 +00:00
|
|
|
|
|
2024-03-10 22:26:24 +00:00
|
|
|
|
case ParserOutputFlags::PREVENT_CLICKJACKING:
|
|
|
|
|
|
return $this->getPreventClickjacking();
|
Add new ParserOutput::{get,set}OutputFlag() interface
This is a uniform mechanism to access a number of bespoke boolean
flags in ParserOutput. It allows extensibility in core (by adding new
field names to ParserOutputFlags) without exposing new getter/setter
methods to Parsoid. It replaces the ParserOutput::{get,set}Flag()
interface which (a) doesn't allow access to certain flags, and (b) is
typically called with a string rather than a constant, and (c) has a
very generic name. (Note that Parser::setOutputFlag() already called
these "output flags".)
In the future we might unify the representation so that we store
everything in $mFlags and don't have explicit properties in
ParserOutput, but those representation details should be invisible to
the clients of this API. (We might also use a proper enumeration
for ParserOutputFlags, when PHP supports this.)
There is some overlap with ParserOutput::{get,set}ExtensionData(), but
I've left those methods as-is because (a) they allow for non-boolean
data, unlike the *Flag() methods, and (b) it seems worthwhile to
distingush properties set by extensions from properties used by core.
Code search:
https://codesearch.wmcloud.org/search/?q=%5BOo%5Dut%28put%29%3F%28%5C%28%5C%29%29%3F-%3E%28g%7Cs%29etFlag%5C%28&i=nope&files=&excludeFiles=&repos=
Bug: T292868
Change-Id: I39bc58d207836df6f328c54be9e3330719cebbeb
2021-10-08 20:04:37 +00:00
|
|
|
|
|
2024-03-10 22:26:24 +00:00
|
|
|
|
default:
|
|
|
|
|
|
return isset( $this->mFlags[$name] );
|
Add new ParserOutput::{get,set}OutputFlag() interface
This is a uniform mechanism to access a number of bespoke boolean
flags in ParserOutput. It allows extensibility in core (by adding new
field names to ParserOutputFlags) without exposing new getter/setter
methods to Parsoid. It replaces the ParserOutput::{get,set}Flag()
interface which (a) doesn't allow access to certain flags, and (b) is
typically called with a string rather than a constant, and (c) has a
very generic name. (Note that Parser::setOutputFlag() already called
these "output flags".)
In the future we might unify the representation so that we store
everything in $mFlags and don't have explicit properties in
ParserOutput, but those representation details should be invisible to
the clients of this API. (We might also use a proper enumeration
for ParserOutputFlags, when PHP supports this.)
There is some overlap with ParserOutput::{get,set}ExtensionData(), but
I've left those methods as-is because (a) they allow for non-boolean
data, unlike the *Flag() methods, and (b) it seems worthwhile to
distingush properties set by extensions from properties used by core.
Code search:
https://codesearch.wmcloud.org/search/?q=%5BOo%5Dut%28put%29%3F%28%5C%28%5C%29%29%3F-%3E%28g%7Cs%29etFlag%5C%28&i=nope&files=&excludeFiles=&repos=
Bug: T292868
Change-Id: I39bc58d207836df6f328c54be9e3330719cebbeb
2021-10-08 20:04:37 +00:00
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-07-25 17:10:54 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Provides a uniform interface to various string sets stored
|
|
|
|
|
|
* in the ParserOutput. String sets internal to MediaWiki core should
|
2023-07-27 19:47:00 +00:00
|
|
|
|
* have names which are constants in ParserOutputStringSets. Extensions
|
2023-07-25 17:10:54 +00:00
|
|
|
|
* should use ::appendExtensionData() rather than creating new string sets
|
2023-07-27 19:47:00 +00:00
|
|
|
|
* with ::appendOutputStrings() in order to prevent namespace conflicts.
|
2023-07-25 17:10:54 +00:00
|
|
|
|
*
|
|
|
|
|
|
* @param string $name A string set name
|
|
|
|
|
|
* @param string[] $value
|
|
|
|
|
|
* @since 1.41
|
|
|
|
|
|
*/
|
2023-07-27 19:47:00 +00:00
|
|
|
|
public function appendOutputStrings( string $name, array $value ): void {
|
2023-07-25 17:10:54 +00:00
|
|
|
|
switch ( $name ) {
|
2023-07-27 19:47:00 +00:00
|
|
|
|
case ParserOutputStringSets::MODULE:
|
|
|
|
|
|
$this->addModules( $value );
|
|
|
|
|
|
break;
|
|
|
|
|
|
case ParserOutputStringSets::MODULE_STYLE:
|
|
|
|
|
|
$this->addModuleStyles( $value );
|
|
|
|
|
|
break;
|
|
|
|
|
|
case ParserOutputStringSets::EXTRA_CSP_DEFAULT_SRC:
|
2023-07-25 17:10:54 +00:00
|
|
|
|
foreach ( $value as $v ) {
|
|
|
|
|
|
$this->addExtraCSPDefaultSrc( $v );
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
2023-07-27 19:47:00 +00:00
|
|
|
|
case ParserOutputStringSets::EXTRA_CSP_SCRIPT_SRC:
|
2023-07-25 17:10:54 +00:00
|
|
|
|
foreach ( $value as $v ) {
|
|
|
|
|
|
$this->addExtraCSPScriptSrc( $v );
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
2023-07-27 19:47:00 +00:00
|
|
|
|
case ParserOutputStringSets::EXTRA_CSP_STYLE_SRC:
|
2023-07-25 17:10:54 +00:00
|
|
|
|
foreach ( $value as $v ) {
|
|
|
|
|
|
$this->addExtraCSPStyleSrc( $v );
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw new UnexpectedValueException( "Unknown output string set name $name" );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Provides a uniform interface to various boolean string sets stored
|
|
|
|
|
|
* in the ParserOutput. String sets internal to MediaWiki core should
|
2023-07-27 19:47:00 +00:00
|
|
|
|
* have names which are constants in ParserOutputStringSets. Extensions
|
|
|
|
|
|
* should only use ::getOutputStrings() to query string sets defined in
|
|
|
|
|
|
* ParserOutputStringSets in core; they should use ::appendExtensionData()
|
2023-07-25 17:10:54 +00:00
|
|
|
|
* to define their own string sets.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param string $name A string set name
|
|
|
|
|
|
* @return string[] The string set value
|
|
|
|
|
|
* @since 1.41
|
|
|
|
|
|
*/
|
2023-07-27 19:47:00 +00:00
|
|
|
|
public function getOutputStrings( string $name ): array {
|
2023-07-25 17:10:54 +00:00
|
|
|
|
switch ( $name ) {
|
2023-07-27 19:47:00 +00:00
|
|
|
|
case ParserOutputStringSets::MODULE:
|
|
|
|
|
|
return $this->getModules();
|
|
|
|
|
|
case ParserOutputStringSets::MODULE_STYLE:
|
|
|
|
|
|
return $this->getModuleStyles();
|
|
|
|
|
|
case ParserOutputStringSets::EXTRA_CSP_DEFAULT_SRC:
|
2023-07-25 17:10:54 +00:00
|
|
|
|
return $this->getExtraCSPDefaultSrcs();
|
2023-07-27 19:47:00 +00:00
|
|
|
|
case ParserOutputStringSets::EXTRA_CSP_SCRIPT_SRC:
|
2023-07-25 17:10:54 +00:00
|
|
|
|
return $this->getExtraCSPScriptSrcs();
|
2023-07-27 19:47:00 +00:00
|
|
|
|
case ParserOutputStringSets::EXTRA_CSP_STYLE_SRC:
|
2023-07-25 17:10:54 +00:00
|
|
|
|
return $this->getExtraCSPStyleSrcs();
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw new UnexpectedValueException( "Unknown output string set name $name" );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2013-01-22 11:59:23 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Attaches arbitrary data to this ParserObject. This can be used to store some information in
|
|
|
|
|
|
* the ParserOutput object for later use during page output. The data will be cached along with
|
2021-10-07 16:13:46 +00:00
|
|
|
|
* the ParserOutput object, but unlike data set using setPageProperty(), it is not recorded in the
|
2013-01-22 11:59:23 +00:00
|
|
|
|
* database.
|
|
|
|
|
|
*
|
|
|
|
|
|
* This method is provided to overcome the unsafe practice of attaching extra information to a
|
|
|
|
|
|
* ParserObject by directly assigning member variables.
|
|
|
|
|
|
*
|
|
|
|
|
|
* To use setExtensionData() to pass extension information from a hook inside the parser to a
|
|
|
|
|
|
* hook in the page output, use this in the parser hook:
|
|
|
|
|
|
*
|
|
|
|
|
|
* @par Example:
|
|
|
|
|
|
* @code
|
|
|
|
|
|
* $parser->getOutput()->setExtensionData( 'my_ext_foo', '...' );
|
|
|
|
|
|
* @endcode
|
|
|
|
|
|
*
|
|
|
|
|
|
* And then later, in OutputPageParserOutput or similar:
|
|
|
|
|
|
*
|
|
|
|
|
|
* @par Example:
|
|
|
|
|
|
* @code
|
|
|
|
|
|
* $output->getExtensionData( 'my_ext_foo' );
|
|
|
|
|
|
* @endcode
|
|
|
|
|
|
*
|
|
|
|
|
|
* In MediaWiki 1.20 and older, you have to use a custom member variable
|
|
|
|
|
|
* within the ParserOutput object:
|
|
|
|
|
|
*
|
|
|
|
|
|
* @par Example:
|
|
|
|
|
|
* @code
|
|
|
|
|
|
* $parser->getOutput()->my_ext_foo = '...';
|
|
|
|
|
|
* @endcode
|
|
|
|
|
|
*
|
2024-05-04 08:58:57 +00:00
|
|
|
|
* @note Only scalar values, e.g. numbers, strings, arrays or MediaWiki\Json\JsonDeserializable
|
2022-05-16 12:43:23 +00:00
|
|
|
|
* instances are supported as a value. Attempt to set other class instance as extension data
|
2020-10-23 00:17:31 +00:00
|
|
|
|
* will break ParserCache for the page.
|
2013-01-22 11:59:23 +00:00
|
|
|
|
*
|
2022-01-28 17:09:22 +00:00
|
|
|
|
* @note Since MW 1.38 the practice of setting conflicting values for
|
2022-05-16 12:43:23 +00:00
|
|
|
|
* the same key has been deprecated. As with ::setJsConfigVar(), if
|
2022-01-28 17:09:22 +00:00
|
|
|
|
* you set the same key multiple times on a ParserOutput, it is expected
|
|
|
|
|
|
* that the value will be identical each time. If you want to collect
|
|
|
|
|
|
* multiple pieces of data under a single key, use ::appendExtensionData().
|
|
|
|
|
|
*
|
2013-01-22 11:59:23 +00:00
|
|
|
|
* @param string $key The key for accessing the data. Extensions should take care to avoid
|
2014-04-21 23:38:39 +00:00
|
|
|
|
* conflicts in naming keys. It is suggested to use the extension's name as a prefix.
|
2013-01-22 11:59:23 +00:00
|
|
|
|
*
|
2024-05-04 08:58:57 +00:00
|
|
|
|
* @param mixed|JsonDeserializable $value The value to set.
|
2020-10-23 00:17:31 +00:00
|
|
|
|
* Setting a value to null is equivalent to removing the value.
|
|
|
|
|
|
* @since 1.21
|
2013-01-22 11:59:23 +00:00
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function setExtensionData( $key, $value ): void {
|
2022-01-28 17:09:22 +00:00
|
|
|
|
if (
|
|
|
|
|
|
array_key_exists( $key, $this->mExtensionData ) &&
|
|
|
|
|
|
$this->mExtensionData[$key] !== $value
|
|
|
|
|
|
) {
|
|
|
|
|
|
// This behavior was deprecated in 1.38. We will eventually
|
|
|
|
|
|
// emit a warning here, then throw an exception.
|
|
|
|
|
|
}
|
2013-01-22 11:59:23 +00:00
|
|
|
|
if ( $value === null ) {
|
|
|
|
|
|
unset( $this->mExtensionData[$key] );
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$this->mExtensionData[$key] = $value;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-01-28 17:09:22 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Appends arbitrary data to this ParserObject. This can be used
|
|
|
|
|
|
* to store some information in the ParserOutput object for later
|
|
|
|
|
|
* use during page output. The data will be cached along with the
|
|
|
|
|
|
* ParserOutput object, but unlike data set using
|
|
|
|
|
|
* setPageProperty(), it is not recorded in the database.
|
|
|
|
|
|
*
|
|
|
|
|
|
* See ::setExtensionData() for more details on rationale and use.
|
|
|
|
|
|
*
|
|
|
|
|
|
* In order to provide for out-of-order/asynchronous/incremental
|
|
|
|
|
|
* parsing, this method appends values to a set. See
|
|
|
|
|
|
* ::setExtensionData() for the flag-like version of this method.
|
|
|
|
|
|
*
|
2022-02-07 17:16:23 +00:00
|
|
|
|
* @note Only values which can be array keys are currently supported
|
|
|
|
|
|
* as values.
|
2022-01-28 17:09:22 +00:00
|
|
|
|
*
|
|
|
|
|
|
* @param string $key The key for accessing the data. Extensions should take care to avoid
|
|
|
|
|
|
* conflicts in naming keys. It is suggested to use the extension's name as a prefix.
|
|
|
|
|
|
*
|
2022-02-07 17:16:23 +00:00
|
|
|
|
* @param int|string $value The value to append to the list.
|
2022-02-07 16:28:23 +00:00
|
|
|
|
* @param string $strategy Merge strategy:
|
|
|
|
|
|
* only MW_MERGE_STRATEGY_UNION is currently supported and external callers
|
|
|
|
|
|
* should treat this parameter as @internal at this time and omit it.
|
2022-01-28 17:09:22 +00:00
|
|
|
|
* @since 1.38
|
|
|
|
|
|
*/
|
2022-02-07 16:28:23 +00:00
|
|
|
|
public function appendExtensionData(
|
|
|
|
|
|
string $key,
|
|
|
|
|
|
$value,
|
|
|
|
|
|
string $strategy = self::MW_MERGE_STRATEGY_UNION
|
|
|
|
|
|
): void {
|
|
|
|
|
|
if ( $strategy !== self::MW_MERGE_STRATEGY_UNION ) {
|
|
|
|
|
|
throw new InvalidArgumentException( "Unknown merge strategy $strategy." );
|
|
|
|
|
|
}
|
2022-01-28 17:09:22 +00:00
|
|
|
|
if ( !array_key_exists( $key, $this->mExtensionData ) ) {
|
|
|
|
|
|
$this->mExtensionData[$key] = [
|
|
|
|
|
|
// Indicate how these values are to be merged.
|
2022-02-07 16:28:23 +00:00
|
|
|
|
self::MW_MERGE_STRATEGY_KEY => $strategy,
|
2022-01-28 17:09:22 +00:00
|
|
|
|
];
|
|
|
|
|
|
} elseif ( !is_array( $this->mExtensionData[$key] ) ) {
|
|
|
|
|
|
throw new InvalidArgumentException( "Mixing set and append for $key" );
|
2022-02-07 16:28:23 +00:00
|
|
|
|
} elseif ( ( $this->mExtensionData[$key][self::MW_MERGE_STRATEGY_KEY] ?? null ) !== $strategy ) {
|
|
|
|
|
|
throw new InvalidArgumentException( "Conflicting merge strategies for $key" );
|
2022-01-28 17:09:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
$this->mExtensionData[$key][$value] = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2013-01-22 11:59:23 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Gets extensions data previously attached to this ParserOutput using setExtensionData().
|
|
|
|
|
|
* Typically, such data would be set while parsing the page, e.g. by a parser function.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @since 1.21
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param string $key The key to look up.
|
|
|
|
|
|
*
|
2014-04-16 14:13:46 +00:00
|
|
|
|
* @return mixed|null The value previously set for the given key using setExtensionData()
|
2013-01-22 11:59:23 +00:00
|
|
|
|
* or null if no value was set for this key.
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function getExtensionData( $key ) {
|
2022-01-28 17:09:22 +00:00
|
|
|
|
$value = $this->mExtensionData[$key] ?? null;
|
|
|
|
|
|
if ( is_array( $value ) ) {
|
|
|
|
|
|
// Don't expose our internal merge strategy key.
|
2022-02-07 16:28:23 +00:00
|
|
|
|
unset( $value[self::MW_MERGE_STRATEGY_KEY] );
|
2022-01-28 17:09:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
return $value;
|
2013-01-22 11:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-05 00:34:25 +00:00
|
|
|
|
private static function getTimes( $clock = null ): array {
|
2016-02-17 09:09:32 +00:00
|
|
|
|
$ret = [];
|
2013-03-14 12:43:06 +00:00
|
|
|
|
if ( !$clock || $clock === 'wall' ) {
|
|
|
|
|
|
$ret['wall'] = microtime( true );
|
|
|
|
|
|
}
|
2014-09-04 23:56:20 +00:00
|
|
|
|
if ( !$clock || $clock === 'cpu' ) {
|
2019-10-22 10:32:06 +00:00
|
|
|
|
$ru = getrusage( 0 /* RUSAGE_SELF */ );
|
|
|
|
|
|
$ret['cpu'] = $ru['ru_utime.tv_sec'] + $ru['ru_utime.tv_usec'] / 1e6;
|
|
|
|
|
|
$ret['cpu'] += $ru['ru_stime.tv_sec'] + $ru['ru_stime.tv_usec'] / 1e6;
|
2013-03-14 12:43:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
return $ret;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Resets the parse start timestamps for future calls to getTimeSinceStart()
|
2023-09-28 15:03:33 +00:00
|
|
|
|
* and recordTimeProfile().
|
|
|
|
|
|
*
|
2013-03-14 12:43:06 +00:00
|
|
|
|
* @since 1.22
|
|
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function resetParseStartTime(): void {
|
2013-03-14 12:43:06 +00:00
|
|
|
|
$this->mParseStartTime = self::getTimes();
|
2023-09-28 15:03:33 +00:00
|
|
|
|
$this->mTimeProfile = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-03 22:56:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Unset the parse start time.
|
|
|
|
|
|
*
|
|
|
|
|
|
* This is intended for testing purposes only, in order to avoid
|
|
|
|
|
|
* spurious differences between testing outputs created at different
|
|
|
|
|
|
* times.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @since 1.43
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function clearParseStartTime(): void {
|
|
|
|
|
|
$this->mParseStartTime = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-09-28 15:03:33 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Record the time since resetParseStartTime() was last called.
|
|
|
|
|
|
* The recorded time can be accessed using getTimeProfile().
|
|
|
|
|
|
*
|
|
|
|
|
|
* After resetParseStartTime() was called, the first call to recordTimeProfile()
|
|
|
|
|
|
* will record the time profile. Subsequent calls to recordTimeProfile() will have
|
|
|
|
|
|
* no effect until resetParseStartTime() is called again.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @since 1.42
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function recordTimeProfile() {
|
|
|
|
|
|
if ( !$this->mParseStartTime ) {
|
|
|
|
|
|
// If resetParseStartTime was never called, there is nothing to record
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ( $this->mTimeProfile !== [] ) {
|
|
|
|
|
|
// Don't override the times recorded by the previous call to recordTimeProfile().
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$now = self::getTimes();
|
|
|
|
|
|
$this->mTimeProfile = [
|
|
|
|
|
|
'wall' => $now['wall'] - $this->mParseStartTime['wall'],
|
|
|
|
|
|
'cpu' => $now['cpu'] - $this->mParseStartTime['cpu'],
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Returns the time that elapsed between the most recent call to resetParseStartTime()
|
|
|
|
|
|
* and the first call to recordTimeProfile() after that.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Clocks available are:
|
|
|
|
|
|
* - wall: Wall clock time
|
|
|
|
|
|
* - cpu: CPU time (requires getrusage)
|
|
|
|
|
|
*
|
|
|
|
|
|
* If recordTimeProfile() has noit been called since the most recent call to
|
|
|
|
|
|
* resetParseStartTime(), or if resetParseStartTime() was never called, then
|
|
|
|
|
|
* this method will return null.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param string $clock
|
|
|
|
|
|
*
|
|
|
|
|
|
* @since 1.42
|
|
|
|
|
|
* @return float|null
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function getTimeProfile( string $clock ) {
|
|
|
|
|
|
return $this->mTimeProfile[ $clock ] ?? null;
|
2013-03-14 12:43:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Returns the time since resetParseStartTime() was last called
|
|
|
|
|
|
*
|
|
|
|
|
|
* Clocks available are:
|
|
|
|
|
|
* - wall: Wall clock time
|
|
|
|
|
|
* - cpu: CPU time (requires getrusage)
|
|
|
|
|
|
*
|
|
|
|
|
|
* @since 1.22
|
2023-09-28 15:03:33 +00:00
|
|
|
|
* @deprecated since 1.42, use getTimeProfile() instead.
|
2013-03-14 12:43:06 +00:00
|
|
|
|
* @param string $clock
|
|
|
|
|
|
* @return float|null
|
|
|
|
|
|
*/
|
2014-08-11 20:24:54 +00:00
|
|
|
|
public function getTimeSinceStart( $clock ) {
|
2023-09-28 15:03:33 +00:00
|
|
|
|
wfDeprecated( __METHOD__, '1.42' );
|
|
|
|
|
|
|
2013-03-14 12:43:06 +00:00
|
|
|
|
if ( !isset( $this->mParseStartTime[$clock] ) ) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$end = self::getTimes( $clock );
|
|
|
|
|
|
return $end[$clock] - $this->mParseStartTime[$clock];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Sets parser limit report data for a key
|
|
|
|
|
|
*
|
2016-11-08 21:03:21 +00:00
|
|
|
|
* The key is used as the prefix for various messages used for formatting:
|
|
|
|
|
|
* - $key: The label for the field in the limit report
|
|
|
|
|
|
* - $key-value-text: Message used to format the value in the "NewPP limit
|
|
|
|
|
|
* report" HTML comment. If missing, uses $key-format.
|
|
|
|
|
|
* - $key-value-html: Message used to format the value in the preview
|
|
|
|
|
|
* limit report table. If missing, uses $key-format.
|
|
|
|
|
|
* - $key-value: Message used to format the value. If missing, uses "$1".
|
|
|
|
|
|
*
|
|
|
|
|
|
* Note that all values are interpreted as wikitext, and so should be
|
|
|
|
|
|
* encoded with htmlspecialchars() as necessary, but should avoid complex
|
2021-11-19 23:19:42 +00:00
|
|
|
|
* HTML for display in the "NewPP limit report" comment.
|
2013-03-14 12:43:06 +00:00
|
|
|
|
*
|
|
|
|
|
|
* @since 1.22
|
2016-11-08 21:03:21 +00:00
|
|
|
|
* @param string $key Message key
|
|
|
|
|
|
* @param mixed $value Appropriate for Message::params()
|
2013-03-14 12:43:06 +00:00
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function setLimitReportData( $key, $value ): void {
|
2016-11-08 21:03:21 +00:00
|
|
|
|
$this->mLimitReportData[$key] = $value;
|
2016-11-10 20:29:27 +00:00
|
|
|
|
|
|
|
|
|
|
if ( is_array( $value ) ) {
|
|
|
|
|
|
if ( array_keys( $value ) === [ 0, 1 ]
|
|
|
|
|
|
&& is_numeric( $value[0] )
|
|
|
|
|
|
&& is_numeric( $value[1] )
|
|
|
|
|
|
) {
|
|
|
|
|
|
$data = [ 'value' => $value[0], 'limit' => $value[1] ];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$data = $value;
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$data = $value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ( strpos( $key, '-' ) ) {
|
|
|
|
|
|
[ $ns, $name ] = explode( '-', $key, 2 );
|
|
|
|
|
|
$this->mLimitReportJSData[$ns][$name] = $data;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$this->mLimitReportJSData[$key] = $data;
|
|
|
|
|
|
}
|
2013-03-14 12:43:06 +00:00
|
|
|
|
}
|
2014-03-18 20:20:09 +00:00
|
|
|
|
|
2015-02-12 23:03:24 +00:00
|
|
|
|
/**
|
WikiPage: Document triggerOpportunisticLinksUpdate and related code
== History of WikiPage::triggerOpportunisticLinksUpdate ==
* 2007 (r19095; T10575; b3a8d488a8)
Introduces the "cascading protection" feature.
This commit added code to Article.php, in a conditional branch
where we encountered a ParserCache "miss" and thus have done a
fresh parse. The code in question would query which templates
we ended up using, and if that differed from what the database
said (e.g. stored during the last actual edit or links update),
then a new LinksUpdate is ad-hoc constructed and executed.
I could not find it anywhere explicitly spelled out, but my best
guess is that the reason for this is to make sure that if the page
in question contains wikitext that trancludes a different page based
on the current date and time (such as how most Wikipedia main pages
transclude news information and "Did you know" information based on
dated subpages that are prepared in advance), then we don't just
want to re-render the page after a day has passed, we also want to
re-do the links update to ensure the search index, category links,
and "WhatLinksHere" is correct, and thus by extent, to make sure
that cascading protection from the main page does in fact apply
to the "current" set of subpages and templates actually in-use.
* 2007 (r19227; 0c0c0eff81)
This adds an optimisation to the added logic that limits it to
pages that satisfy `mTitle->areRestrictionsCascading()`.
Thus for most articles, which aren't protected at all, we don't
run LinksUpdate mid-request after a cache miss page view.
Because of this commit, the pre-2007 status quo remained unaltered
and has remains unaltered to this very day: We don't re-index
categories and WhatLinksHere etc, unless an article edit or
propagating template edit takes place.
* 2009 (r52888; 1353a8ba29)
Introduces the PoolCounter feature.
The logic in question moves to Article::doCascadeProtectionUpdates().
* 2015 (Iea952d4d2e66; df5ef8b5d7).
The logic in question is changed, motivated by wanting to avoid
DB writes during page views.
* Instead of executing LinksUpdate mid-request, we now queue a
RefreshLinksJob on the JobQueue, and utilize a newly added
`prioritize => true` parameter.
This commit also introduces a new feature, which is to queue
RefreshLinksJob also for pages that do not have cascading
protection, but that do satisfy a new boolean method
called `$parserOutput->hasDynamicContent()`, which is set when
the Parser encounters TTL-reducing magic words and functions
such as {{CURRENTDAY}} and {{#time}}. For this new case, however,
the `prioritize` parameter is not set, and this feature is disabled
in WMF production (and other farms that enable wgMiserMode).
This commit also renamed doCascadeProtectionUpdates()
to triggerOpportunisticLinksUpdate().
This commit also removed various documentation comments, which
I've partly restored in this patch, the patch you're looking at
now.
== Actual changes ==
* Rename hasDynamicContent() to hasReducedExpiry() and keep the
previous method as a non-deprecated wrapper.
This change is motivated by T280605, in which I intent to make use
of a Parser hook that reduces the cache expiry. There are numerous
extensions in WMF production that already do this, and thus the
assumption that these have "dynamic content" is already false in
some cases. I'm not yet sure how or if to refactor this so to allow
reducing of the TTL *without* causing this side-effect, but as a
first step we can make the method more obvious in its impact
and behaviour.
I've also updated two of the callers that I think will benefit from
this more explicit name and (current) implementation detail.
Bug: T280605
Change-Id: I85bdff7f86911f8ea5b866e3639f08ddd3f3bf6f
2021-05-05 01:03:16 +00:00
|
|
|
|
* Check whether the cache TTL was lowered from the site default.
|
2015-02-12 23:03:24 +00:00
|
|
|
|
*
|
|
|
|
|
|
* When content is determined by more than hard state (e.g. page edits),
|
|
|
|
|
|
* such as template/file transclusions based on the current timestamp or
|
|
|
|
|
|
* extension tags that generate lists based on queries, this return true.
|
|
|
|
|
|
*
|
WikiPage: Document triggerOpportunisticLinksUpdate and related code
== History of WikiPage::triggerOpportunisticLinksUpdate ==
* 2007 (r19095; T10575; b3a8d488a8)
Introduces the "cascading protection" feature.
This commit added code to Article.php, in a conditional branch
where we encountered a ParserCache "miss" and thus have done a
fresh parse. The code in question would query which templates
we ended up using, and if that differed from what the database
said (e.g. stored during the last actual edit or links update),
then a new LinksUpdate is ad-hoc constructed and executed.
I could not find it anywhere explicitly spelled out, but my best
guess is that the reason for this is to make sure that if the page
in question contains wikitext that trancludes a different page based
on the current date and time (such as how most Wikipedia main pages
transclude news information and "Did you know" information based on
dated subpages that are prepared in advance), then we don't just
want to re-render the page after a day has passed, we also want to
re-do the links update to ensure the search index, category links,
and "WhatLinksHere" is correct, and thus by extent, to make sure
that cascading protection from the main page does in fact apply
to the "current" set of subpages and templates actually in-use.
* 2007 (r19227; 0c0c0eff81)
This adds an optimisation to the added logic that limits it to
pages that satisfy `mTitle->areRestrictionsCascading()`.
Thus for most articles, which aren't protected at all, we don't
run LinksUpdate mid-request after a cache miss page view.
Because of this commit, the pre-2007 status quo remained unaltered
and has remains unaltered to this very day: We don't re-index
categories and WhatLinksHere etc, unless an article edit or
propagating template edit takes place.
* 2009 (r52888; 1353a8ba29)
Introduces the PoolCounter feature.
The logic in question moves to Article::doCascadeProtectionUpdates().
* 2015 (Iea952d4d2e66; df5ef8b5d7).
The logic in question is changed, motivated by wanting to avoid
DB writes during page views.
* Instead of executing LinksUpdate mid-request, we now queue a
RefreshLinksJob on the JobQueue, and utilize a newly added
`prioritize => true` parameter.
This commit also introduces a new feature, which is to queue
RefreshLinksJob also for pages that do not have cascading
protection, but that do satisfy a new boolean method
called `$parserOutput->hasDynamicContent()`, which is set when
the Parser encounters TTL-reducing magic words and functions
such as {{CURRENTDAY}} and {{#time}}. For this new case, however,
the `prioritize` parameter is not set, and this feature is disabled
in WMF production (and other farms that enable wgMiserMode).
This commit also renamed doCascadeProtectionUpdates()
to triggerOpportunisticLinksUpdate().
This commit also removed various documentation comments, which
I've partly restored in this patch, the patch you're looking at
now.
== Actual changes ==
* Rename hasDynamicContent() to hasReducedExpiry() and keep the
previous method as a non-deprecated wrapper.
This change is motivated by T280605, in which I intent to make use
of a Parser hook that reduces the cache expiry. There are numerous
extensions in WMF production that already do this, and thus the
assumption that these have "dynamic content" is already false in
some cases. I'm not yet sure how or if to refactor this so to allow
reducing of the TTL *without* causing this side-effect, but as a
first step we can make the method more obvious in its impact
and behaviour.
I've also updated two of the callers that I think will benefit from
this more explicit name and (current) implementation detail.
Bug: T280605
Change-Id: I85bdff7f86911f8ea5b866e3639f08ddd3f3bf6f
2021-05-05 01:03:16 +00:00
|
|
|
|
* This method mainly exists to facilitate the logic in
|
|
|
|
|
|
* WikiPage::triggerOpportunisticLinksUpdate. As such, beware that reducing the TTL for
|
|
|
|
|
|
* reasons that do not relate to "dynamic content", may have the side-effect of incurring
|
|
|
|
|
|
* more RefreshLinksJob executions.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @internal For use by Parser and WikiPage
|
|
|
|
|
|
* @since 1.37
|
2015-02-12 23:03:24 +00:00
|
|
|
|
* @return bool
|
|
|
|
|
|
*/
|
2021-07-22 03:11:47 +00:00
|
|
|
|
public function hasReducedExpiry(): bool {
|
2022-04-10 15:34:45 +00:00
|
|
|
|
$parserCacheExpireTime = MediaWikiServices::getInstance()->getMainConfig()->get(
|
|
|
|
|
|
MainConfigNames::ParserCacheExpireTime );
|
2015-02-12 23:03:24 +00:00
|
|
|
|
|
2022-01-06 18:44:56 +00:00
|
|
|
|
return $this->getCacheExpiry() < $parserCacheExpireTime;
|
2015-02-12 23:03:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-09-29 20:58:59 +00:00
|
|
|
|
/**
|
2023-07-27 22:40:08 +00:00
|
|
|
|
* Set the prevent-clickjacking flag. If set this will cause an
|
|
|
|
|
|
* `X-Frame-Options` header appropriate for edit pages to be sent.
|
|
|
|
|
|
* The header value is controlled by `$wgEditPageFrameOptions`.
|
|
|
|
|
|
*
|
|
|
|
|
|
* This is the default for special pages. If you display a CSRF-protected
|
|
|
|
|
|
* form on an ordinary view page, then you need to call this function
|
|
|
|
|
|
* with `$flag = true`.
|
2021-09-29 20:58:59 +00:00
|
|
|
|
*
|
|
|
|
|
|
* @param bool $flag New flag value
|
|
|
|
|
|
* @since 1.38
|
|
|
|
|
|
*/
|
2022-02-09 21:33:39 +00:00
|
|
|
|
public function setPreventClickjacking( bool $flag ): void {
|
2021-09-29 20:58:59 +00:00
|
|
|
|
$this->mPreventClickjacking = $flag;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2023-07-27 22:40:08 +00:00
|
|
|
|
* Get the prevent-clickjacking flag.
|
2021-09-29 20:58:59 +00:00
|
|
|
|
*
|
|
|
|
|
|
* @return bool Flag value
|
|
|
|
|
|
* @since 1.38
|
2023-07-27 22:40:08 +00:00
|
|
|
|
* @see ::setPreventClickjacking
|
2021-09-29 20:58:59 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function getPreventClickjacking(): bool {
|
|
|
|
|
|
return $this->mPreventClickjacking;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-03-18 20:20:09 +00:00
|
|
|
|
/**
|
2016-08-30 19:35:08 +00:00
|
|
|
|
* Lower the runtime adaptive TTL to at most this value
|
|
|
|
|
|
*
|
2017-08-20 11:20:59 +00:00
|
|
|
|
* @param int $ttl
|
2016-08-30 19:35:08 +00:00
|
|
|
|
* @since 1.28
|
|
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function updateRuntimeAdaptiveExpiry( $ttl ): void {
|
2016-08-30 19:35:08 +00:00
|
|
|
|
$this->mMaxAdaptiveExpiry = min( $ttl, $this->mMaxAdaptiveExpiry );
|
|
|
|
|
|
$this->updateCacheExpiry( $ttl );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-02-03 09:50:14 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Add an extra value to Content-Security-Policy default-src directive
|
|
|
|
|
|
*
|
|
|
|
|
|
* Call this if you are including a resource (e.g. image) from a third party domain.
|
|
|
|
|
|
* This is used for all source types except style and script.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @since 1.35
|
|
|
|
|
|
* @param string $src CSP source e.g. example.com
|
|
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function addExtraCSPDefaultSrc( $src ): void {
|
2020-02-03 09:50:14 +00:00
|
|
|
|
$this->mExtraDefaultSrcs[] = $src;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Add an extra value to Content-Security-Policy style-src directive
|
|
|
|
|
|
*
|
|
|
|
|
|
* @since 1.35
|
|
|
|
|
|
* @param string $src CSP source e.g. example.com
|
|
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function addExtraCSPStyleSrc( $src ): void {
|
2020-02-03 09:50:14 +00:00
|
|
|
|
$this->mExtraStyleSrcs[] = $src;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Add an extra value to Content-Security-Policy script-src directive
|
|
|
|
|
|
*
|
|
|
|
|
|
* Call this if you are loading third-party Javascript
|
|
|
|
|
|
*
|
|
|
|
|
|
* @since 1.35
|
|
|
|
|
|
* @param string $src CSP source e.g. example.com
|
|
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function addExtraCSPScriptSrc( $src ): void {
|
2020-02-03 09:50:14 +00:00
|
|
|
|
$this->mExtraScriptSrcs[] = $src;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-08-30 19:35:08 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Call this when parsing is done to lower the TTL based on low parse times
|
|
|
|
|
|
*
|
|
|
|
|
|
* @since 1.28
|
2014-03-18 20:20:09 +00:00
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function finalizeAdaptiveCacheExpiry(): void {
|
2016-08-30 19:35:08 +00:00
|
|
|
|
if ( is_infinite( $this->mMaxAdaptiveExpiry ) ) {
|
|
|
|
|
|
return; // not set
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-09-28 15:03:33 +00:00
|
|
|
|
$runtime = $this->getTimeProfile( 'wall' );
|
2016-08-30 19:35:08 +00:00
|
|
|
|
if ( is_float( $runtime ) ) {
|
|
|
|
|
|
$slope = ( self::SLOW_AR_TTL - self::FAST_AR_TTL )
|
|
|
|
|
|
/ ( self::PARSE_SLOW_SEC - self::PARSE_FAST_SEC );
|
|
|
|
|
|
// SLOW_AR_TTL = PARSE_SLOW_SEC * $slope + $point
|
|
|
|
|
|
$point = self::SLOW_AR_TTL - self::PARSE_SLOW_SEC * $slope;
|
|
|
|
|
|
|
|
|
|
|
|
$adaptiveTTL = min(
|
|
|
|
|
|
max( $slope * $runtime + $point, self::MIN_AR_TTL ),
|
|
|
|
|
|
$this->mMaxAdaptiveExpiry
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->updateCacheExpiry( $adaptiveTTL );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-12 05:07:33 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Transfer parser options which affect post-processing from ParserOptions
|
|
|
|
|
|
* to this ParserOutput.
|
|
|
|
|
|
* @param ParserOptions $parserOptions
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function setFromParserOptions( ParserOptions $parserOptions ) {
|
|
|
|
|
|
// Copied from Parser.php::parse and should probably be abstracted
|
|
|
|
|
|
// into the parent base class (probably as part of T236809)
|
|
|
|
|
|
// Wrap non-interface parser output in a <div> so it can be targeted
|
|
|
|
|
|
// with CSS (T37247)
|
|
|
|
|
|
$class = $parserOptions->getWrapOutputClass();
|
|
|
|
|
|
if ( $class !== false && !$parserOptions->getInterfaceMessage() ) {
|
|
|
|
|
|
$this->addWrapperDivClass( $class );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-11-06 20:43:31 +00:00
|
|
|
|
// Record whether we should suppress section edit links
|
|
|
|
|
|
if ( $parserOptions->getSuppressSectionEditLinks() ) {
|
|
|
|
|
|
$this->setOutputFlag( ParserOutputFlags::NO_SECTION_EDIT_LINKS );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-04-05 22:46:57 +00:00
|
|
|
|
// Record whether we should wrap sections for collapsing them
|
|
|
|
|
|
if ( $parserOptions->getCollapsibleSections() ) {
|
|
|
|
|
|
$this->setOutputFlag( ParserOutputFlags::COLLAPSIBLE_SECTIONS );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-12 05:07:33 +00:00
|
|
|
|
// Record whether this is a preview parse in the output (T341010)
|
|
|
|
|
|
if ( $parserOptions->getIsPreview() ) {
|
|
|
|
|
|
$this->setOutputFlag( ParserOutputFlags::IS_PREVIEW, true );
|
|
|
|
|
|
// Ensure that previews aren't cacheable, just to be safe.
|
|
|
|
|
|
$this->updateCacheExpiry( 0 );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-08-11 20:24:54 +00:00
|
|
|
|
public function __sleep() {
|
2019-08-01 00:08:38 +00:00
|
|
|
|
return array_filter( array_keys( get_object_vars( $this ) ),
|
2021-02-10 22:31:02 +00:00
|
|
|
|
static function ( $field ) {
|
2022-02-07 16:28:23 +00:00
|
|
|
|
if ( $field === 'mParseStartTime' || $field === 'mWarningMsgs' ) {
|
2019-08-01 00:08:38 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2021-08-17 21:15:57 +00:00
|
|
|
|
// Unserializing unknown private fields in HHVM causes
|
|
|
|
|
|
// member variables with nulls in their names (T229366)
|
|
|
|
|
|
return strpos( $field, "\0" ) === false;
|
2019-08-01 00:08:38 +00:00
|
|
|
|
}
|
2014-03-18 20:20:09 +00:00
|
|
|
|
);
|
|
|
|
|
|
}
|
2018-08-07 16:52:40 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Merges internal metadata such as flags, accessed options, and profiling info
|
|
|
|
|
|
* from $source into this ParserOutput. This should be used whenever the state of $source
|
|
|
|
|
|
* has any impact on the state of this ParserOutput.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param ParserOutput $source
|
|
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function mergeInternalMetaDataFrom( ParserOutput $source ): void {
|
2018-08-07 16:52:40 +00:00
|
|
|
|
$this->mWarnings = self::mergeMap( $this->mWarnings, $source->mWarnings ); // don't use getter
|
2023-11-06 21:25:07 +00:00
|
|
|
|
$this->mTimestamp = $this->useMaxValue( $this->mTimestamp, $source->getRevisionTimestamp() );
|
Add ParserOutput::{get,set}RenderId() and set render id in ContentRenderer
Set the render ID for each parse stored into cache so that we are able
to identify a specific parse when there are dependencies (for example
in an edit based on that parse). This is recorded as a property added
to the ParserOutput, not the parent CacheTime interface. Even though
the render ID is /related/ to the CacheTime interface, CacheTime is
also used directly as a parser cache key, and the UUID should not be
part of the lookup key.
In general we are trying to move the location where these cache
properties are set as early as possible, so we check at each location
to ensure we don't overwrite a previously-set value. Eventually we
can convert most of these checks into assertions that the cache
properties have already been set (T350538). The primary location for
setting cache properties is the ContentRenderer.
Moved setting the revision timestamp into ContentRenderer as well, as
it was set along the same code paths. An extra parameter was added to
ContentRenderer::getParserOutput() to support this.
Added merge code to ParserOutput::mergeInternalMetaDataFrom() which
should ensure that cache time, revision, timestamp, and render id are
all set properly when multiple slots are combined together in MCR.
In order to ensure the render ID is set on all codepaths we needed to
plumb the GlobalIdGenerator service into ContentRenderer, ParserCache,
ParserCacheFactory, and RevisionOutputCache. Eventually (T350538) it
should only be necessary in the ContentRenderer.
Bug: T350538
Bug: T349868
Followup-To: Ic9b7cc0fcf365e772b7d080d76a065e3fd585f80
Change-Id: I72c5e6f86b7f081ab5ce7a56f5365d2f75067a78
2023-09-14 16:11:20 +00:00
|
|
|
|
if ( $source->hasCacheTime() ) {
|
|
|
|
|
|
$sourceCacheTime = $source->getCacheTime();
|
|
|
|
|
|
if (
|
|
|
|
|
|
!$this->hasCacheTime() ||
|
|
|
|
|
|
// "undocumented use of -1 to mean not cacheable"
|
|
|
|
|
|
// deprecated, but still supported by ::setCacheTime()
|
|
|
|
|
|
strval( $sourceCacheTime ) === '-1' ||
|
|
|
|
|
|
(
|
|
|
|
|
|
strval( $this->getCacheTime() ) !== '-1' &&
|
|
|
|
|
|
// use newer of the two times
|
|
|
|
|
|
$this->getCacheTime() < $sourceCacheTime
|
|
|
|
|
|
)
|
|
|
|
|
|
) {
|
|
|
|
|
|
$this->setCacheTime( $sourceCacheTime );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if ( $source->getRenderId() !== null ) {
|
|
|
|
|
|
// Final render ID should be a function of all component POs
|
|
|
|
|
|
$rid = ( $this->getRenderId() ?? '' ) . $source->getRenderId();
|
|
|
|
|
|
$this->setRenderId( $rid );
|
|
|
|
|
|
}
|
|
|
|
|
|
if ( $source->getCacheRevisionId() !== null ) {
|
|
|
|
|
|
$sourceCacheRevisionId = $source->getCacheRevisionId();
|
|
|
|
|
|
$thisCacheRevisionId = $this->getCacheRevisionId();
|
|
|
|
|
|
if ( $thisCacheRevisionId === null ) {
|
|
|
|
|
|
$this->setCacheRevisionId( $sourceCacheRevisionId );
|
|
|
|
|
|
} elseif ( $sourceCacheRevisionId !== $thisCacheRevisionId ) {
|
|
|
|
|
|
// May throw an exception here in the future
|
|
|
|
|
|
wfDeprecated(
|
|
|
|
|
|
__METHOD__ . ": conflicting revision IDs " .
|
|
|
|
|
|
"$thisCacheRevisionId and $sourceCacheRevisionId"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2018-08-07 16:52:40 +00:00
|
|
|
|
|
2019-10-16 01:24:50 +00:00
|
|
|
|
foreach ( self::SPECULATIVE_FIELDS as $field ) {
|
2019-06-27 04:30:35 +00:00
|
|
|
|
if ( $this->$field && $source->$field && $this->$field !== $source->$field ) {
|
|
|
|
|
|
wfLogWarning( __METHOD__ . ": inconsistent '$field' properties!" );
|
|
|
|
|
|
}
|
|
|
|
|
|
$this->$field = $this->useMaxValue( $this->$field, $source->$field );
|
2018-08-07 16:52:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$this->mParseStartTime = $this->useEachMinValue(
|
|
|
|
|
|
$this->mParseStartTime,
|
|
|
|
|
|
$source->mParseStartTime
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2023-09-28 15:03:33 +00:00
|
|
|
|
$this->mTimeProfile = $this->useEachTotalValue(
|
|
|
|
|
|
$this->mTimeProfile,
|
|
|
|
|
|
$source->mTimeProfile
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2018-08-07 16:52:40 +00:00
|
|
|
|
$this->mFlags = self::mergeMap( $this->mFlags, $source->mFlags );
|
2020-11-05 17:05:40 +00:00
|
|
|
|
$this->mParseUsedOptions = self::mergeMap( $this->mParseUsedOptions, $source->mParseUsedOptions );
|
2018-08-07 16:52:40 +00:00
|
|
|
|
|
|
|
|
|
|
// TODO: maintain per-slot limit reports!
|
2023-09-08 21:28:11 +00:00
|
|
|
|
if ( !$this->mLimitReportData ) {
|
2018-08-07 16:52:40 +00:00
|
|
|
|
$this->mLimitReportData = $source->mLimitReportData;
|
|
|
|
|
|
}
|
2023-09-08 21:28:11 +00:00
|
|
|
|
if ( !$this->mLimitReportJSData ) {
|
2018-08-07 16:52:40 +00:00
|
|
|
|
$this->mLimitReportJSData = $source->mLimitReportJSData;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Merges HTML metadata such as head items, JS config vars, and HTTP cache control info
|
|
|
|
|
|
* from $source into this ParserOutput. This should be used whenever the HTML in $source
|
2022-01-27 19:44:35 +00:00
|
|
|
|
* has been somehow merged into the HTML of this ParserOutput.
|
2018-08-07 16:52:40 +00:00
|
|
|
|
*
|
|
|
|
|
|
* @param ParserOutput $source
|
|
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function mergeHtmlMetaDataFrom( ParserOutput $source ): void {
|
2018-08-07 16:52:40 +00:00
|
|
|
|
// HTML and HTTP
|
|
|
|
|
|
$this->mHeadItems = self::mergeMixedList( $this->mHeadItems, $source->getHeadItems() );
|
2023-11-22 19:33:05 +00:00
|
|
|
|
$this->addModules( $source->getModules() );
|
|
|
|
|
|
$this->addModuleStyles( $source->getModuleStyles() );
|
2022-01-27 19:44:35 +00:00
|
|
|
|
$this->mJsConfigVars = self::mergeMapStrategy( $this->mJsConfigVars, $source->mJsConfigVars );
|
2018-08-07 16:52:40 +00:00
|
|
|
|
$this->mMaxAdaptiveExpiry = min( $this->mMaxAdaptiveExpiry, $source->mMaxAdaptiveExpiry );
|
2020-02-03 09:50:14 +00:00
|
|
|
|
$this->mExtraStyleSrcs = self::mergeList(
|
|
|
|
|
|
$this->mExtraStyleSrcs,
|
|
|
|
|
|
$source->getExtraCSPStyleSrcs()
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->mExtraScriptSrcs = self::mergeList(
|
|
|
|
|
|
$this->mExtraScriptSrcs,
|
|
|
|
|
|
$source->getExtraCSPScriptSrcs()
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->mExtraDefaultSrcs = self::mergeList(
|
|
|
|
|
|
$this->mExtraDefaultSrcs,
|
|
|
|
|
|
$source->getExtraCSPDefaultSrcs()
|
|
|
|
|
|
);
|
2018-08-07 16:52:40 +00:00
|
|
|
|
|
|
|
|
|
|
// "noindex" always wins!
|
2022-03-30 16:45:00 +00:00
|
|
|
|
$this->mIndexSet = $this->mIndexSet || $source->mIndexSet;
|
|
|
|
|
|
$this->mNoIndexSet = $this->mNoIndexSet || $source->mNoIndexSet;
|
2018-08-07 16:52:40 +00:00
|
|
|
|
|
|
|
|
|
|
// Skin control
|
|
|
|
|
|
$this->mNewSection = $this->mNewSection || $source->getNewSection();
|
|
|
|
|
|
$this->mHideNewSection = $this->mHideNewSection || $source->getHideNewSection();
|
|
|
|
|
|
$this->mNoGallery = $this->mNoGallery || $source->getNoGallery();
|
|
|
|
|
|
$this->mEnableOOUI = $this->mEnableOOUI || $source->getEnableOOUI();
|
2021-09-29 20:58:59 +00:00
|
|
|
|
$this->mPreventClickjacking = $this->mPreventClickjacking || $source->getPreventClickjacking();
|
2018-08-07 16:52:40 +00:00
|
|
|
|
|
2022-09-01 23:07:29 +00:00
|
|
|
|
$tocData = $this->getTOCData();
|
|
|
|
|
|
$sourceTocData = $source->getTOCData();
|
|
|
|
|
|
if ( $tocData !== null ) {
|
|
|
|
|
|
if ( $sourceTocData !== null ) {
|
|
|
|
|
|
// T327429: Section merging is broken, since it doesn't respect
|
|
|
|
|
|
// global numbering, but there are tests which expect section
|
2023-11-17 16:05:54 +00:00
|
|
|
|
// metadata to be concatenated.
|
2022-09-01 23:07:29 +00:00
|
|
|
|
// There should eventually be a deprecation warning here.
|
|
|
|
|
|
foreach ( $sourceTocData->getSections() as $s ) {
|
|
|
|
|
|
$tocData->addSection( $s );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} elseif ( $sourceTocData !== null ) {
|
|
|
|
|
|
$this->setTOCData( $sourceTocData );
|
|
|
|
|
|
}
|
2018-08-07 16:52:40 +00:00
|
|
|
|
|
|
|
|
|
|
// XXX: we don't want to concatenate title text, so first write wins.
|
|
|
|
|
|
// We should use the first *modified* title text, but we don't have the original to check.
|
|
|
|
|
|
if ( $this->mTitleText === null || $this->mTitleText === '' ) {
|
|
|
|
|
|
$this->mTitleText = $source->mTitleText;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// class names are stored in array keys
|
|
|
|
|
|
$this->mWrapperDivClasses = self::mergeMap(
|
|
|
|
|
|
$this->mWrapperDivClasses,
|
|
|
|
|
|
$source->mWrapperDivClasses
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// NOTE: last write wins, same as within one ParserOutput
|
|
|
|
|
|
$this->mIndicators = self::mergeMap( $this->mIndicators, $source->getIndicators() );
|
|
|
|
|
|
|
|
|
|
|
|
// NOTE: include extension data in "tracking meta data" as well as "html meta data"!
|
|
|
|
|
|
// TODO: add a $mergeStrategy parameter to setExtensionData to allow different
|
|
|
|
|
|
// kinds of extension data to be merged in different ways.
|
2022-01-28 17:09:22 +00:00
|
|
|
|
$this->mExtensionData = self::mergeMapStrategy(
|
2018-08-07 16:52:40 +00:00
|
|
|
|
$this->mExtensionData,
|
|
|
|
|
|
$source->mExtensionData
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Merges dependency tracking metadata such as backlinks, images used, and extension data
|
|
|
|
|
|
* from $source into this ParserOutput. This allows dependency tracking to be done for the
|
|
|
|
|
|
* combined output of multiple content slots.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param ParserOutput $source
|
|
|
|
|
|
*/
|
2022-02-05 00:34:25 +00:00
|
|
|
|
public function mergeTrackingMetaDataFrom( ParserOutput $source ): void {
|
2024-06-14 03:54:52 +00:00
|
|
|
|
foreach ( $source->getLanguageLinks() as $ll ) {
|
|
|
|
|
|
$this->addLanguageLink( $ll );
|
|
|
|
|
|
}
|
2023-09-21 17:06:50 +00:00
|
|
|
|
$this->mCategories = self::mergeMap( $this->mCategories, $source->getCategoryMap() );
|
2018-08-07 16:52:40 +00:00
|
|
|
|
$this->mLinks = self::merge2D( $this->mLinks, $source->getLinks() );
|
|
|
|
|
|
$this->mTemplates = self::merge2D( $this->mTemplates, $source->getTemplates() );
|
|
|
|
|
|
$this->mTemplateIds = self::merge2D( $this->mTemplateIds, $source->getTemplateIds() );
|
|
|
|
|
|
$this->mImages = self::mergeMap( $this->mImages, $source->getImages() );
|
|
|
|
|
|
$this->mFileSearchOptions = self::mergeMap(
|
|
|
|
|
|
$this->mFileSearchOptions,
|
|
|
|
|
|
$source->getFileSearchOptions()
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->mExternalLinks = self::mergeMap( $this->mExternalLinks, $source->getExternalLinks() );
|
|
|
|
|
|
$this->mInterwikiLinks = self::merge2D(
|
|
|
|
|
|
$this->mInterwikiLinks,
|
|
|
|
|
|
$source->getInterwikiLinks()
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2021-10-07 16:13:46 +00:00
|
|
|
|
// TODO: add a $mergeStrategy parameter to setPageProperty to allow different
|
2018-08-07 16:52:40 +00:00
|
|
|
|
// kinds of properties to be merged in different ways.
|
2022-01-27 19:44:35 +00:00
|
|
|
|
// (Model this after ::appendJsConfigVar(); use ::mergeMapStrategy here)
|
2021-10-07 16:13:46 +00:00
|
|
|
|
$this->mProperties = self::mergeMap( $this->mProperties, $source->getPageProperties() );
|
2018-08-07 16:52:40 +00:00
|
|
|
|
|
|
|
|
|
|
// NOTE: include extension data in "tracking meta data" as well as "html meta data"!
|
2022-01-28 17:09:22 +00:00
|
|
|
|
$this->mExtensionData = self::mergeMapStrategy(
|
2018-08-07 16:52:40 +00:00
|
|
|
|
$this->mExtensionData,
|
|
|
|
|
|
$source->mExtensionData
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-07 16:28:23 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Adds the metadata collected in this ParserOutput to the supplied
|
|
|
|
|
|
* ContentMetadataCollector. This is similar to ::mergeHtmlMetaDataFrom()
|
|
|
|
|
|
* but in the opposite direction, since ParserOutput is read/write while
|
|
|
|
|
|
* ContentMetadataCollector is write-only.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param ContentMetadataCollector $metadata
|
|
|
|
|
|
* @since 1.38
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function collectMetadata( ContentMetadataCollector $metadata ): void {
|
|
|
|
|
|
// Uniform handling of all boolean flags: they are OR'ed together.
|
|
|
|
|
|
$flags = array_keys(
|
|
|
|
|
|
$this->mFlags + array_flip( ParserOutputFlags::cases() )
|
|
|
|
|
|
);
|
|
|
|
|
|
foreach ( $flags as $name ) {
|
|
|
|
|
|
if ( $this->getOutputFlag( $name ) ) {
|
|
|
|
|
|
$metadata->setOutputFlag( $name );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2023-07-27 19:47:00 +00:00
|
|
|
|
|
2023-07-31 22:07:21 +00:00
|
|
|
|
// Uniform handling of string sets: they are unioned.
|
|
|
|
|
|
// (This includes modules, style modes, and CSP src.)
|
|
|
|
|
|
foreach ( ParserOutputStringSets::cases() as $name ) {
|
|
|
|
|
|
$metadata->appendOutputStrings(
|
|
|
|
|
|
$name, $this->getOutputStrings( $name )
|
|
|
|
|
|
);
|
2023-07-27 19:47:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-18 19:33:54 +00:00
|
|
|
|
foreach ( $this->mCategories as $cat => $key ) {
|
|
|
|
|
|
// Numeric category strings are going to come out of the
|
|
|
|
|
|
// `mCategories` array as ints; cast back to string.
|
|
|
|
|
|
// Also convert back to a LinkTarget!
|
|
|
|
|
|
$lt = TitleValue::tryNew( NS_CATEGORY, (string)$cat );
|
|
|
|
|
|
$metadata->addCategory( $lt, $key );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-08-26 18:16:51 +00:00
|
|
|
|
foreach ( $this->mLinks as $ns => $arr ) {
|
|
|
|
|
|
foreach ( $arr as $dbk => $id ) {
|
|
|
|
|
|
// Numeric titles are going to come out of the
|
|
|
|
|
|
// `mLinks` array as ints; cast back to string.
|
|
|
|
|
|
$lt = TitleValue::tryNew( $ns, (string)$dbk );
|
|
|
|
|
|
$metadata->addLink( $lt, $id );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-06 15:59:58 +00:00
|
|
|
|
foreach ( $this->mInterwikiLinks as $prefix => $arr ) {
|
|
|
|
|
|
foreach ( $arr as $dbk => $ignore ) {
|
|
|
|
|
|
$lt = TitleValue::tryNew( NS_MAIN, (string)$dbk, '', $prefix );
|
|
|
|
|
|
$metadata->addLink( $lt );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-08-26 18:16:51 +00:00
|
|
|
|
foreach ( $this->mLinksSpecial as $dbk => $ignore ) {
|
|
|
|
|
|
// Numeric titles are going to come out of the
|
|
|
|
|
|
// `mLinks` array as ints; cast back to string.
|
|
|
|
|
|
$lt = TitleValue::tryNew( NS_SPECIAL, (string)$dbk );
|
|
|
|
|
|
$metadata->addLink( $lt );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach ( $this->mImages as $name => $ignore ) {
|
|
|
|
|
|
// Numeric titles come out of mImages as ints.
|
|
|
|
|
|
$lt = TitleValue::tryNew( NS_FILE, (string)$name );
|
|
|
|
|
|
$props = $this->mFileSearchOptions[$name] ?? [];
|
|
|
|
|
|
$metadata->addImage( $lt, $props['time'] ?? null, $props['sha1'] ?? null );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-06-14 03:54:52 +00:00
|
|
|
|
foreach ( $this->mLanguageLinkMap as $lang => $title ) {
|
|
|
|
|
|
if ( $title === '|' ) {
|
|
|
|
|
|
continue; // T374736: not a valid language link
|
|
|
|
|
|
}
|
2024-09-06 15:59:58 +00:00
|
|
|
|
# language links can have fragments!
|
|
|
|
|
|
[ $title, $frag ] = array_pad( explode( '#', $title, 2 ), 2, '' );
|
2024-06-14 03:54:52 +00:00
|
|
|
|
$lt = TitleValue::tryNew( NS_MAIN, $title, $frag, (string)$lang );
|
2024-08-26 18:16:51 +00:00
|
|
|
|
$metadata->addLanguageLink( $lt );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-07 16:28:23 +00:00
|
|
|
|
foreach ( $this->mJsConfigVars as $key => $value ) {
|
|
|
|
|
|
if ( is_array( $value ) && isset( $value[self::MW_MERGE_STRATEGY_KEY] ) ) {
|
|
|
|
|
|
$strategy = $value[self::MW_MERGE_STRATEGY_KEY];
|
|
|
|
|
|
foreach ( $value as $item => $ignore ) {
|
|
|
|
|
|
if ( $item !== self::MW_MERGE_STRATEGY_KEY ) {
|
|
|
|
|
|
$metadata->appendJsConfigVar( $key, $item, $strategy );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-05-17 12:16:38 +00:00
|
|
|
|
} elseif ( $metadata instanceof ParserOutput &&
|
|
|
|
|
|
array_key_exists( $key, $metadata->mJsConfigVars )
|
|
|
|
|
|
) {
|
2022-03-11 17:47:36 +00:00
|
|
|
|
// This behavior is deprecated, will likely result in
|
|
|
|
|
|
// incorrect output, and we'll eventually emit a
|
|
|
|
|
|
// warning here---but at the moment this is usually
|
|
|
|
|
|
// caused by limitations in Parsoid and/or use of
|
|
|
|
|
|
// the ParserAfterParse hook: T303015#7770480
|
2022-05-17 12:16:38 +00:00
|
|
|
|
$metadata->mJsConfigVars[$key] = $value;
|
2022-02-07 16:28:23 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
$metadata->setJsConfigVar( $key, $value );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
foreach ( $this->mExtensionData as $key => $value ) {
|
|
|
|
|
|
if ( is_array( $value ) && isset( $value[self::MW_MERGE_STRATEGY_KEY] ) ) {
|
|
|
|
|
|
$strategy = $value[self::MW_MERGE_STRATEGY_KEY];
|
|
|
|
|
|
foreach ( $value as $item => $ignore ) {
|
|
|
|
|
|
if ( $item !== self::MW_MERGE_STRATEGY_KEY ) {
|
|
|
|
|
|
$metadata->appendExtensionData( $key, $item, $strategy );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-05-17 12:16:38 +00:00
|
|
|
|
} elseif ( $metadata instanceof ParserOutput &&
|
|
|
|
|
|
array_key_exists( $key, $metadata->mExtensionData )
|
|
|
|
|
|
) {
|
2022-03-11 17:47:36 +00:00
|
|
|
|
// This behavior is deprecated, will likely result in
|
|
|
|
|
|
// incorrect output, and we'll eventually emit a
|
|
|
|
|
|
// warning here---but at the moment this is usually
|
|
|
|
|
|
// caused by limitations in Parsoid and/or use of
|
|
|
|
|
|
// the ParserAfterParse hook: T303015#7770480
|
2022-05-17 12:16:38 +00:00
|
|
|
|
$metadata->mExtensionData[$key] = $value;
|
2022-02-07 16:28:23 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
$metadata->setExtensionData( $key, $value );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
foreach ( $this->mExternalLinks as $url => $ignore ) {
|
|
|
|
|
|
$metadata->addExternalLink( $url );
|
|
|
|
|
|
}
|
|
|
|
|
|
foreach ( $this->mProperties as $prop => $value ) {
|
2024-09-03 19:21:13 +00:00
|
|
|
|
if ( is_numeric( $value ) ) {
|
2024-08-26 18:16:51 +00:00
|
|
|
|
$metadata->setNumericPageProperty( $prop, $value );
|
2024-09-04 19:05:23 +00:00
|
|
|
|
} elseif ( is_string( $value ) ) {
|
2024-09-03 19:21:13 +00:00
|
|
|
|
$metadata->setUnsortedPageProperty( $prop, $value );
|
2024-09-04 19:05:23 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
// Deprecated, but there are still sites which call
|
|
|
|
|
|
// ::setPageProperty() with "unusual" values (T374046)
|
|
|
|
|
|
$metadata->setPageProperty( $prop, $value );
|
2024-08-26 18:16:51 +00:00
|
|
|
|
}
|
2022-02-07 16:28:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
foreach ( $this->mWarningMsgs as $msg => $args ) {
|
|
|
|
|
|
$metadata->addWarningMsg( $msg, ...$args );
|
|
|
|
|
|
}
|
|
|
|
|
|
foreach ( $this->mLimitReportData as $key => $value ) {
|
|
|
|
|
|
$metadata->setLimitReportData( $key, $value );
|
|
|
|
|
|
}
|
2024-08-26 18:16:51 +00:00
|
|
|
|
foreach ( $this->mIndicators as $id => $content ) {
|
|
|
|
|
|
$metadata->setIndicator( $id, $content );
|
|
|
|
|
|
}
|
2023-02-07 16:32:23 +00:00
|
|
|
|
|
|
|
|
|
|
// ParserOutput-only fields; maintained "behind the curtain"
|
|
|
|
|
|
// since Parsoid doesn't have to know about them.
|
|
|
|
|
|
//
|
|
|
|
|
|
// In production use, the $metadata supplied to this method
|
|
|
|
|
|
// will almost always be an instance of ParserOutput, passed to
|
|
|
|
|
|
// Parsoid by core when parsing begins and returned to core by
|
|
|
|
|
|
// Parsoid as a ContentMetadataCollector (Parsoid's name for
|
|
|
|
|
|
// ParserOutput) when DataAccess::parseWikitext() is called.
|
|
|
|
|
|
//
|
|
|
|
|
|
// We may use still Parsoid's StubMetadataCollector for testing or
|
|
|
|
|
|
// when running Parsoid in standalone mode, so forcing a downcast
|
|
|
|
|
|
// here would lose some flexibility.
|
|
|
|
|
|
|
|
|
|
|
|
if ( $metadata instanceof ParserOutput ) {
|
|
|
|
|
|
foreach ( $this->getUsedOptions() as $opt ) {
|
|
|
|
|
|
$metadata->recordOption( $opt );
|
|
|
|
|
|
}
|
|
|
|
|
|
if ( $this->mCacheExpiry !== null ) {
|
|
|
|
|
|
$metadata->updateCacheExpiry( $this->mCacheExpiry );
|
|
|
|
|
|
}
|
|
|
|
|
|
if ( $this->mCacheTime !== '' ) {
|
|
|
|
|
|
$metadata->setCacheTime( $this->mCacheTime );
|
|
|
|
|
|
}
|
|
|
|
|
|
if ( $this->mCacheRevisionId !== null ) {
|
|
|
|
|
|
$metadata->setCacheRevisionId( $this->mCacheRevisionId );
|
|
|
|
|
|
}
|
2023-04-15 04:18:56 +00:00
|
|
|
|
// T293514: We should use the first *modified* title text, but
|
|
|
|
|
|
// we don't have the original to check.
|
|
|
|
|
|
$otherTitle = $metadata->getTitleText();
|
|
|
|
|
|
if ( $otherTitle === null || $otherTitle === '' ) {
|
|
|
|
|
|
$metadata->setTitleText( $this->getTitleText() );
|
|
|
|
|
|
}
|
2024-09-06 15:59:58 +00:00
|
|
|
|
foreach ( $this->mTemplates as $ns => $arr ) {
|
|
|
|
|
|
foreach ( $arr as $dbk => $page_id ) {
|
|
|
|
|
|
$rev_id = $this->mTemplateIds[$ns][$dbk];
|
|
|
|
|
|
$metadata->addTemplate( TitleValue::tryNew( $ns, (string)$dbk ), $page_id, $rev_id );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2023-02-07 16:32:23 +00:00
|
|
|
|
}
|
2022-02-07 16:28:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-05 00:34:25 +00:00
|
|
|
|
private static function mergeMixedList( array $a, array $b ): array {
|
2018-08-07 16:52:40 +00:00
|
|
|
|
return array_unique( array_merge( $a, $b ), SORT_REGULAR );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-05 00:34:25 +00:00
|
|
|
|
private static function mergeList( array $a, array $b ): array {
|
2018-08-07 16:52:40 +00:00
|
|
|
|
return array_values( array_unique( array_merge( $a, $b ), SORT_REGULAR ) );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-05 00:34:25 +00:00
|
|
|
|
private static function mergeMap( array $a, array $b ): array {
|
2018-08-07 16:52:40 +00:00
|
|
|
|
return array_replace( $a, $b );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-05 00:34:25 +00:00
|
|
|
|
private static function mergeMapStrategy( array $a, array $b ): array {
|
2022-01-27 19:44:35 +00:00
|
|
|
|
foreach ( $b as $key => $bValue ) {
|
|
|
|
|
|
if ( !array_key_exists( $key, $a ) ) {
|
|
|
|
|
|
$a[$key] = $bValue;
|
|
|
|
|
|
} elseif (
|
2022-07-07 14:42:25 +00:00
|
|
|
|
is_array( $a[$key] ) &&
|
2022-02-07 16:28:23 +00:00
|
|
|
|
isset( $a[$key][self::MW_MERGE_STRATEGY_KEY] ) &&
|
|
|
|
|
|
isset( $bValue[self::MW_MERGE_STRATEGY_KEY] )
|
2022-01-27 19:44:35 +00:00
|
|
|
|
) {
|
2022-02-07 16:28:23 +00:00
|
|
|
|
$strategy = $bValue[self::MW_MERGE_STRATEGY_KEY];
|
|
|
|
|
|
if ( $strategy !== $a[$key][self::MW_MERGE_STRATEGY_KEY] ) {
|
2022-01-27 19:44:35 +00:00
|
|
|
|
throw new InvalidArgumentException( "Conflicting merge strategy for $key" );
|
|
|
|
|
|
}
|
2022-02-07 16:28:23 +00:00
|
|
|
|
if ( $strategy === self::MW_MERGE_STRATEGY_UNION ) {
|
2022-01-27 19:44:35 +00:00
|
|
|
|
// Note the array_merge is *not* safe to use here, because
|
|
|
|
|
|
// the $bValue is expected to be a map from items to `true`.
|
|
|
|
|
|
// If the item is a numeric string like '1' then array_merge
|
|
|
|
|
|
// will convert it to an integer and renumber the array!
|
|
|
|
|
|
$a[$key] = array_replace( $a[$key], $bValue );
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new InvalidArgumentException( "Unknown merge strategy $strategy" );
|
|
|
|
|
|
}
|
2022-07-07 22:10:09 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
$valuesSame = ( $a[$key] === $bValue );
|
|
|
|
|
|
if ( ( !$valuesSame ) &&
|
2024-04-19 22:25:15 +00:00
|
|
|
|
is_object( $a[$key] ) &&
|
|
|
|
|
|
is_object( $bValue )
|
2022-07-07 22:10:09 +00:00
|
|
|
|
) {
|
|
|
|
|
|
$jsonCodec = MediaWikiServices::getInstance()->getJsonCodec();
|
|
|
|
|
|
$valuesSame = ( $jsonCodec->serialize( $a[$key] ) === $jsonCodec->serialize( $bValue ) );
|
|
|
|
|
|
}
|
|
|
|
|
|
if ( !$valuesSame ) {
|
|
|
|
|
|
// Silently replace for now; in the future will first emit
|
|
|
|
|
|
// a deprecation warning, and then (later) throw.
|
|
|
|
|
|
$a[$key] = $bValue;
|
|
|
|
|
|
}
|
2022-01-27 19:44:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return $a;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-05 00:34:25 +00:00
|
|
|
|
private static function merge2D( array $a, array $b ): array {
|
2018-08-07 16:52:40 +00:00
|
|
|
|
$values = [];
|
|
|
|
|
|
$keys = array_merge( array_keys( $a ), array_keys( $b ) );
|
|
|
|
|
|
|
|
|
|
|
|
foreach ( $keys as $k ) {
|
|
|
|
|
|
if ( empty( $a[$k] ) ) {
|
|
|
|
|
|
$values[$k] = $b[$k];
|
|
|
|
|
|
} elseif ( empty( $b[$k] ) ) {
|
|
|
|
|
|
$values[$k] = $a[$k];
|
|
|
|
|
|
} elseif ( is_array( $a[$k] ) && is_array( $b[$k] ) ) {
|
|
|
|
|
|
$values[$k] = array_replace( $a[$k], $b[$k] );
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$values[$k] = $b[$k];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $values;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-05 00:34:25 +00:00
|
|
|
|
private static function useEachMinValue( array $a, array $b ): array {
|
2018-08-07 16:52:40 +00:00
|
|
|
|
$values = [];
|
|
|
|
|
|
$keys = array_merge( array_keys( $a ), array_keys( $b ) );
|
|
|
|
|
|
|
|
|
|
|
|
foreach ( $keys as $k ) {
|
2023-09-28 15:03:33 +00:00
|
|
|
|
$values[$k] = min( $a[$k] ?? INF, $b[$k] ?? INF );
|
2018-08-07 16:52:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $values;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-09-28 15:03:33 +00:00
|
|
|
|
private static function useEachTotalValue( array $a, array $b ): array {
|
|
|
|
|
|
$values = [];
|
|
|
|
|
|
$keys = array_merge( array_keys( $a ), array_keys( $b ) );
|
2018-08-07 16:52:40 +00:00
|
|
|
|
|
2023-09-28 15:03:33 +00:00
|
|
|
|
foreach ( $keys as $k ) {
|
|
|
|
|
|
$values[$k] = ( $a[$k] ?? 0 ) + ( $b[$k] ?? 0 );
|
2018-08-07 16:52:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-09-28 15:03:33 +00:00
|
|
|
|
return $values;
|
2018-08-07 16:52:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static function useMaxValue( $a, $b ) {
|
|
|
|
|
|
if ( $a === null ) {
|
|
|
|
|
|
return $b;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ( $b === null ) {
|
|
|
|
|
|
return $a;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return max( $a, $b );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-09-29 19:18:00 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Returns a JSON serializable structure representing this ParserOutput instance.
|
|
|
|
|
|
* @see newFromJson()
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return array
|
|
|
|
|
|
*/
|
2020-10-23 00:17:31 +00:00
|
|
|
|
protected function toJsonArray(): array {
|
2023-01-03 18:08:22 +00:00
|
|
|
|
// WARNING: When changing how this class is serialized, follow the instructions
|
|
|
|
|
|
// at <https://www.mediawiki.org/wiki/Manual:Parser_cache/Serialization_compatibility>!
|
|
|
|
|
|
|
2020-09-29 19:18:00 +00:00
|
|
|
|
$data = [
|
2024-02-09 22:27:00 +00:00
|
|
|
|
'Text' => $this->mRawText,
|
2024-06-14 03:54:52 +00:00
|
|
|
|
'LanguageLinks' => $this->getLanguageLinks(),
|
2020-09-29 19:18:00 +00:00
|
|
|
|
'Categories' => $this->mCategories,
|
|
|
|
|
|
'Indicators' => $this->mIndicators,
|
|
|
|
|
|
'TitleText' => $this->mTitleText,
|
|
|
|
|
|
'Links' => $this->mLinks,
|
|
|
|
|
|
'LinksSpecial' => $this->mLinksSpecial,
|
|
|
|
|
|
'Templates' => $this->mTemplates,
|
|
|
|
|
|
'TemplateIds' => $this->mTemplateIds,
|
|
|
|
|
|
'Images' => $this->mImages,
|
|
|
|
|
|
'FileSearchOptions' => $this->mFileSearchOptions,
|
|
|
|
|
|
'ExternalLinks' => $this->mExternalLinks,
|
|
|
|
|
|
'InterwikiLinks' => $this->mInterwikiLinks,
|
|
|
|
|
|
'NewSection' => $this->mNewSection,
|
|
|
|
|
|
'HideNewSection' => $this->mHideNewSection,
|
|
|
|
|
|
'NoGallery' => $this->mNoGallery,
|
|
|
|
|
|
'HeadItems' => $this->mHeadItems,
|
2023-11-22 19:33:05 +00:00
|
|
|
|
'Modules' => array_keys( $this->mModuleSet ),
|
|
|
|
|
|
'ModuleStyles' => array_keys( $this->mModuleStyleSet ),
|
2020-09-29 19:18:00 +00:00
|
|
|
|
'JsConfigVars' => $this->mJsConfigVars,
|
|
|
|
|
|
'Warnings' => $this->mWarnings,
|
2022-09-01 23:07:29 +00:00
|
|
|
|
'Sections' => $this->getSections(),
|
2020-11-03 10:58:00 +00:00
|
|
|
|
'Properties' => self::detectAndEncodeBinary( $this->mProperties ),
|
2020-09-29 19:18:00 +00:00
|
|
|
|
'Timestamp' => $this->mTimestamp,
|
|
|
|
|
|
'EnableOOUI' => $this->mEnableOOUI,
|
2022-03-30 16:45:00 +00:00
|
|
|
|
'IndexPolicy' => $this->getIndexPolicy(),
|
2020-10-23 00:17:31 +00:00
|
|
|
|
// may contain arbitrary structures!
|
|
|
|
|
|
'ExtensionData' => $this->mExtensionData,
|
2020-09-29 19:18:00 +00:00
|
|
|
|
'LimitReportData' => $this->mLimitReportData,
|
|
|
|
|
|
'LimitReportJSData' => $this->mLimitReportJSData,
|
2021-11-09 16:31:27 +00:00
|
|
|
|
'CacheMessage' => $this->mCacheMessage,
|
2023-09-28 15:03:33 +00:00
|
|
|
|
'TimeProfile' => $this->mTimeProfile,
|
2024-10-03 22:56:04 +00:00
|
|
|
|
'ParseStartTime' => [], // don't serialize this
|
2020-09-29 19:18:00 +00:00
|
|
|
|
'PreventClickjacking' => $this->mPreventClickjacking,
|
|
|
|
|
|
'ExtraScriptSrcs' => $this->mExtraScriptSrcs,
|
|
|
|
|
|
'ExtraDefaultSrcs' => $this->mExtraDefaultSrcs,
|
|
|
|
|
|
'ExtraStyleSrcs' => $this->mExtraStyleSrcs,
|
2022-09-01 23:07:29 +00:00
|
|
|
|
'Flags' => $this->mFlags + (
|
|
|
|
|
|
// backward-compatibility: distinguish "no sections" from
|
|
|
|
|
|
// "sections not set" (Will be unnecessary after T327439.)
|
|
|
|
|
|
$this->mTOCData === null ? [] : [ 'mw:toc-set' => true ]
|
|
|
|
|
|
),
|
2020-09-29 19:18:00 +00:00
|
|
|
|
'SpeculativeRevId' => $this->mSpeculativeRevId,
|
|
|
|
|
|
'SpeculativePageIdUsed' => $this->speculativePageIdUsed,
|
|
|
|
|
|
'RevisionTimestampUsed' => $this->revisionTimestampUsed,
|
|
|
|
|
|
'RevisionUsedSha1Base36' => $this->revisionUsedSha1Base36,
|
|
|
|
|
|
'WrapperDivClasses' => $this->mWrapperDivClasses,
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// Fill in missing fields from parents. Array addition does not override existing fields.
|
2020-10-23 00:17:31 +00:00
|
|
|
|
$data += parent::toJsonArray();
|
2020-09-29 19:18:00 +00:00
|
|
|
|
|
|
|
|
|
|
// TODO: make more fields optional!
|
|
|
|
|
|
|
|
|
|
|
|
if ( $this->mMaxAdaptiveExpiry !== INF ) {
|
|
|
|
|
|
// NOTE: JSON can't encode infinity!
|
|
|
|
|
|
$data['MaxAdaptiveExpiry'] = $this->mMaxAdaptiveExpiry;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-02-01 19:51:07 +00:00
|
|
|
|
if ( $this->mTOCData ) {
|
|
|
|
|
|
// Temporarily add information from TOCData extension data
|
|
|
|
|
|
// T327439: We should eventually make the entire mTOCData
|
|
|
|
|
|
// serializable
|
|
|
|
|
|
$toc = $this->mTOCData->jsonSerialize();
|
2023-02-17 08:44:23 +00:00
|
|
|
|
if ( isset( $toc['extensionData'] ) ) {
|
|
|
|
|
|
$data['TOCExtensionData'] = $toc['extensionData'];
|
|
|
|
|
|
}
|
2023-02-01 19:51:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-09-29 19:18:00 +00:00
|
|
|
|
return $data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-04 08:58:57 +00:00
|
|
|
|
public static function newFromJsonArray( JsonDeserializer $deserializer, array $json ): ParserOutput {
|
2020-10-23 00:17:31 +00:00
|
|
|
|
$parserOutput = new ParserOutput();
|
2024-05-04 08:58:57 +00:00
|
|
|
|
$parserOutput->initFromJson( $deserializer, $json );
|
2020-10-23 00:17:31 +00:00
|
|
|
|
return $parserOutput;
|
2020-09-29 19:18:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Initialize member fields from an array returned by jsonSerialize().
|
2024-05-04 08:58:57 +00:00
|
|
|
|
* @param JsonDeserializer $deserializer
|
2020-09-29 19:18:00 +00:00
|
|
|
|
* @param array $jsonData
|
|
|
|
|
|
*/
|
2024-05-04 08:58:57 +00:00
|
|
|
|
protected function initFromJson( JsonDeserializer $deserializer, array $jsonData ): void {
|
|
|
|
|
|
parent::initFromJson( $deserializer, $jsonData );
|
2020-09-29 19:18:00 +00:00
|
|
|
|
|
2023-01-03 18:08:22 +00:00
|
|
|
|
// WARNING: When changing how this class is serialized, follow the instructions
|
|
|
|
|
|
// at <https://www.mediawiki.org/wiki/Manual:Parser_cache/Serialization_compatibility>!
|
|
|
|
|
|
|
2024-02-09 22:27:00 +00:00
|
|
|
|
$this->mRawText = $jsonData['Text'];
|
2024-10-02 18:53:34 +00:00
|
|
|
|
$this->mLanguageLinkMap = [];
|
|
|
|
|
|
foreach ( ( $jsonData['LanguageLinks'] ?? [] ) as $l ) {
|
|
|
|
|
|
$this->addLanguageLink( $l );
|
|
|
|
|
|
}
|
2020-09-29 19:18:00 +00:00
|
|
|
|
$this->mCategories = $jsonData['Categories'];
|
|
|
|
|
|
$this->mIndicators = $jsonData['Indicators'];
|
|
|
|
|
|
$this->mTitleText = $jsonData['TitleText'];
|
|
|
|
|
|
$this->mLinks = $jsonData['Links'];
|
|
|
|
|
|
$this->mLinksSpecial = $jsonData['LinksSpecial'];
|
|
|
|
|
|
$this->mTemplates = $jsonData['Templates'];
|
|
|
|
|
|
$this->mTemplateIds = $jsonData['TemplateIds'];
|
|
|
|
|
|
$this->mImages = $jsonData['Images'];
|
|
|
|
|
|
$this->mFileSearchOptions = $jsonData['FileSearchOptions'];
|
|
|
|
|
|
$this->mExternalLinks = $jsonData['ExternalLinks'];
|
|
|
|
|
|
$this->mInterwikiLinks = $jsonData['InterwikiLinks'];
|
|
|
|
|
|
$this->mNewSection = $jsonData['NewSection'];
|
|
|
|
|
|
$this->mHideNewSection = $jsonData['HideNewSection'];
|
|
|
|
|
|
$this->mNoGallery = $jsonData['NoGallery'];
|
|
|
|
|
|
$this->mHeadItems = $jsonData['HeadItems'];
|
2023-11-22 19:33:05 +00:00
|
|
|
|
$this->mModuleSet = array_fill_keys( $jsonData['Modules'], true );
|
|
|
|
|
|
$this->mModuleStyleSet = array_fill_keys( $jsonData['ModuleStyles'], true );
|
2020-09-29 19:18:00 +00:00
|
|
|
|
$this->mJsConfigVars = $jsonData['JsConfigVars'];
|
|
|
|
|
|
$this->mWarnings = $jsonData['Warnings'];
|
2022-09-01 23:07:29 +00:00
|
|
|
|
$this->mFlags = $jsonData['Flags'];
|
|
|
|
|
|
if (
|
|
|
|
|
|
$jsonData['Sections'] !== [] ||
|
|
|
|
|
|
// backward-compatibility: distinguish "no sections" from
|
|
|
|
|
|
// "sections not set" (Will be unnecessary after T327439.)
|
|
|
|
|
|
$this->getOutputFlag( 'mw:toc-set' )
|
|
|
|
|
|
) {
|
|
|
|
|
|
$this->setSections( $jsonData['Sections'] );
|
|
|
|
|
|
unset( $this->mFlags['mw:toc-set'] );
|
2023-02-01 19:51:07 +00:00
|
|
|
|
if ( isset( $jsonData['TOCExtensionData'] ) ) {
|
|
|
|
|
|
$tocData = $this->getTOCData(); // created by setSections() above
|
|
|
|
|
|
foreach ( $jsonData['TOCExtensionData'] as $key => $value ) {
|
|
|
|
|
|
$tocData->setExtensionData( $key, $value );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-09-01 23:07:29 +00:00
|
|
|
|
}
|
2020-11-03 10:58:00 +00:00
|
|
|
|
$this->mProperties = self::detectAndDecodeBinary( $jsonData['Properties'] );
|
2020-09-29 19:18:00 +00:00
|
|
|
|
$this->mTimestamp = $jsonData['Timestamp'];
|
|
|
|
|
|
$this->mEnableOOUI = $jsonData['EnableOOUI'];
|
2022-03-30 16:45:00 +00:00
|
|
|
|
$this->setIndexPolicy( $jsonData['IndexPolicy'] );
|
2022-07-07 20:32:08 +00:00
|
|
|
|
$this->mExtensionData = $jsonData['ExtensionData'] ?? [];
|
2020-09-29 19:18:00 +00:00
|
|
|
|
$this->mLimitReportData = $jsonData['LimitReportData'];
|
|
|
|
|
|
$this->mLimitReportJSData = $jsonData['LimitReportJSData'];
|
2021-11-09 16:31:27 +00:00
|
|
|
|
$this->mCacheMessage = $jsonData['CacheMessage'] ?? '';
|
2024-10-03 22:56:04 +00:00
|
|
|
|
$this->mParseStartTime = []; // invalid after reloading
|
2023-09-28 15:03:33 +00:00
|
|
|
|
$this->mTimeProfile = $jsonData['TimeProfile'] ?? [];
|
2020-09-29 19:18:00 +00:00
|
|
|
|
$this->mPreventClickjacking = $jsonData['PreventClickjacking'];
|
|
|
|
|
|
$this->mExtraScriptSrcs = $jsonData['ExtraScriptSrcs'];
|
|
|
|
|
|
$this->mExtraDefaultSrcs = $jsonData['ExtraDefaultSrcs'];
|
|
|
|
|
|
$this->mExtraStyleSrcs = $jsonData['ExtraStyleSrcs'];
|
|
|
|
|
|
$this->mSpeculativeRevId = $jsonData['SpeculativeRevId'];
|
|
|
|
|
|
$this->speculativePageIdUsed = $jsonData['SpeculativePageIdUsed'];
|
|
|
|
|
|
$this->revisionTimestampUsed = $jsonData['RevisionTimestampUsed'];
|
|
|
|
|
|
$this->revisionUsedSha1Base36 = $jsonData['RevisionUsedSha1Base36'];
|
|
|
|
|
|
$this->mWrapperDivClasses = $jsonData['WrapperDivClasses'];
|
|
|
|
|
|
$this->mMaxAdaptiveExpiry = $jsonData['MaxAdaptiveExpiry'] ?? INF;
|
|
|
|
|
|
}
|
2020-11-03 10:58:00 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Finds any non-utf8 strings in the given array and replaces them with
|
|
|
|
|
|
* an associative array that wraps a base64 encoded version of the data.
|
|
|
|
|
|
* Inverse of detectAndDecodeBinary().
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param array $properties
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return array
|
|
|
|
|
|
*/
|
|
|
|
|
|
private static function detectAndEncodeBinary( array $properties ) {
|
|
|
|
|
|
foreach ( $properties as $key => $value ) {
|
|
|
|
|
|
if ( is_string( $value ) ) {
|
|
|
|
|
|
if ( !mb_detect_encoding( $value, 'UTF-8', true ) ) {
|
|
|
|
|
|
$properties[$key] = [
|
2022-07-07 20:32:08 +00:00
|
|
|
|
// T313818: This key name conflicts with JsonCodec
|
2020-11-03 10:58:00 +00:00
|
|
|
|
'_type_' => 'string',
|
|
|
|
|
|
'_encoding_' => 'base64',
|
|
|
|
|
|
'_data_' => base64_encode( $value ),
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $properties;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Finds any associative arrays that represent encoded binary strings, and
|
|
|
|
|
|
* replaces them with the decoded binary data.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param array $properties
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return array
|
|
|
|
|
|
*/
|
|
|
|
|
|
private static function detectAndDecodeBinary( array $properties ) {
|
|
|
|
|
|
foreach ( $properties as $key => $value ) {
|
|
|
|
|
|
if ( is_array( $value ) && isset( $value['_encoding_'] ) ) {
|
|
|
|
|
|
if ( $value['_encoding_'] === 'base64' ) {
|
|
|
|
|
|
$properties[$key] = base64_decode( $value['_data_'] );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $properties;
|
|
|
|
|
|
}
|
2020-11-05 17:04:37 +00:00
|
|
|
|
|
|
|
|
|
|
public function __wakeup() {
|
2024-04-24 04:42:59 +00:00
|
|
|
|
$oldAliases = [
|
|
|
|
|
|
// This was the pre-namespace name of the class, which is still
|
|
|
|
|
|
// used in pre-1.42 serialized objects.
|
|
|
|
|
|
'ParserOutput',
|
|
|
|
|
|
];
|
2020-11-05 17:05:40 +00:00
|
|
|
|
// Backwards compatibility, pre 1.36
|
2024-04-24 04:42:59 +00:00
|
|
|
|
$priorAccessedOptions = $this->getGhostFieldValue( 'mAccessedOptions', ...$oldAliases );
|
2020-11-05 17:05:40 +00:00
|
|
|
|
if ( $priorAccessedOptions ) {
|
|
|
|
|
|
$this->mParseUsedOptions = $priorAccessedOptions;
|
2020-11-05 17:04:37 +00:00
|
|
|
|
}
|
2022-03-30 16:45:00 +00:00
|
|
|
|
// Backwards compatibility, pre 1.39
|
2024-04-24 04:42:59 +00:00
|
|
|
|
$priorIndexPolicy = $this->getGhostFieldValue( 'mIndexPolicy', ...$oldAliases );
|
2022-03-30 16:45:00 +00:00
|
|
|
|
if ( $priorIndexPolicy ) {
|
|
|
|
|
|
$this->setIndexPolicy( $priorIndexPolicy );
|
2022-02-07 20:47:11 +00:00
|
|
|
|
}
|
2022-09-01 23:07:29 +00:00
|
|
|
|
// Backwards compatibility, pre 1.40
|
2024-04-24 04:42:59 +00:00
|
|
|
|
$mSections = $this->getGhostFieldValue( 'mSections', ...$oldAliases );
|
2022-09-01 23:07:29 +00:00
|
|
|
|
if ( $mSections !== null && $mSections !== [] ) {
|
|
|
|
|
|
$this->setSections( $mSections );
|
|
|
|
|
|
}
|
2023-11-22 19:33:05 +00:00
|
|
|
|
// Backwards compatibility, pre 1.42
|
2024-04-24 04:42:59 +00:00
|
|
|
|
$mModules = $this->getGhostFieldValue( 'mModules', ...$oldAliases );
|
2023-11-22 19:33:05 +00:00
|
|
|
|
if ( $mModules !== null && $mModules !== [] ) {
|
|
|
|
|
|
$this->addModules( $mModules );
|
|
|
|
|
|
}
|
|
|
|
|
|
// Backwards compatibility, pre 1.42
|
2024-04-24 04:42:59 +00:00
|
|
|
|
$mModuleStyles = $this->getGhostFieldValue( 'mModuleStyles', ...$oldAliases );
|
2023-11-22 19:33:05 +00:00
|
|
|
|
if ( $mModuleStyles !== null && $mModuleStyles !== [] ) {
|
|
|
|
|
|
$this->addModuleStyles( $mModuleStyles );
|
|
|
|
|
|
}
|
2024-02-09 22:27:00 +00:00
|
|
|
|
// Backwards compatibility, pre 1.42
|
2024-04-24 04:42:59 +00:00
|
|
|
|
$mText = $this->getGhostFieldValue( 'mText', ...$oldAliases );
|
2024-02-09 22:27:00 +00:00
|
|
|
|
if ( $mText !== null ) {
|
|
|
|
|
|
$this->setRawText( $mText );
|
|
|
|
|
|
}
|
2024-06-14 03:54:52 +00:00
|
|
|
|
// Backwards compatibility, pre 1.42
|
|
|
|
|
|
$ll = $this->getGhostFieldValue( 'mLanguageLinks', ...$oldAliases );
|
|
|
|
|
|
if ( $ll !== null && $ll !== [] ) {
|
|
|
|
|
|
foreach ( $ll as $l ) {
|
|
|
|
|
|
$this->addLanguageLink( $l );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-04-24 04:42:59 +00:00
|
|
|
|
// Backward compatibility with private fields, pre 1.42
|
|
|
|
|
|
$oldPrivateFields = [
|
|
|
|
|
|
'mRawText',
|
|
|
|
|
|
'mCategories',
|
|
|
|
|
|
'mIndicators',
|
|
|
|
|
|
'mTitleText',
|
|
|
|
|
|
'mLinks',
|
|
|
|
|
|
'mLinksSpecial',
|
|
|
|
|
|
'mTemplates',
|
|
|
|
|
|
'mTemplateIds',
|
|
|
|
|
|
'mImages',
|
|
|
|
|
|
'mFileSearchOptions',
|
|
|
|
|
|
'mExternalLinks',
|
|
|
|
|
|
'mInterwikiLinks',
|
|
|
|
|
|
'mNewSection',
|
|
|
|
|
|
'mHideNewSection',
|
|
|
|
|
|
'mNoGallery',
|
|
|
|
|
|
'mHeadItems',
|
|
|
|
|
|
'mModuleSet',
|
|
|
|
|
|
'mModuleStyleSet',
|
|
|
|
|
|
'mJsConfigVars',
|
|
|
|
|
|
'mWarnings',
|
|
|
|
|
|
'mWarningMsgs',
|
|
|
|
|
|
'mTOCData',
|
|
|
|
|
|
'mProperties',
|
|
|
|
|
|
'mTimestamp',
|
|
|
|
|
|
'mEnableOOUI',
|
|
|
|
|
|
'mIndexSet',
|
|
|
|
|
|
'mNoIndexSet',
|
|
|
|
|
|
'mExtensionData',
|
|
|
|
|
|
'mLimitReportData',
|
|
|
|
|
|
'mLimitReportJSData',
|
|
|
|
|
|
'mCacheMessage',
|
|
|
|
|
|
'mParseStartTime',
|
|
|
|
|
|
'mTimeProfile',
|
|
|
|
|
|
'mPreventClickjacking',
|
|
|
|
|
|
'mExtraScriptSrcs',
|
|
|
|
|
|
'mExtraDefaultSrcs',
|
|
|
|
|
|
'mExtraStyleSrcs',
|
|
|
|
|
|
'mFlags',
|
|
|
|
|
|
'mSpeculativeRevId',
|
|
|
|
|
|
'speculativePageIdUsed',
|
|
|
|
|
|
'revisionTimestampUsed',
|
|
|
|
|
|
'revisionUsedSha1Base36',
|
|
|
|
|
|
'mWrapperDivClasses',
|
|
|
|
|
|
'mMaxAdaptiveExpiry',
|
|
|
|
|
|
];
|
|
|
|
|
|
foreach ( $oldPrivateFields as $f ) {
|
|
|
|
|
|
$this->restoreAliasedGhostField( $f, ...$oldAliases );
|
|
|
|
|
|
}
|
2024-10-03 22:56:04 +00:00
|
|
|
|
$this->clearParseStartTime();
|
2020-11-05 17:04:37 +00:00
|
|
|
|
}
|
2020-11-10 01:47:09 +00:00
|
|
|
|
|
2023-10-20 13:21:53 +00:00
|
|
|
|
public function __clone() {
|
|
|
|
|
|
// It seems that very little of this object needs to be explicitly deep-cloned
|
|
|
|
|
|
// while keeping copies reasonably separated.
|
|
|
|
|
|
// Most of the non-scalar properties of this object are either
|
|
|
|
|
|
// - (potentially multi-nested) arrays of scalars (which get deep-cloned), or
|
|
|
|
|
|
// - arrays that may contain arbitrary elements (which don't necessarily get
|
|
|
|
|
|
// deep-cloned), but for which no particular care elsewhere is given to
|
|
|
|
|
|
// copying their references around (e.g. mJsConfigVars).
|
|
|
|
|
|
// Hence, we are not going out of our way to ensure that the references to innermost
|
|
|
|
|
|
// objects that may appear in a ParserOutput are unique. If that becomes the
|
|
|
|
|
|
// expectation at any point, this method will require updating as well.
|
|
|
|
|
|
// The exception is TOCData (which is an object), which we clone explicitly.
|
|
|
|
|
|
if ( $this->mTOCData ) {
|
|
|
|
|
|
$this->mTOCData = clone $this->mTOCData;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Returns the content holder text of the ParserOutput.
|
|
|
|
|
|
* This will eventually be replaced by something like getContentHolder()->getText() when we have a
|
|
|
|
|
|
* ContentHolder/HtmlHolder class.
|
|
|
|
|
|
* @internal
|
|
|
|
|
|
* @unstable
|
|
|
|
|
|
* @return string
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function getContentHolderText(): string {
|
|
|
|
|
|
return $this->getRawText();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Sets the content holder text of the ParserOutput.
|
|
|
|
|
|
* This will eventually be replaced by something like getContentHolder()->setText() when we have a
|
|
|
|
|
|
* ContentHolder/HtmlHolder class.
|
|
|
|
|
|
* @internal
|
|
|
|
|
|
* @unstable
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function setContentHolderText( string $s ): void {
|
2024-02-09 22:27:00 +00:00
|
|
|
|
$this->setRawText( $s );
|
2023-10-20 13:21:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-11-10 01:47:09 +00:00
|
|
|
|
public function __get( $name ) {
|
|
|
|
|
|
if ( property_exists( get_called_class(), $name ) ) {
|
|
|
|
|
|
// Direct access to a public property, deprecated.
|
|
|
|
|
|
wfDeprecatedMsg( "ParserOutput::{$name} public read access deprecated", '1.38' );
|
|
|
|
|
|
return $this->$name;
|
|
|
|
|
|
} elseif ( property_exists( $this, $name ) ) {
|
|
|
|
|
|
// Dynamic property access, deprecated.
|
|
|
|
|
|
wfDeprecatedMsg( "ParserOutput::{$name} dynamic property read access deprecated", '1.38' );
|
|
|
|
|
|
return $this->$name;
|
|
|
|
|
|
} else {
|
2022-03-30 16:45:00 +00:00
|
|
|
|
trigger_error( "Inaccessible property via __get(): $name" );
|
2020-11-10 01:47:09 +00:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function __set( $name, $value ) {
|
|
|
|
|
|
if ( property_exists( get_called_class(), $name ) ) {
|
|
|
|
|
|
// Direct access to a public property, deprecated.
|
|
|
|
|
|
wfDeprecatedMsg( "ParserOutput::$name public write access deprecated", '1.38' );
|
|
|
|
|
|
$this->$name = $value;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Dynamic property access, deprecated.
|
|
|
|
|
|
wfDeprecatedMsg( "ParserOutput::$name dynamic property write access deprecated", '1.38' );
|
|
|
|
|
|
$this->$name = $value;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2007-01-20 12:50:56 +00:00
|
|
|
|
}
|
2023-12-14 19:20:33 +00:00
|
|
|
|
|
2024-07-05 16:16:27 +00:00
|
|
|
|
/** @deprecated class alias since 1.42 */
|
2023-12-14 19:20:33 +00:00
|
|
|
|
class_alias( ParserOutput::class, 'ParserOutput' );
|