(e.g. MediaWiki:Common.js), CSS or JSON was separated from 'editinterface'
and is available under 'editsitejs'/'editsitecss'/'editsitejson'. Having
'editinterface' is still necessary to edit such pages.
+* $wgMultiContentRevisionSchemaMigrationStage now defaults to writing both the
+ old and the new schema, but reading the new schema, so Multi-Content Revisions
+ (MCR) are now functional per default. The new default value of the setting is
+ SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_NEW.
==== Removed configuration ====
* $wgEnableAPI and $wgEnableWriteAPI – These settings, deprecated in 1.31,
=== Bug fixes in 1.32 ===
* SpecialPage::execute() will now only call checkLoginSecurityLevel() if
getLoginSecurityLevel() returns non-false.
+* (T43720, T46197) Improved page display title handling for category pages
=== Action API changes in 1.32 ===
* Added templated parameters.
Define $wgProfiler via LocalSettings.php instead.
* The mw.loader.addSource() is now considered a private method, and no longer
supports the `id, url` signature. Use the `Object` parameter instead.
+* The backwards-compatibility code in HTMLForm to add a drop-down control to an
+ option that is not set to be a drop-down if the "mw-chosen" class is present,
+ is now removed.
+* Several collations were removed. They were workarounds for bugs in the ICU
+ library and they are no longer needed (as of ICU 57.1):
+ * 'uppercase-se' (NorthernSamiUppercaseCollation) - use 'uca-se' instead
+ * 'xx-uca-et' (CollationEt) - use 'uca-et' instead
+ * 'xx-uca-fa' (CollationFa) - use 'uca-fa' instead
+* The hooks 'SpecialRecentChangesFilters' & 'SpecialWatchlistFilters' deprecated
+ in 1.23 were removed. Instead, use 'ChangesListSpecialPageStructuredFilters'.
+ The ChangesListSpecialPage code for these legacy hooks, and their use in
+ SpecialRecentchanges.php and SpecialWatchlist, was also removed:
+ * ChangesListSpecialPage->getCustomFilters()
+ * ChangesListSpecialPage->getFilterGroupDefinitionFromLegacyCustomFilters()
+ * ChangesListSpecialPage::customFilters
+* The global function wfUseMW, deprecated since 1.26, has now been removed. Use
+ the "requires" property of static extension registration instead.
=== Deprecations in 1.32 ===
* HTMLForm::setSubmitProgressive() is deprecated. No need to call it. Submit
* Overriding SearchEngine::{searchText,searchTitle,searchArchiveTitle}
in extending classes is deprecated. Extend related doSearch* methods
instead.
-* CollationFa has been removed completely as it's not needed anymore
* The following 'mediawiki.api' plugin modules were merged into mediawiki.api
and deprecated: mediawiki.api.category, mediawiki.api.edit,
mediawiki.api.login, mediawiki.api.options, mediawiki.api.parse,
'help', 'help-message', 'help-messages' instead.
* (T197179) HTMLFormField::getNotices() is now deprecated.
* The jquery.localize module is now deprecated. Use jquery.i18n instead.
+ * The SecondaryDataUpdates hook was deprecated in favor of RevisionDataUpdates,
+ or overriding ContentHandler::getSecondaryDataUpdates (T194038).
+ * The WikiPageDeletionUpdates hook was deprecated in favor of
+ PageDeletionDataUpdates, or overriding ContentHandler::getDeletionDataUpdates
+ (T194038).
+ * Content::getSecondaryDataUpdates has been deprecated in favor of
+ ContentHandler::getSecondaryDataUpdates() for overriding by extensions
+ (T194038).
+ Application logic should call WikiPage::doSecondaryDataUpdates() (T194037).
+ * Content::getDeletionUpdates has been deprecated in favor of
+ ContentHandler::getDeletionUpdates() for overriding by extensions (T194038).
+ Application logic should call WikiPage::doSecondaryDataUpdates() (T194037).
=== Other changes in 1.32 ===
* (T198811) The following tables have had their UNIQUE indexes turned into
`'help-inline' => false`.
* The archive table's ar_rev_id field is now unique.
* Special:BotPasswords now requires reauthentication.
+* (T174023) Multi-Content Revision (MCR) capabilities were introduced into the
+ storage layer and have basic support for display. No user interface exists
+ yet for creating or managing content in slots beides the main slot. See
+ <https://www.mediawiki.org/wiki/Multi-Content_Revisions> for more
+ information.
* …
== Compatibility ==
'CodeContentHandler' => __DIR__ . '/includes/content/CodeContentHandler.php',
'Collation' => __DIR__ . '/includes/collation/Collation.php',
'CollationCkb' => __DIR__ . '/includes/collation/CollationCkb.php',
- 'CollationEt' => __DIR__ . '/includes/collation/CollationEt.php',
'CommandLineInc' => __DIR__ . '/maintenance/commandLine.inc',
'CommandLineInstaller' => __DIR__ . '/maintenance/install.php',
'CommentStore' => __DIR__ . '/includes/CommentStore.php',
'MediaWiki\\ProcOpenError' => __DIR__ . '/includes/exception/ProcOpenError.php',
'MediaWiki\\Revision\\RenderedRevision' => __DIR__ . '/includes/Revision/RenderedRevision.php',
'MediaWiki\\Revision\\RevisionRenderer' => __DIR__ . '/includes/Revision/RevisionRenderer.php',
+ 'MediaWiki\\Revision\\SlotRenderingProvider' => __DIR__ . '/includes/Revision/SlotRenderingProvider.php',
'MediaWiki\\Search\\ParserOutputSearchDataExtractor' => __DIR__ . '/includes/search/ParserOutputSearchDataExtractor.php',
'MediaWiki\\ShellDisabledError' => __DIR__ . '/includes/exception/ShellDisabledError.php',
'MediaWiki\\Site\\MediaWikiPageNameNormalizer' => __DIR__ . '/includes/site/MediaWikiPageNameNormalizer.php',
AND in the final query)
$logTypes: Array of log types being queried
-'ArticleAfterFetchContentObject': After fetching content of an article from the
-database.
+'ArticleAfterFetchContentObject': DEPRECATED since 1.32, use ArticleRevisionViewCustom
+to control output. After fetching content of an article from the database.
&$article: the article (object) being loaded from the database
&$content: the content of the article, as a Content object
$diffEngine: the DifferenceEngine
$output: the OutputPage object
-'ArticleContentViewCustom': Allows to output the text of the article in a
-different format than wikitext. Note that it is preferable to implement proper
-handing for a custom data type using the ContentHandler facility.
+'ArticleRevisionViewCustom': Allows custom rendering of an article's content.
+Note that it is preferable to implement proper handing for a custom data type using
+the ContentHandler facility.
+$revision: content of the page, as a RevisionRecord object, or null if the revision
+ could not be loaded. May also be a fake that wraps content supplied by an extension.
+$title: title of the page
+$oldid: the requested revision id, or 0 for the currrent revision.
+$output: a ParserOutput object
+
+'ArticleContentViewCustom': DEPRECATED since 1.32, use ArticleRevisionViewCustom instead,
+or provide an appropriate ContentHandler. Allows to output the text of the article in a
+different format than wikitext.
$content: content of the page, as a Content object
$title: title of the page
-$output: reference to $wgOut
+$output: a ParserOutput object
'ArticleDelete': Before an article is deleted.
&$wikiPage: the WikiPage (object) being deleted
$article: Article object
$patrolFooterShown: boolean whether patrol footer is shown
-'ArticleViewHeader': Before the parser cache is about to be tried for article
-viewing.
+'ArticleViewHeader': Control article output. Called before the parser cache is about
+to be tried for article viewing.
&$article: the article
&$pcache: whether to try the parser cache or not
&$outputDone: whether the output for this page finished or not. Set to
(Used to be called $baseRevId.)
$undidRevId: the rev ID (or 0) this edit undid
+ 'PageDeletionDataUpdates': Called when constructing a list of DeferrableUpdate to be
+ executed when a page is deleted.
+ $title The Title of the page being deleted.
+ $revision A RevisionRecord representing the page's current revision at the time of deletion.
+ &$updates A list of DeferrableUpdate that can be manipulated by the hook handler.
+
'PageHistoryBeforeList': When a history page list is about to be constructed.
&$article: the article that the history is loading for
$context: RequestContext object
added to any module.
&$ResourceLoader: object
+ 'RevisionDataUpdates': Called when constructing a list of DeferrableUpdate to be
+ executed to record secondary data about a revision.
+ $title The Title of the page the revision belongs to
+ $renderedRevision a RenderedRevision object representing the new revision and providing access
+ to the RevisionRecord as well as ParserOutput of that revision.
+ &$updates A list of DeferrableUpdate that can be manipulated by the hook handler.
+
'RevisionRecordInserted': Called after a revision is inserted into the database.
$revisionRecord: the RevisionRecord that has just been inserted.
Note that lists should be in the format name => object and the names in both
lists should be distinct.
- 'SecondaryDataUpdates': Allows modification of the list of DataUpdates to
- perform when page content is modified. Currently called by
- AbstractContent::getSecondaryDataUpdates.
+ 'SecondaryDataUpdates': DEPRECATED! Use RevisionDataUpdates or override
+ ContentHandler::getSecondaryDataUpdates instead.
+ Allows modification of the list of DataUpdates to perform when page content is modified.
$title: Title of the page that is being edited.
$oldContent: Content object representing the page's content before the edit.
$recursive: bool indicating whether DataUpdates should trigger recursive
&$title: If the hook returns false, a Title object to use instead of the
result from the normal query
-'SpecialRecentChangesFilters': DEPRECATED since 1.23! Use
-ChangesListSpecialPageStructuredFilters instead.
-Called after building form options at RecentChanges.
-$special: the special page object
-&$filters: associative array of filter definitions. The keys are the HTML
- name/URL parameters. Each key maps to an associative array with a 'msg'
- (message key) and a 'default' value.
-
'SpecialRecentChangesPanel': Called when building form options in
SpecialRecentChanges.
&$extraOpts: array of added items, to which can be added
$wgVersion: Current $wgVersion for you to use
&$versionUrl: Raw url to link to (eg: release notes)
-'SpecialWatchlistFilters': DEPRECATED since 1.23! Use
-ChangesListSpecialPageStructuredFilters instead.
-Called after building form options at Watchlist.
-$special: the special page object
-&$filters: associative array of filter definitions. The keys are the HTML
- name/URL parameters. Each key maps to an associative array with a 'msg'
- (message key) and a 'default' value.
-
'SpecialWatchlistGetNonRevisionTypes': Called when building sql query for
SpecialWatchlist. Allows extensions to register custom values they have
inserted to rc_type so they can be returned as part of the watchlist.
&$opts: Options to use for the query
&$join: Join conditions
- 'WikiPageDeletionUpdates': manipulate the list of DeferrableUpdates to be
- applied when a page is deleted. Called in WikiPage::getDeletionUpdates(). Note
- that updates specific to a content model should be provided by the respective
- Content's getDeletionUpdates() method.
+ 'WikiPageDeletionUpdates': DEPRECATED! Use PageDeletionDataUpdates or
+ override ContentHandler::getDeletionDataUpdates instead.
+ Manipulates the list of DeferrableUpdates to be applied when a page is deleted.
$page: the WikiPage
$content: the Content to generate updates for, or null in case the page revision
could not be loaded. The delete will succeed despite this.
*
* @since 1.32
*/
- class RenderedRevision {
+ class RenderedRevision implements SlotRenderingProvider {
/**
* @var Title
* but should use a RevisionRenderer instead.
*
* @param Title $title
- * @param RevisionRecord $revision
+ * @param RevisionRecord $revision The revision to render. The content for rendering will be
+ * taken from this RevisionRecord. However, if the RevisionRecord is not complete
+ * according isReadyForInsertion(), but a revision ID is known, the parser may load
+ * the revision from the database if it needs revision meta data to handle magic
+ * words like {{REVISIONUSER}}.
* @param ParserOptions $options
* @param callable $combineOutput Callback for combining slot output into revision output.
* Signature: function ( RenderedRevision $this ): ParserOutput.
private function setRevisionInternal( RevisionRecord $revision ) {
$this->revision = $revision;
- // Make sure the parser uses the correct Revision object
- $title = $this->title;
- $oldCallback = $this->options->getCurrentRevisionCallback();
- $this->options->setCurrentRevisionCallback(
- function ( Title $parserTitle, $parser = false ) use ( $title, $oldCallback ) {
- if ( $parserTitle->equals( $title ) ) {
- $legacyRevision = new Revision( $this->revision );
- return $legacyRevision;
- } else {
- return call_user_func( $oldCallback, $parserTitle, $parser );
+ // Force the parser to use $this->revision to resolve magic words like {{REVISIONUSER}}
+ // if the revision is either known to be complete, or it doesn't have a revision ID set.
+ // If it's incomplete and we have a revision ID, the parser can do better by loading
+ // the revision from the database if needed to handle a magic word.
+ //
+ // The following considerations inform the logic described above:
+ //
+ // 1) If we have a saved revision already loaded, we want the parser to use it, instead of
+ // loading it again.
+ //
+ // 2) If the revision is a fake that wraps some kind of synthetic content, such as an
+ // error message from Article, it should be used directly and things like {{REVISIONUSER}}
+ // should not expected to work, since there may not even be an actual revision to
+ // refer to.
+ //
+ // 3) If the revision is a fake constructed around a Title, a Content object, and
+ // a revision ID, to provide backwards compatibility to code that has access to those
+ // but not to a complete RevisionRecord for rendering, then we want the Parser to
+ // load the actual revision from the database when it encounters a magic word like
+ // {{REVISIONUSER}}, but we don't want to load that revision ahead of time just in case.
+ //
+ // 4) Previewing an edit to a template should use the submitted unsaved
+ // MutableRevisionRecord for self-transclusions in the template's documentation (see T7278).
+ // That revision would be complete except for the ID field.
+ //
+ // 5) Pre-save transform would provide a RevisionRecord that has all meta-data but is
+ // incomplete due to not yet having content set. However, since it doesn't have a revision
+ // ID either, the below code would still force it to be used, allowing
+ // {{subst::REVISIONUSER}} to function as expected.
+
+ if ( $this->revision->isReadyForInsertion() || !$this->revision->getId() ) {
+ $title = $this->title;
+ $oldCallback = $this->options->getCurrentRevisionCallback();
+ $this->options->setCurrentRevisionCallback(
+ function ( Title $parserTitle, $parser = false ) use ( $title, $oldCallback ) {
+ if ( $title->equals( $parserTitle ) ) {
+ $legacyRevision = new Revision( $this->revision );
+ return $legacyRevision;
+ } else {
+ return call_user_func( $oldCallback, $parserTitle, $parser );
+ }
}
- }
- );
+ );
+ }
}
/**
use Content;
use ContentHandler;
use DataUpdate;
+ use DeferrableUpdate;
use DeferredUpdates;
use Hooks;
use IDBAccessObject;
use InvalidArgumentException;
use JobQueueGroup;
use Language;
+ use LinksDeletionUpdate;
use LinksUpdate;
use LogicException;
use MediaWiki\Edit\PreparedEdit;
*
* Contains the following fields:
* - oldRevision (RevisionRecord|null): the revision that was current before the change
- * associated with this update. Might not be set, use getOldRevision() instead of direct
- * access.
+ * associated with this update. Might not be set, use getParentRevision().
* - oldId (int|null): the id of the above revision. 0 if there is no such revision (the change
* was about creating a new page); null if not known (that should not happen).
* - oldIsRedirect (bool|null): whether the page was a redirect before the change. Lazy-loaded,
*/
private $slotsUpdate = null;
+ /**
+ * @var RevisionRecord|null
+ */
+ private $parentRevision = null;
+
/**
* @var RevisionRecord|null
*/
}
/**
- * Returns the revision that was current before the edit. This would be null if the edit
- * created the page, or the revision's parent for a regular edit, or the revision itself
- * for a null-edit.
- * Only defined after calling grabCurrentRevision() or prepareContent() or prepareUpdate()!
+ * Returns the parent revision of the new revision wrapped by this update.
+ * If the update is a null-edit, this will return the parent of the current (and new) revision.
+ * This will return null if the revision wrapped by this update created the page.
+ * Only defined after calling prepareContent() or prepareUpdate()!
*
- * @return RevisionRecord|null the revision that was current before the edit, or null if
- * the edit created the page.
+ * @return RevisionRecord|null the parent revision of the new revision, or null if
+ * the update created the page.
*/
- private function getOldRevision() {
- $this->assertHasPageState( __METHOD__ );
+ private function getParentRevision() {
+ $this->assertPrepared( __METHOD__ );
- // If 'oldRevision' is not set, load it!
- // Useful if $this->oldPageState is initialized by prepareUpdate.
- if ( !array_key_exists( 'oldRevision', $this->pageState ) ) {
- /** @var int $oldId */
- $oldId = $this->pageState['oldId'];
- $flags = $this->useMaster() ? RevisionStore::READ_LATEST : 0;
- $this->pageState['oldRevision'] = $oldId
- ? $this->revisionStore->getRevisionById( $oldId, $flags )
- : null;
+ if ( $this->parentRevision ) {
+ return $this->parentRevision;
}
- return $this->pageState['oldRevision'];
+ if ( !$this->pageState['oldId'] ) {
+ // If there was no current revision, there is no parent revision,
+ // since the page didn't exist.
+ return null;
+ }
+
+ $oldId = $this->revision->getParentId();
+ $flags = $this->useMaster() ? RevisionStore::READ_LATEST : 0;
+ $this->parentRevision = $oldId
+ ? $this->revisionStore->getRevisionById( $oldId, $flags )
+ : null;
+
+ return $this->parentRevision;
}
/**
* @note After prepareUpdate() was called, grabCurrentRevision() will throw an exception
* to avoid confusion, since the page's current revision is then the new revision after
* the edit, which was presumably passed to prepareUpdate() as the $revision parameter.
- * Use getOldRevision() instead to access the revision that used to be current before the
- * edit.
+ * Use getParentRevision() instead to access the revision that is the parent of the
+ * new revision.
*
* @return RevisionRecord|null the page's current revision, or null if the page does not
* yet exist.
// prepareUpdate() is redundant for null-edits
$this->doTransition( 'has-revision' );
+ } else {
+ $this->parentRevision = $parentRevision;
}
}
$this->assertPrepared( __METHOD__ );
if ( !$this->slotsUpdate ) {
- $old = $this->getOldRevision();
+ $old = $this->getParentRevision();
$this->slotsUpdate = RevisionSlotsUpdate::newFromRevisionSlots(
$this->revision->getSlots(),
$old ? $old->getSlots() : null
} else {
throw new LogicException(
'Trying to re-use DerivedPageDataUpdater with revision '
- .$revision->getId()
+ . $revision->getId()
. ', but it\'s already bound to revision '
. $this->revision->getId()
);
if ( !$this->user->equals( $user ) ) {
throw new LogicException(
'The Revision provided has a mismatching actor: expected '
- .$this->user->getName()
+ . $this->user->getName()
. ', got '
. $user->getName()
);
/**
* @param bool $recursive
*
- * @return DataUpdate[]
+ * @return DeferrableUpdate[]
*/
public function getSecondaryDataUpdates( $recursive = false ) {
- // TODO: MCR: getSecondaryDataUpdates() needs a complete overhaul to avoid DataUpdates
- // from different slots overwriting each other in the database. Plan:
- // * replace direct calls to Content::getSecondaryDataUpdates() with calls to this method
- // * Construct LinksUpdate here, on the combined ParserOutput, instead of in AbstractContent
- // for each slot.
- // * Pass $slot into getSecondaryDataUpdates() - probably be introducing a new duplicate
- // version of this function in ContentHandler.
- // * The new method gets the PreparedEdit, but no $recursive flag (that's for LinksUpdate)
- // * Hack: call both the old and the new getSecondaryDataUpdates method here; Pass
- // the per-slot ParserOutput to the old method, for B/C.
- // * Hack: If there is more than one slot, filter LinksUpdate from the DataUpdates
- // returned by getSecondaryDataUpdates, and use a LinksUpdated for the combined output
- // instead.
- // * Call the SecondaryDataUpdates hook here (or kill it - its signature doesn't make sense)
-
- $content = $this->getSlots()->getContent( 'main' );
-
- // NOTE: $output is the combined output, to be shown in the default view.
+ if ( $this->isContentDeleted() ) {
+ // This shouldn't happen, since the current content is always public,
+ // and DataUpates are only needed for current content.
+ return [];
+ }
+
$output = $this->getCanonicalParserOutput();
- $updates = $content->getSecondaryDataUpdates(
- $this->getTitle(), null, $recursive, $output
+ // Construct a LinksUpdate for the combined canonical output.
+ $linksUpdate = new LinksUpdate(
+ $this->getTitle(),
+ $output,
+ $recursive
);
- return $updates;
+ $allUpdates = [ $linksUpdate ];
+
+ // NOTE: Run updates for all slots, not just the modified slots! Otherwise,
+ // info for an inherited slot may end up being removed. This is also needed
+ // to ensure that purges are effective.
+ $renderedRevision = $this->getRenderedRevision();
+ foreach ( $this->getSlots()->getSlotRoles() as $role ) {
+ $slot = $this->getRawSlot( $role );
+ $content = $slot->getContent();
+ $handler = $content->getContentHandler();
+
+ $updates = $handler->getSecondaryDataUpdates(
+ $this->getTitle(),
+ $content,
+ $role,
+ $renderedRevision
+ );
+ $allUpdates = array_merge( $allUpdates, $updates );
+
+ // TODO: remove B/C hack in 1.32!
+ // NOTE: we assume that the combined output contains all relevant meta-data for
+ // all slots!
+ $legacyUpdates = $content->getSecondaryDataUpdates(
+ $this->getTitle(),
+ null,
+ $recursive,
+ $output
+ );
+
+ // HACK: filter out redundant and incomplete LinksUpdates
+ $legacyUpdates = array_filter( $legacyUpdates, function ( $update ) {
+ return !( $update instanceof LinksUpdate );
+ } );
+
+ $allUpdates = array_merge( $allUpdates, $legacyUpdates );
+ }
+
+ // XXX: if a slot was removed by an earlier edit, but deletion updates failed to run at
+ // that time, we don't know for which slots to run deletion updates when purging a page.
+ // We'd have to examine the entire history of the page to determine that. Perhaps there
+ // could be a "try extra hard" mode for that case that would run a DB query to find all
+ // roles/models ever used on the page. On the other hand, removing slots should be quite
+ // rare, so perhaps this isn't worth the trouble.
+
+ // TODO: consolidate with similar logic in WikiPage::getDeletionUpdates()
+ $wikiPage = $this->getWikiPage();
+ $parentRevision = $this->getParentRevision();
+ foreach ( $this->getRemovedSlotRoles() as $role ) {
+ // HACK: we should get the content model of the removed slot from a SlotRoleHandler!
+ // For now, find the slot in the parent revision - if the slot was removed, it should
+ // always exist in the parent revision.
+ $parentSlot = $parentRevision->getSlot( $role, RevisionRecord::RAW );
+ $content = $parentSlot->getContent();
+ $handler = $content->getContentHandler();
+
+ $updates = $handler->getDeletionUpdates(
+ $this->getTitle(),
+ $role
+ );
+ $allUpdates = array_merge( $allUpdates, $updates );
+
+ // TODO: remove B/C hack in 1.32!
+ $legacyUpdates = $content->getDeletionUpdates( $wikiPage );
+
+ // HACK: filter out redundant and incomplete LinksDeletionUpdate
+ $legacyUpdates = array_filter( $legacyUpdates, function ( $update ) {
+ return !( $update instanceof LinksDeletionUpdate );
+ } );
+
+ $allUpdates = array_merge( $allUpdates, $legacyUpdates );
+ }
+
+ // TODO: hard deprecate SecondaryDataUpdates in favor of RevisionDataUpdates in 1.33!
+ Hooks::run(
+ 'RevisionDataUpdates',
+ [ $this->getTitle(), $renderedRevision, &$allUpdates ]
+ );
+
+ return $allUpdates;
}
/**
WikiPage::onArticleEdit( $title, $legacyRevision, $this->getTouchedSlotRoles() );
}
- $oldRevision = $this->getOldRevision();
+ $oldRevision = $this->getParentRevision();
$oldLegacyRevision = $oldRevision ? new Revision( $oldRevision ) : null;
// TODO: In the wiring, register a listener for this on the new PageEventEmitter
}
foreach ( $updates as $update ) {
- $update->setCause( $causeAction, $causeAgent );
+ if ( $update instanceof DataUpdate ) {
+ $update->setCause( $causeAction, $causeAgent );
+ }
if ( $update instanceof LinksUpdate ) {
$update->setRevision( $legacyRevision );
$update->setTriggeringUser( $triggeringUser );
use Wikimedia\Assert\Assert;
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
+ use MediaWiki\Revision\SlotRenderingProvider;
use MediaWiki\Search\ParserOutputSearchDataExtractor;
/**
* @return ParserOutput
*/
public function getParserOutputForIndexing( WikiPage $page, ParserCache $cache = null ) {
+ // TODO: MCR: ContentHandler should be called per slot, not for the whole page.
+ // See T190066.
$parserOptions = $page->makeParserOptions( 'canonical' );
- $revId = $page->getRevision()->getId();
if ( $cache ) {
$parserOutput = $cache->get( $page, $parserOptions );
}
+
if ( empty( $parserOutput ) ) {
+ $renderer = MediaWikiServices::getInstance()->getRevisionRenderer();
$parserOutput =
- $page->getContent()->getParserOutput( $page->getTitle(), $revId, $parserOptions );
+ $renderer->getRenderedRevision(
+ $page->getRevision()->getRevisionRecord(),
+ $parserOptions
+ )->getRevisionParserOutput();
if ( $cache ) {
$cache->save( $parserOutput, $page, $parserOptions );
}
return $parserOutput;
}
+ /**
+ * Returns a list of DeferrableUpdate objects for recording information about the
+ * given Content in some secondary data store.
+ *
+ * Application logic should not call this method directly. Instead, it should call
+ * DerivedPageDataUpdater::getSecondaryDataUpdates().
+ *
+ * @note Implementations must not return a LinksUpdate instance. Instead, a LinksUpdate
+ * is created by the calling code in DerivedPageDataUpdater, on the combined ParserOutput
+ * of all slots, not for each slot individually. This is in contrast to the old
+ * getSecondaryDataUpdates method defined by AbstractContent, which returned a LinksUpdate.
+ *
+ * @note Implementations should not call $content->getParserOutput, they should call
+ * $slotOutput->getSlotRendering( $role, false ) instead if they need to access a ParserOutput
+ * of $content. This allows existing ParserOutput objects to be re-used, while avoiding
+ * creating a ParserOutput when none is needed.
+ *
+ * @param Title $title The title of the page to supply the updates for
+ * @param Content $content The content to generate data updates for.
+ * @param string $role The role (slot) in which the content is being used. Which updates
+ * are performed should generally not depend on the role the content has, but the
+ * DeferrableUpdates themselves may need to know the role, to track to which slot the
+ * data refers, and to avoid overwriting data of the same kind from another slot.
+ * @param SlotRenderingProvider $slotOutput A provider that can be used to gain access to
+ * a ParserOutput of $content by calling $slotOutput->getSlotParserOutput( $role, false ).
+ * @return DeferrableUpdate[] A list of DeferrableUpdate objects for putting information
+ * about this content object somewhere. The default implementation returns an empty
+ * array.
+ * @since 1.32
+ */
+ public function getSecondaryDataUpdates(
+ Title $title,
+ Content $content,
+ $role,
+ SlotRenderingProvider $slotOutput
+ ) {
+ return [];
+ }
+
+ /**
+ * Returns a list of DeferrableUpdate objects for removing information about content
+ * in some secondary data store. This is used when a page is deleted, and also when
+ * a slot is removed from a page.
+ *
+ * Application logic should not call this method directly. Instead, it should call
+ * WikiPage::getSecondaryDataUpdates().
+ *
+ * @note Implementations must not return a LinksDeletionUpdate instance. Instead, a
+ * LinksDeletionUpdate is created by the calling code in WikiPage.
+ * This is in contrast to the old getDeletionUpdates method defined by AbstractContent,
+ * which returned a LinksUpdate.
+ *
+ * @note Implementations should not rely on the page's current content, but rather the current
+ * state of the secondary data store.
+ *
+ * @param Title $title The title of the page to supply the updates for
+ * @param string $role The role (slot) in which the content is being used. Which updates
+ * are performed should generally not depend on the role the content has, but the
+ * DeferrableUpdates themselves may need to know the role, to track to which slot the
+ * data refers, and to avoid overwriting data of the same kind from another slot.
+ *
+ * @return DeferrableUpdate[] A list of DeferrableUpdate objects for putting information
+ * about this content object somewhere. The default implementation returns an empty
+ * array.
+ *
+ * @since 1.32
+ */
+ public function getDeletionUpdates( Title $title, $role ) {
+ return [];
+ }
+
}
* @file
*/
use MediaWiki\MediaWikiServices;
+use MediaWiki\Storage\MutableRevisionRecord;
+use MediaWiki\Storage\RevisionRecord;
/**
* Class for viewing MediaWiki article and history.
class Article implements Page {
/**
* @var IContextSource|null The context this Article is executed in.
- * If null, REquestContext::getMain() is used.
+ * If null, RequestContext::getMain() is used.
*/
protected $mContext;
- /** @var WikiPage The WikiPage object of this instance */
+ /** @var WikiPage|null The WikiPage object of this instance */
protected $mPage;
/**
public $mParserOptions;
/**
- * @var string|null Text of the revision we are working on
- * @todo BC cruft
- */
- public $mContent;
-
- /**
- * @var Content|null Content of the revision we are working on.
- * Initialized by fetchContentObject().
+ * @var Content|null Content of the main slot of $this->mRevision.
+ * @note This variable is read only, setting it has no effect.
+ * Extensions that wish to override the output of Article::view should use a hook.
+ * @todo MCR: Remove in 1.33
+ * @deprecated since 1.32
* @since 1.21
*/
public $mContentObject;
- /** @var bool Is the content ($mContent) already loaded? */
+ /**
+ * @var bool Is the target revision loaded? Set by fetchRevisionRecord().
+ *
+ * @deprecated since 1.32. Whether content has been loaded should not be relevant to
+ * code outside this class.
+ */
public $mContentLoaded = false;
- /** @var int|null The oldid of the article that is to be shown, 0 for the current revision */
+ /**
+ * @var int|null The oldid of the article that was requested to be shown,
+ * 0 for the current revision.
+ * @see $mRevIdFetched
+ */
public $mOldId;
/** @var Title|null Title from which we were redirected here, if any. */
/** @var string|bool URL to redirect to or false if none */
public $mRedirectUrl = false;
- /** @var int Revision ID of revision we are working on */
+ /**
+ * @var int Revision ID of revision that was loaded.
+ * @see $mOldId
+ * @deprecated since 1.32, use getRevIdFetched() instead.
+ */
public $mRevIdFetched = 0;
/**
- * @var Revision|null Revision we are working on. Initialized by getOldIDFromRequest()
- * or fetchContentObject().
+ * @var Status|null represents the outcome of fetchRevisionRecord().
+ * $fetchResult->value is the RevisionRecord object, if the operation was successful.
+ *
+ * The information in $fetchResult is duplicated by the following deprecated public fields:
+ * $mRevIdFetched, $mContentLoaded. $mRevision (and $mContentObject) also typically duplicate
+ * information of the loaded revision, but may be overwritten by extensions or due to errors.
+ */
+ private $fetchResult = null;
+
+ /**
+ * @var Revision|null Revision to be shown. Initialized by getOldIDFromRequest()
+ * or fetchContentObject(). Normally loaded from the database, but may be replaced
+ * by an extension, or be a fake representing an error message or some such.
+ * While the output of Article::view is typically based on this revision,
+ * it may be overwritten by error messages or replaced by extensions.
*/
public $mRevision = null;
/**
* @var ParserOutput|null|false The ParserOutput generated for viewing the page,
* initialized by view(). If no ParserOutput could be generated, this is set to false.
+ * @deprecated since 1.32
*/
- public $mParserOutput;
+ public $mParserOutput = null;
/**
* @var bool Whether render() was called. With the way subclasses work
*/
public static function newFromTitle( $title, IContextSource $context ) {
if ( NS_MEDIA == $title->getNamespace() ) {
- // FIXME: where should this go?
+ // XXX: This should not be here, but where should it go?
$title = Title::makeTitle( NS_FILE, $title->getDBkey() );
}
$this->mRedirectedFrom = null; # Title object if set
$this->mRevIdFetched = 0;
$this->mRedirectUrl = false;
+ $this->mRevision = null;
+ $this->mContentObject = null;
+ $this->fetchResult = null;
+
+ // TODO hard-deprecate direct access to public fields
$this->mPage->clear();
}
* This function has side effects! Do not use this function if you
* only want the real revision text if any.
*
- * @return Content Return the content of this revision
+ * @deprecated since 1.32, use getRevisionFetched() or fetchRevisionRecord() instead.
+ *
+ * @return Content
*
* @since 1.21
*/
protected function getContentObject() {
if ( $this->mPage->getId() === 0 ) {
- # If this is a MediaWiki:x message, then load the messages
- # and return the message value for x.
- if ( $this->getTitle()->getNamespace() == NS_MEDIAWIKI ) {
- $text = $this->getTitle()->getDefaultMessageText();
- if ( $text === false ) {
- $text = '';
- }
-
- $content = ContentHandler::makeContent( $text, $this->getTitle() );
- } else {
- $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
- $content = new MessageContent( $message, null, 'parsemag' );
- }
+ $content = $this->getSubstituteContent();
} else {
$this->fetchContentObject();
$content = $this->mContentObject;
}
/**
- * @return int The oldid of the article that is to be shown, 0 for the current revision
+ * Returns Content object to use when the page does not exist.
+ *
+ * @return Content
+ */
+ private function getSubstituteContent() {
+ # If this is a MediaWiki:x message, then load the messages
+ # and return the message value for x.
+ if ( $this->getTitle()->getNamespace() == NS_MEDIAWIKI ) {
+ $text = $this->getTitle()->getDefaultMessageText();
+ if ( $text === false ) {
+ $text = '';
+ }
+
+ $content = ContentHandler::makeContent( $text, $this->getTitle() );
+ } else {
+ $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
+ $content = new MessageContent( $message, null, 'parsemag' );
+ }
+
+ return $content;
+ }
+
+ /**
+ * Returns ParserOutput to use when a page does not exist. In some cases, we still want to show
+ * "virtual" content, e.g. in the MediaWiki namespace, or in the File namespace for non-local
+ * files.
+ *
+ * @param ParserOptions $options
+ *
+ * @return ParserOutput
+ */
+ protected function getEmptyPageParserOutput( ParserOptions $options ) {
+ $content = $this->getSubstituteContent();
+
+ return $content->getParserOutput( $this->getTitle(), 0, $options );
+ }
+
+ /**
+ * @see getOldIDFromRequest()
+ * @see getRevIdFetched()
+ *
+ * @return int The oldid of the article that is was requested in the constructor or via the
+ * context's WebRequest.
*/
public function getOldID() {
if ( is_null( $this->mOldId ) ) {
if ( $this->mRevision !== null ) {
// Revision title doesn't match the page title given?
if ( $this->mPage->getId() != $this->mRevision->getPage() ) {
- $function = get_class( $this->mPage ). '::newFromID';
+ $function = get_class( $this->mPage ) . '::newFromID';
$this->mPage = $function( $this->mRevision->getPage() );
}
}
}
}
+ $this->mRevIdFetched = $this->mRevision ? $this->mRevision->getId() : 0;
+
return $oldid;
}
* Get text content object
* Does *NOT* follow redirects.
* @todo When is this null?
+ * @deprecated since 1.32, use fetchRevisionRecord() instead.
*
* @note Code that wants to retrieve page content from the database should
* use WikiPage::getContent().
* @since 1.21
*/
protected function fetchContentObject() {
- if ( $this->mContentLoaded ) {
- return $this->mContentObject;
+ if ( !$this->mContentLoaded ) {
+ $this->fetchRevisionRecord();
+ }
+
+ return $this->mContentObject;
+ }
+
+ /**
+ * Fetches the revision to work on.
+ * The revision is typically loaded from the database, but may also be a fake representing
+ * an error message or content supplied by an extension. Refer to $this->fetchResult for
+ * the revision actually loaded from the database, and any errors encountered while doing
+ * that.
+ *
+ * @return RevisionRecord|null
+ */
+ protected function fetchRevisionRecord() {
+ if ( $this->fetchResult ) {
+ return $this->mRevision ? $this->mRevision->getRevisionRecord() : null;
}
$this->mContentLoaded = true;
- $this->mContent = null;
+ $this->mContentObject = null;
$oldid = $this->getOldID();
- # Pre-fill content with error message so that if something
- # fails we'll have something telling us what we intended.
- // XXX: this isn't page content but a UI message. horrible.
- $this->mContentObject = new MessageContent( 'missing-revision', [ $oldid ] );
+ // $this->mRevision might already be fetched by getOldIDFromRequest()
+ if ( !$this->mRevision ) {
+ if ( !$oldid ) {
+ $this->mRevision = $this->mPage->getRevision();
+
+ if ( !$this->mRevision ) {
+ wfDebug( __METHOD__ . " failed to find page data for title " .
+ $this->getTitle()->getPrefixedText() . "\n" );
- if ( $oldid ) {
- # $this->mRevision might already be fetched by getOldIDFromRequest()
- if ( !$this->mRevision ) {
+ // Just for sanity, output for this case is done by showMissingArticle().
+ $this->fetchResult = Status::newFatal( 'noarticletext' );
+ $this->applyContentOverride( $this->makeFetchErrorContent() );
+ return null;
+ }
+ } else {
$this->mRevision = Revision::newFromId( $oldid );
+
if ( !$this->mRevision ) {
- wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" );
- return false;
+ wfDebug( __METHOD__ . " failed to load revision, rev_id $oldid\n" );
+
+ $this->fetchResult = Status::newFatal( 'missing-revision', $oldid );
+ $this->applyContentOverride( $this->makeFetchErrorContent() );
+ return null;
}
}
- } else {
- $oldid = $this->mPage->getLatest();
- if ( !$oldid ) {
- wfDebug( __METHOD__ . " failed to find page data for title " .
- $this->getTitle()->getPrefixedText() . "\n" );
- return false;
- }
+ }
+
+ $this->mRevIdFetched = $this->mRevision->getId();
+ $this->fetchResult = Status::newGood( $this->mRevision );
+
+ if ( !$this->mRevision->userCan( Revision::DELETED_TEXT, $this->getContext()->getUser() ) ) {
+ wfDebug( __METHOD__ . " failed to retrieve content of revision " .
+ $this->mRevision->getId() . "\n" );
+
+ // Just for sanity, output for this case is done by showDeletedRevisionHeader().
+ $this->fetchResult = Status::newFatal( 'rev-deleted-text-permission' );
+ $this->applyContentOverride( $this->makeFetchErrorContent() );
+ return null;
+ }
+
+ if ( Hooks::isRegistered( 'ArticleAfterFetchContentObject' ) ) {
+ $contentObject = $this->mRevision->getContent(
+ Revision::FOR_THIS_USER,
+ $this->getContext()->getUser()
+ );
- # Update error message with correct oldid
- $this->mContentObject = new MessageContent( 'missing-revision', [ $oldid ] );
+ $hookContentObject = $contentObject;
- $this->mRevision = $this->mPage->getRevision();
+ // Avoid PHP 7.1 warning of passing $this by reference
+ $articlePage = $this;
+
+ Hooks::run(
+ 'ArticleAfterFetchContentObject',
+ [ &$articlePage, &$hookContentObject ],
+ '1.32'
+ );
- if ( !$this->mRevision ) {
- wfDebug( __METHOD__ . " failed to retrieve current page, rev_id $oldid\n" );
- return false;
+ if ( $hookContentObject !== $contentObject ) {
+ // A hook handler is trying to override the content
+ $this->applyContentOverride( $hookContentObject );
}
}
- // @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks.
- // We should instead work with the Revision object when we need it...
- // Loads if user is allowed
- $content = $this->mRevision->getContent(
+ // For B/C only
+ $this->mContentObject = $this->mRevision->getContent(
Revision::FOR_THIS_USER,
$this->getContext()->getUser()
);
- if ( !$content ) {
- wfDebug( __METHOD__ . " failed to retrieve content of revision " .
- $this->mRevision->getId() . "\n" );
- return false;
+ return $this->mRevision->getRevisionRecord();
+ }
+
+ /**
+ * Returns a Content object representing any error in $this->fetchContent, or null
+ * if there is no such error.
+ *
+ * @return Content|null
+ */
+ private function makeFetchErrorContent() {
+ if ( !$this->fetchResult || $this->fetchResult->isOK() ) {
+ return null;
}
- $this->mContentObject = $content;
- $this->mRevIdFetched = $this->mRevision->getId();
+ return new MessageContent( $this->fetchResult->getMessage() );
+ }
- // Avoid PHP 7.1 warning of passing $this by reference
- $articlePage = $this;
+ /**
+ * Applies a content override by constructing a fake Revision object and assigning
+ * it to mRevision. The fake revision will not have a user, timestamp or summary set.
+ *
+ * This mechanism exists mainly to accommodate extensions that use the
+ * ArticleAfterFetchContentObject. Once that hook has been removed, there should no longer
+ * be a need for a fake revision object. fetchRevisionRecord() presently also uses this mechanism
+ * to report errors, but that could be changed to use $this->fetchResult instead.
+ *
+ * @param Content $override Content to be used instead of the actual page content,
+ * coming from an extension or representing an error message.
+ */
+ private function applyContentOverride( Content $override ) {
+ // Construct a fake revision
+ $rev = new MutableRevisionRecord( $this->getTitle() );
+ $rev->setContent( 'main', $override );
- Hooks::run(
- 'ArticleAfterFetchContentObject',
- [ &$articlePage, &$this->mContentObject ]
- );
+ $this->mRevision = new Revision( $rev );
- return $this->mContentObject;
+ // For B/C only
+ $this->mContentObject = $override;
}
/**
/**
* Get the fetched Revision object depending on request parameters or null
- * on failure.
+ * on failure. The revision returned may be a fake representing an error message or
+ * wrapping content supplied by an extension. Refer to $this->fetchResult for the
+ * revision actually loaded from the database.
*
* @since 1.19
* @return Revision|null
*/
public function getRevisionFetched() {
- $this->fetchContentObject();
+ $this->fetchRevisionRecord();
- return $this->mRevision;
+ if ( $this->fetchResult->isOK() ) {
+ return $this->mRevision;
+ }
}
/**
* Use this to fetch the rev ID used on page views
*
+ * Before fetchRevisionRecord was called, this returns the page's latest revision,
+ * regardless of what getOldID() returns.
+ *
* @return int Revision ID of last article revision
*/
public function getRevIdFetched() {
- if ( $this->mRevIdFetched ) {
- return $this->mRevIdFetched;
+ if ( $this->fetchResult && $this->fetchResult->isOK() ) {
+ return $this->fetchResult->value->getId();
} else {
return $this->mPage->getLatest();
}
}
break;
case 3:
- # This will set $this->mRevision if needed
- $this->fetchContentObject();
-
# Are we looking at an old revision
- if ( $oldid && $this->mRevision ) {
+ $rev = $this->fetchRevisionRecord();
+ if ( $oldid && $this->fetchResult->isOK() ) {
$this->setOldSubtitle( $oldid );
if ( !$this->showDeletedRevisionHeader() ) {
"<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
'clearyourcache'
);
+ } elseif ( !Hooks::run( 'ArticleRevisionViewCustom', [
+ $rev,
+ $this->getTitle(),
+ $oldid,
+ $outputPage,
+ ] )
+ ) {
+ // NOTE: sync with hooks called in DifferenceEngine::renderNewRevision()
+ // Allow extensions do their own custom view for certain pages
+ $outputDone = true;
} elseif ( !Hooks::run( 'ArticleContentViewCustom',
- [ $this->fetchContentObject(), $this->getTitle(), $outputPage ] )
+ [ $this->fetchContentObject(), $this->getTitle(), $outputPage ], '1.32' )
) {
- # Allow extensions do their own custom view for certain pages
+ // NOTE: sync with hooks called in DifferenceEngine::renderNewRevision()
+ // Allow extensions do their own custom view for certain pages
$outputDone = true;
}
break;
# Run the parse, protected by a pool counter
wfDebug( __METHOD__ . ": doing uncached parse\n" );
- $content = $this->getContentObject();
- $poolArticleView = new PoolWorkArticleView( $this->getPage(), $parserOptions,
- $this->getRevIdFetched(), $useParserCache, $content );
+ $rev = $this->fetchRevisionRecord();
+ $error = null;
- if ( !$poolArticleView->execute() ) {
+ if ( $rev ) {
+ $poolArticleView = new PoolWorkArticleView(
+ $this->getPage(),
+ $parserOptions,
+ $this->getRevIdFetched(),
+ $useParserCache,
+ $rev
+ );
+ $ok = $poolArticleView->execute();
$error = $poolArticleView->getError();
+ $this->mParserOutput = $poolArticleView->getParserOutput() ?: null;
+
+ # Don't cache a dirty ParserOutput object
+ if ( $poolArticleView->getIsDirty() ) {
+ $outputPage->setCdnMaxage( 0 );
+ $outputPage->addHTML( "<!-- parser cache is expired, " .
+ "sending anyway due to pool overload-->\n" );
+ }
+ } else {
+ $ok = false;
+ }
+
+ if ( !$ok ) {
if ( $error ) {
$outputPage->clearHTML(); // for release() errors
$outputPage->enableClientCache( false );
return;
}
- $this->mParserOutput = $poolArticleView->getParserOutput();
- $outputPage->addParserOutput( $this->mParserOutput, $poOptions );
- if ( $content->getRedirectTarget() ) {
- $outputPage->addSubtitle( "<span id=\"redirectsub\">" .
- $this->getContext()->msg( 'redirectpagesub' )->parse() . "</span>" );
+ if ( $this->mParserOutput ) {
+ $outputPage->addParserOutput( $this->mParserOutput, $poOptions );
}
- # Don't cache a dirty ParserOutput object
- if ( $poolArticleView->getIsDirty() ) {
- $outputPage->setCdnMaxage( 0 );
- $outputPage->addHTML( "<!-- parser cache is expired, " .
- "sending anyway due to pool overload-->\n" );
+ if ( $rev && $this->getRevisionRedirectTarget( $rev ) ) {
+ $outputPage->addSubtitle( "<span id=\"redirectsub\">" .
+ $this->getContext()->msg( 'redirectpagesub' )->parse() . "</span>" );
}
$outputDone = true;
}
}
- # Get the ParserOutput actually *displayed* here.
- # Note that $this->mParserOutput is the *current*/oldid version output.
+ // Get the ParserOutput actually *displayed* here.
+ // Note that $this->mParserOutput is the *current*/oldid version output.
+ // Note that the ArticleViewHeader hook is allowed to set $outputDone to a
+ // ParserOutput instance.
$pOutput = ( $outputDone instanceof ParserOutput )
+ // phpcs:ignore MediaWiki.Usage.NestedInlineTernary.UnparenthesizedTernary -- FIXME T203805
? $outputDone // object fetched by hook
: $this->mParserOutput ?: null; // ParserOutput or null, avoid false
$outputPage->adaptCdnTTL( $this->mPage->getTimestamp(), IExpiringStore::TTL_DAY );
# Check for any __NOINDEX__ tags on the page using $pOutput
- $policy = $this->getRobotPolicy( 'view', $pOutput );
+ $policy = $this->getRobotPolicy( 'view', $pOutput ?: null );
$outputPage->setIndexPolicy( $policy['index'] );
- $outputPage->setFollowPolicy( $policy['follow'] );
+ $outputPage->setFollowPolicy( $policy['follow'] ); // FIXME: test this
$this->showViewFooter();
- $this->mPage->doViewUpdates( $user, $oldid );
+ $this->mPage->doViewUpdates( $user, $oldid ); // FIXME: test this
# Load the postEdit module if the user just saved this revision
# See also EditPage::setPostEditCookie
# Clear the cookie. This also prevents caching of the response.
$request->response()->clearCookie( $cookieKey );
$outputPage->addJsConfigVars( 'wgPostEdit', $postEdit );
- $outputPage->addModules( 'mediawiki.action.view.postEdit' );
+ $outputPage->addModules( 'mediawiki.action.view.postEdit' ); // FIXME: test this
}
}
+ /**
+ * @param RevisionRecord $revision
+ * @return null|Title
+ */
+ private function getRevisionRedirectTarget( RevisionRecord $revision ) {
+ // TODO: find a *good* place for the code that determines the redirect target for
+ // a given revision!
+ // NOTE: Use main slot content. Compare code in DerivedPageDataUpdater::revisionIsRedirect.
+ $content = $revision->getContent( 'main' );
+ return $content ? $content->getRedirectTarget() : null;
+ }
+
/**
* Adjust title for pages with displaytitle, -{T|}- or language conversion
* @param ParserOutput $pOutput
*/
public function adjustDisplayTitle( ParserOutput $pOutput ) {
+ $out = $this->getContext()->getOutput();
+
# Adjust the title if it was set by displaytitle, -{T|}- or language conversion
$titleText = $pOutput->getTitleText();
if ( strval( $titleText ) !== '' ) {
- $this->getContext()->getOutput()->setPageTitle( $titleText );
+ $out->setPageTitle( $titleText );
+ $out->setDisplayTitle( $titleText );
}
}
# Show error message
$oldid = $this->getOldID();
if ( !$oldid && $title->getNamespace() === NS_MEDIAWIKI && $title->hasSourceText() ) {
- $outputPage->addParserOutput( $this->getContentObject()->getParserOutput( $title ) );
+ // use fake Content object for system message
+ $parserOptions = ParserOptions::newCanonical( 'canonical' );
+ $outputPage->addParserOutput( $this->getEmptyPageParserOutput( $parserOptions ) );
} else {
if ( $oldid ) {
$text = wfMessage( 'missing-revision', $oldid )->plain();
__METHOD__
);
- // @todo FIXME: i18n issue/patchwork message
+ // @todo i18n issue/patchwork message
$context->getOutput()->addHTML(
'<strong class="mw-delete-warning-revisions">' .
$context->msg( 'historywarning' )->numParams( $revisions )->parse() .
/**
* Output deletion confirmation dialog
- * @todo FIXME: Move to another file?
+ * @todo Move to another file?
* @param string $reason Prefilled reason
*/
public function confirmDelete( $reason ) {
* Call to WikiPage function for backwards compatibility.
* @see WikiPage::doDeleteUpdates
*/
- public function doDeleteUpdates( $id, Content $content = null ) {
- return $this->mPage->doDeleteUpdates( $id, $content );
+ public function doDeleteUpdates(
+ $id,
+ Content $content = null,
+ $revision = null,
+ User $user = null
+ ) {
+ $this->mPage->doDeleteUpdates( $id, $content, $revision, $user );
}
/**