2012-02-01 22:07:47 +00:00
|
|
|
<?php
|
2018-08-05 17:58:51 +00:00
|
|
|
|
2012-12-20 19:44:47 +00:00
|
|
|
/**
|
|
|
|
|
* Base class for content handling.
|
|
|
|
|
*
|
|
|
|
|
* 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
|
|
|
|
|
*
|
|
|
|
|
* @since 1.21
|
|
|
|
|
*
|
|
|
|
|
* @file
|
|
|
|
|
* @ingroup Content
|
|
|
|
|
*
|
|
|
|
|
* @author Daniel Kinzler
|
|
|
|
|
*/
|
2018-07-17 19:23:25 +00:00
|
|
|
|
2019-09-12 19:40:57 +00:00
|
|
|
use MediaWiki\Revision\RevisionRecord;
|
2018-07-17 19:23:25 +00:00
|
|
|
use Wikimedia\Assert\Assert;
|
|
|
|
|
use MediaWiki\Logger\LoggerFactory;
|
|
|
|
|
use MediaWiki\MediaWikiServices;
|
2018-03-09 22:05:47 +00:00
|
|
|
use MediaWiki\Revision\SlotRenderingProvider;
|
2018-07-17 19:23:25 +00:00
|
|
|
use MediaWiki\Search\ParserOutputSearchDataExtractor;
|
|
|
|
|
|
2012-02-01 22:07:47 +00:00
|
|
|
/**
|
2012-06-13 07:11:43 +00:00
|
|
|
* A content handler knows how do deal with a specific type of content on a wiki
|
|
|
|
|
* page. Content is stored in the database in a serialized form (using a
|
|
|
|
|
* serialization format a.k.a. MIME type) and is unserialized into its native
|
|
|
|
|
* PHP representation (the content model), which is wrapped in an instance of
|
|
|
|
|
* the appropriate subclass of Content.
|
2012-04-16 10:35:38 +00:00
|
|
|
*
|
2012-06-13 07:11:43 +00:00
|
|
|
* ContentHandler instances are stateless singletons that serve, among other
|
|
|
|
|
* things, as a factory for Content objects. Generally, there is one subclass
|
|
|
|
|
* of ContentHandler and one subclass of Content for every type of content model.
|
2012-04-16 10:35:38 +00:00
|
|
|
*
|
2012-06-13 07:11:43 +00:00
|
|
|
* Some content types have a flat model, that is, their native representation
|
|
|
|
|
* is the same as their serialized form. Examples would be JavaScript and CSS
|
|
|
|
|
* code. As of now, this also applies to wikitext (MediaWiki's default content
|
|
|
|
|
* type), but wikitext content may be represented by a DOM or AST structure in
|
|
|
|
|
* the future.
|
2012-04-26 11:24:13 +00:00
|
|
|
*
|
2012-10-16 18:04:32 +00:00
|
|
|
* @ingroup Content
|
2012-02-01 22:07:47 +00:00
|
|
|
*/
|
|
|
|
|
abstract class ContentHandler {
|
2012-04-25 16:24:33 +00:00
|
|
|
/**
|
2012-06-13 07:11:43 +00:00
|
|
|
* Convenience function for getting flat text from a Content object. This
|
|
|
|
|
* should only be used in the context of backwards compatibility with code
|
|
|
|
|
* that is not yet able to handle Content objects!
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
|
|
|
|
* If $content is null, this method returns the empty string.
|
|
|
|
|
*
|
2012-06-13 07:11:43 +00:00
|
|
|
* If $content is an instance of TextContent, this method returns the flat
|
2019-02-07 00:14:23 +00:00
|
|
|
* text as returned by $content->getText().
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
2012-06-13 07:11:43 +00:00
|
|
|
* If $content is not a TextContent object, the behavior of this method
|
|
|
|
|
* depends on the global $wgContentHandlerTextFallback:
|
|
|
|
|
* - If $wgContentHandlerTextFallback is 'fail' and $content is not a
|
|
|
|
|
* TextContent object, an MWException is thrown.
|
|
|
|
|
* - If $wgContentHandlerTextFallback is 'serialize' and $content is not a
|
|
|
|
|
* TextContent object, $content->serialize() is called to get a string
|
|
|
|
|
* form of the content.
|
|
|
|
|
* - If $wgContentHandlerTextFallback is 'ignore' and $content is not a
|
|
|
|
|
* TextContent object, this method returns null.
|
2013-03-04 08:44:38 +00:00
|
|
|
* - otherwise, the behavior is undefined.
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
2012-10-05 13:03:24 +00:00
|
|
|
* @since 1.21
|
2012-05-10 19:22:11 +00:00
|
|
|
*
|
2018-06-26 21:14:43 +00:00
|
|
|
* @param Content|null $content
|
2014-03-03 17:08:05 +00:00
|
|
|
*
|
|
|
|
|
* @throws MWException If the content is not an instance of TextContent and
|
|
|
|
|
* wgContentHandlerTextFallback was set to 'fail'.
|
|
|
|
|
* @return string|null Textual form of the content, if available.
|
2012-04-25 16:24:33 +00:00
|
|
|
*/
|
|
|
|
|
public static function getContentText( Content $content = null ) {
|
|
|
|
|
global $wgContentHandlerTextFallback;
|
|
|
|
|
|
|
|
|
|
if ( is_null( $content ) ) {
|
2012-04-10 17:47:46 +00:00
|
|
|
return '';
|
|
|
|
|
}
|
2012-03-05 15:53:25 +00:00
|
|
|
|
2012-04-25 16:24:33 +00:00
|
|
|
if ( $content instanceof TextContent ) {
|
2018-11-08 15:19:23 +00:00
|
|
|
return $content->getText();
|
2012-04-25 16:24:33 +00:00
|
|
|
}
|
2012-03-05 15:53:25 +00:00
|
|
|
|
2012-10-15 15:27:34 +00:00
|
|
|
wfDebugLog( 'ContentHandler', 'Accessing ' . $content->getModel() . ' content as text!' );
|
|
|
|
|
|
2012-04-25 16:24:33 +00:00
|
|
|
if ( $wgContentHandlerTextFallback == 'fail' ) {
|
2012-06-13 07:11:43 +00:00
|
|
|
throw new MWException(
|
|
|
|
|
"Attempt to get text from Content with model " .
|
|
|
|
|
$content->getModel()
|
|
|
|
|
);
|
2012-04-10 17:47:46 +00:00
|
|
|
}
|
|
|
|
|
|
2012-04-25 16:24:33 +00:00
|
|
|
if ( $wgContentHandlerTextFallback == 'serialize' ) {
|
2012-04-10 17:47:46 +00:00
|
|
|
return $content->serialize();
|
|
|
|
|
}
|
2012-03-09 17:23:05 +00:00
|
|
|
|
2012-04-25 16:24:33 +00:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2012-06-13 07:11:43 +00:00
|
|
|
* Convenience function for creating a Content object from a given textual
|
|
|
|
|
* representation.
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
2012-06-13 07:11:43 +00:00
|
|
|
* $text will be deserialized into a Content object of the model specified
|
|
|
|
|
* by $modelId (or, if that is not given, $title->getContentModel()) using
|
|
|
|
|
* the given format.
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
2012-10-05 13:03:24 +00:00
|
|
|
* @since 1.21
|
2012-05-10 19:22:11 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @param string $text The textual representation, will be
|
2012-06-13 07:11:43 +00:00
|
|
|
* unserialized to create the Content object
|
2018-06-26 21:14:43 +00:00
|
|
|
* @param Title|null $title The title of the page this text belongs to.
|
2012-06-13 07:11:43 +00:00
|
|
|
* Required if $modelId is not provided.
|
2018-06-26 21:14:43 +00:00
|
|
|
* @param string|null $modelId The model to deserialize to. If not provided,
|
2012-06-13 07:11:43 +00:00
|
|
|
* $title->getContentModel() is used.
|
2018-06-26 21:14:43 +00:00
|
|
|
* @param string|null $format The format to use for deserialization. If not
|
2012-06-13 07:11:43 +00:00
|
|
|
* given, the model's default format is used.
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @throws MWException If model ID or format is not supported or if the text can not be
|
|
|
|
|
* unserialized using the format.
|
|
|
|
|
* @return Content A Content object representing the text.
|
2012-04-25 16:24:33 +00:00
|
|
|
*/
|
2012-06-13 07:11:43 +00:00
|
|
|
public static function makeContent( $text, Title $title = null,
|
2014-03-03 17:08:05 +00:00
|
|
|
$modelId = null, $format = null ) {
|
2012-05-13 22:02:29 +00:00
|
|
|
if ( is_null( $modelId ) ) {
|
2012-06-11 10:35:46 +00:00
|
|
|
if ( is_null( $title ) ) {
|
|
|
|
|
throw new MWException( "Must provide a Title object or a content model ID." );
|
|
|
|
|
}
|
|
|
|
|
|
2012-05-13 22:02:29 +00:00
|
|
|
$modelId = $title->getContentModel();
|
2012-04-25 16:24:33 +00:00
|
|
|
}
|
|
|
|
|
|
2017-07-23 01:24:09 +00:00
|
|
|
$handler = self::getForModelID( $modelId );
|
2013-11-19 21:26:16 +00:00
|
|
|
|
2012-04-25 16:24:33 +00:00
|
|
|
return $handler->unserializeContent( $text, $format );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2012-06-13 07:11:43 +00:00
|
|
|
* Returns the name of the default content model to be used for the page
|
|
|
|
|
* with the given title.
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
|
|
|
|
* Note: There should rarely be need to call this method directly.
|
2012-06-13 07:11:43 +00:00
|
|
|
* To determine the actual content model for a given page, use
|
|
|
|
|
* Title::getContentModel().
|
|
|
|
|
*
|
|
|
|
|
* Which model is to be used by default for the page is determined based
|
|
|
|
|
* on several factors:
|
|
|
|
|
* - The global setting $wgNamespaceContentModels specifies a content model
|
|
|
|
|
* per namespace.
|
2012-10-04 14:34:57 +00:00
|
|
|
* - The hook ContentHandlerDefaultModelFor may be used to override the page's default
|
2012-06-13 07:11:43 +00:00
|
|
|
* model.
|
|
|
|
|
* - Pages in NS_MEDIAWIKI and NS_USER default to the CSS or JavaScript
|
|
|
|
|
* model if they end in .js or .css, respectively.
|
|
|
|
|
* - Pages in NS_MEDIAWIKI default to the wikitext model otherwise.
|
|
|
|
|
* - The hook TitleIsCssOrJsPage may be used to force a page to use the CSS
|
2012-10-04 14:34:57 +00:00
|
|
|
* or JavaScript model. This is a compatibility feature. The ContentHandlerDefaultModelFor
|
|
|
|
|
* hook should be used instead if possible.
|
2012-06-13 07:11:43 +00:00
|
|
|
* - The hook TitleIsWikitextPage may be used to force a page to use the
|
2012-10-04 14:34:57 +00:00
|
|
|
* wikitext model. This is a compatibility feature. The ContentHandlerDefaultModelFor
|
|
|
|
|
* hook should be used instead if possible.
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
|
|
|
|
* If none of the above applies, the wikitext model is used.
|
|
|
|
|
*
|
2012-05-13 22:02:29 +00:00
|
|
|
* Note: this is used by, and may thus not use, Title::getContentModel()
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
2012-10-05 13:03:24 +00:00
|
|
|
* @since 1.21
|
2018-11-19 11:39:56 +00:00
|
|
|
* @deprecated since 1.33, use SlotRoleHandler::getDefaultModel() together with
|
|
|
|
|
* SlotRoleRegistry::getRoleHandler().
|
2012-05-10 19:22:11 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @param Title $title
|
|
|
|
|
*
|
|
|
|
|
* @return string Default model name for the page given by $title
|
2012-04-25 16:24:33 +00:00
|
|
|
*/
|
|
|
|
|
public static function getDefaultModelFor( Title $title ) {
|
2018-11-19 11:39:56 +00:00
|
|
|
$slotRoleregistry = MediaWikiServices::getInstance()->getSlotRoleRegistry();
|
|
|
|
|
$mainSlotHandler = $slotRoleregistry->getRoleHandler( 'main' );
|
|
|
|
|
return $mainSlotHandler->getDefaultModel( $title );
|
2012-04-25 16:24:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2012-06-13 07:11:43 +00:00
|
|
|
* Returns the appropriate ContentHandler singleton for the given title.
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
2012-10-05 13:03:24 +00:00
|
|
|
* @since 1.21
|
2012-05-10 19:22:11 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @param Title $title
|
|
|
|
|
*
|
2012-04-25 16:24:33 +00:00
|
|
|
* @return ContentHandler
|
|
|
|
|
*/
|
|
|
|
|
public static function getForTitle( Title $title ) {
|
2012-05-13 22:02:29 +00:00
|
|
|
$modelId = $title->getContentModel();
|
2013-11-19 21:26:16 +00:00
|
|
|
|
2017-07-23 01:24:09 +00:00
|
|
|
return self::getForModelID( $modelId );
|
2012-04-25 16:24:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2012-06-13 07:11:43 +00:00
|
|
|
* Returns the appropriate ContentHandler singleton for the given Content
|
|
|
|
|
* object.
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
2012-10-05 13:03:24 +00:00
|
|
|
* @since 1.21
|
2012-05-10 19:22:11 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @param Content $content
|
|
|
|
|
*
|
2012-04-25 16:24:33 +00:00
|
|
|
* @return ContentHandler
|
|
|
|
|
*/
|
|
|
|
|
public static function getForContent( Content $content ) {
|
2012-05-13 22:02:29 +00:00
|
|
|
$modelId = $content->getModel();
|
2013-11-19 21:26:16 +00:00
|
|
|
|
2017-07-23 01:24:09 +00:00
|
|
|
return self::getForModelID( $modelId );
|
2012-04-25 16:24:33 +00:00
|
|
|
}
|
|
|
|
|
|
2012-06-14 10:33:16 +00:00
|
|
|
/**
|
2013-11-20 05:09:49 +00:00
|
|
|
* @var array A Cache of ContentHandler instances by model id
|
2012-06-14 10:33:16 +00:00
|
|
|
*/
|
2013-11-20 05:09:49 +00:00
|
|
|
protected static $handlers;
|
2012-06-14 10:33:16 +00:00
|
|
|
|
2012-04-25 16:24:33 +00:00
|
|
|
/**
|
2012-06-13 07:11:43 +00:00
|
|
|
* Returns the ContentHandler singleton for the given model ID. Use the
|
|
|
|
|
* CONTENT_MODEL_XXX constants to identify the desired content model.
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
2012-06-13 07:11:43 +00:00
|
|
|
* ContentHandler singletons are taken from the global $wgContentHandlers
|
|
|
|
|
* array. Keys in that array are model names, the values are either
|
|
|
|
|
* ContentHandler singleton objects, or strings specifying the appropriate
|
2012-04-25 16:24:33 +00:00
|
|
|
* subclass of ContentHandler.
|
|
|
|
|
*
|
2012-06-13 07:11:43 +00:00
|
|
|
* If a class name is encountered when looking up the singleton for a given
|
|
|
|
|
* model name, the class is instantiated and the class name is replaced by
|
|
|
|
|
* the resulting singleton in $wgContentHandlers.
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
2012-06-13 07:11:43 +00:00
|
|
|
* If no ContentHandler is defined for the desired $modelId, the
|
|
|
|
|
* ContentHandler may be provided by the ContentHandlerForModelID hook.
|
|
|
|
|
* If no ContentHandler can be determined, an MWException is raised.
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
2012-10-05 13:03:24 +00:00
|
|
|
* @since 1.21
|
2012-05-10 19:22:11 +00:00
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $modelId The ID of the content model for which to get a
|
2012-06-13 07:11:43 +00:00
|
|
|
* handler. Use CONTENT_MODEL_XXX constants.
|
2014-03-03 17:08:05 +00:00
|
|
|
*
|
2015-10-10 08:51:20 +00:00
|
|
|
* @throws MWException For internal errors and problems in the configuration.
|
|
|
|
|
* @throws MWUnknownContentModelException If no handler is known for the model ID.
|
2014-03-03 17:08:05 +00:00
|
|
|
* @return ContentHandler The ContentHandler singleton for handling the model given by the ID.
|
2012-04-25 16:24:33 +00:00
|
|
|
*/
|
2012-05-13 22:02:29 +00:00
|
|
|
public static function getForModelID( $modelId ) {
|
2012-04-25 16:24:33 +00:00
|
|
|
global $wgContentHandlers;
|
|
|
|
|
|
2017-07-23 01:24:09 +00:00
|
|
|
if ( isset( self::$handlers[$modelId] ) ) {
|
|
|
|
|
return self::$handlers[$modelId];
|
2012-06-14 10:33:16 +00:00
|
|
|
}
|
|
|
|
|
|
2012-05-13 22:02:29 +00:00
|
|
|
if ( empty( $wgContentHandlers[$modelId] ) ) {
|
2012-04-25 16:24:33 +00:00
|
|
|
$handler = null;
|
2012-03-27 12:15:30 +00:00
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
Hooks::run( 'ContentHandlerForModelID', [ $modelId, &$handler ] );
|
2012-04-25 16:24:33 +00:00
|
|
|
|
2012-06-14 10:33:16 +00:00
|
|
|
if ( $handler === null ) {
|
2015-10-10 08:51:20 +00:00
|
|
|
throw new MWUnknownContentModelException( $modelId );
|
2012-04-25 16:24:33 +00:00
|
|
|
}
|
|
|
|
|
|
2012-06-14 10:33:16 +00:00
|
|
|
if ( !( $handler instanceof ContentHandler ) ) {
|
|
|
|
|
throw new MWException( "ContentHandlerForModelID must supply a ContentHandler instance" );
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2016-02-05 13:40:39 +00:00
|
|
|
$classOrCallback = $wgContentHandlers[$modelId];
|
|
|
|
|
|
|
|
|
|
if ( is_callable( $classOrCallback ) ) {
|
|
|
|
|
$handler = call_user_func( $classOrCallback, $modelId );
|
|
|
|
|
} else {
|
|
|
|
|
$handler = new $classOrCallback( $modelId );
|
|
|
|
|
}
|
2012-06-14 10:33:16 +00:00
|
|
|
|
|
|
|
|
if ( !( $handler instanceof ContentHandler ) ) {
|
2019-08-29 09:59:59 +00:00
|
|
|
throw new MWException(
|
|
|
|
|
var_export( $classOrCallback, true ) . " from \$wgContentHandlers is not " .
|
|
|
|
|
"compatible with ContentHandler"
|
|
|
|
|
);
|
2012-06-14 10:33:16 +00:00
|
|
|
}
|
2012-04-25 16:24:33 +00:00
|
|
|
}
|
|
|
|
|
|
2012-10-15 15:27:34 +00:00
|
|
|
wfDebugLog( 'ContentHandler', 'Created handler for ' . $modelId
|
2013-11-19 21:26:16 +00:00
|
|
|
. ': ' . get_class( $handler ) );
|
2012-10-15 15:27:34 +00:00
|
|
|
|
2017-07-23 01:24:09 +00:00
|
|
|
self::$handlers[$modelId] = $handler;
|
2013-11-19 21:26:16 +00:00
|
|
|
|
2017-07-23 01:24:09 +00:00
|
|
|
return self::$handlers[$modelId];
|
2012-05-13 22:02:29 +00:00
|
|
|
}
|
|
|
|
|
|
2018-03-15 22:36:47 +00:00
|
|
|
/**
|
|
|
|
|
* Clean up handlers cache.
|
|
|
|
|
*/
|
|
|
|
|
public static function cleanupHandlersCache() {
|
|
|
|
|
self::$handlers = [];
|
|
|
|
|
}
|
|
|
|
|
|
2012-06-19 12:45:25 +00:00
|
|
|
/**
|
|
|
|
|
* Returns the localized name for a given content model.
|
2012-05-13 22:02:29 +00:00
|
|
|
*
|
|
|
|
|
* Model names are localized using system messages. Message keys
|
2012-06-19 12:45:25 +00:00
|
|
|
* have the form content-model-$name, where $name is getContentModelName( $id ).
|
2012-05-13 22:02:29 +00:00
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $name The content model ID, as given by a CONTENT_MODEL_XXX
|
2012-06-13 07:11:43 +00:00
|
|
|
* constant or returned by Revision::getContentModel().
|
2015-03-29 03:36:01 +00:00
|
|
|
* @param Language|null $lang The language to parse the message in (since 1.26)
|
2012-05-13 22:02:29 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @throws MWException If the model ID isn't known.
|
2013-11-16 11:28:48 +00:00
|
|
|
* @return string The content model's localized name.
|
2012-05-13 22:02:29 +00:00
|
|
|
*/
|
2015-03-29 03:36:01 +00:00
|
|
|
public static function getLocalizedName( $name, Language $lang = null ) {
|
2013-11-16 11:37:25 +00:00
|
|
|
// Messages: content-model-wikitext, content-model-text,
|
|
|
|
|
// content-model-javascript, content-model-css
|
2012-06-19 12:45:25 +00:00
|
|
|
$key = "content-model-$name";
|
2012-05-13 22:02:29 +00:00
|
|
|
|
2012-10-05 13:15:11 +00:00
|
|
|
$msg = wfMessage( $key );
|
2015-03-29 03:36:01 +00:00
|
|
|
if ( $lang ) {
|
|
|
|
|
$msg->inLanguage( $lang );
|
|
|
|
|
}
|
2012-10-05 13:15:11 +00:00
|
|
|
|
|
|
|
|
return $msg->exists() ? $msg->plain() : $name;
|
2012-04-25 16:24:33 +00:00
|
|
|
}
|
|
|
|
|
|
2012-08-21 14:14:18 +00:00
|
|
|
public static function getContentModels() {
|
|
|
|
|
global $wgContentHandlers;
|
|
|
|
|
|
2017-01-31 05:31:30 +00:00
|
|
|
$models = array_keys( $wgContentHandlers );
|
|
|
|
|
Hooks::run( 'GetContentModels', [ &$models ] );
|
|
|
|
|
return $models;
|
2012-08-21 14:14:18 +00:00
|
|
|
}
|
|
|
|
|
|
2012-06-25 21:30:51 +00:00
|
|
|
public static function getAllContentFormats() {
|
|
|
|
|
global $wgContentHandlers;
|
|
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
$formats = [];
|
2012-06-25 21:30:51 +00:00
|
|
|
|
|
|
|
|
foreach ( $wgContentHandlers as $model => $class ) {
|
2017-07-23 01:24:09 +00:00
|
|
|
$handler = self::getForModelID( $model );
|
2012-06-25 21:30:51 +00:00
|
|
|
$formats = array_merge( $formats, $handler->getSupportedFormats() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$formats = array_unique( $formats );
|
2013-11-19 21:26:16 +00:00
|
|
|
|
2012-06-25 21:30:51 +00:00
|
|
|
return $formats;
|
|
|
|
|
}
|
|
|
|
|
|
2012-06-13 07:11:43 +00:00
|
|
|
// ------------------------------------------------------------------------
|
2012-04-25 16:24:33 +00:00
|
|
|
|
2014-03-03 17:08:05 +00:00
|
|
|
/**
|
|
|
|
|
* @var string
|
|
|
|
|
*/
|
2012-05-13 22:02:29 +00:00
|
|
|
protected $mModelID;
|
2014-03-03 17:08:05 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @var string[]
|
|
|
|
|
*/
|
2012-05-13 22:02:29 +00:00
|
|
|
protected $mSupportedFormats;
|
|
|
|
|
|
2012-04-25 16:24:33 +00:00
|
|
|
/**
|
2012-06-13 07:11:43 +00:00
|
|
|
* Constructor, initializing the ContentHandler instance with its model ID
|
|
|
|
|
* and a list of supported formats. Values for the parameters are typically
|
|
|
|
|
* provided as literals by subclass's constructors.
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $modelId (use CONTENT_MODEL_XXX constants).
|
2014-03-03 17:08:05 +00:00
|
|
|
* @param string[] $formats List for supported serialization formats
|
2012-06-13 07:11:43 +00:00
|
|
|
* (typically as MIME types)
|
2012-04-25 16:24:33 +00:00
|
|
|
*/
|
2012-05-13 22:02:29 +00:00
|
|
|
public function __construct( $modelId, $formats ) {
|
|
|
|
|
$this->mModelID = $modelId;
|
2012-04-25 16:24:33 +00:00
|
|
|
$this->mSupportedFormats = $formats;
|
2012-06-19 12:45:25 +00:00
|
|
|
}
|
2012-04-25 16:24:33 +00:00
|
|
|
|
|
|
|
|
/**
|
2012-06-13 07:11:43 +00:00
|
|
|
* Serializes a Content object of the type supported by this ContentHandler.
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
2012-10-05 13:03:24 +00:00
|
|
|
* @since 1.21
|
2012-05-10 19:22:11 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @param Content $content The Content object to serialize
|
2018-06-26 21:14:43 +00:00
|
|
|
* @param string|null $format The desired serialization format
|
2014-03-03 17:08:05 +00:00
|
|
|
*
|
2012-06-13 07:11:43 +00:00
|
|
|
* @return string Serialized form of the content
|
2012-04-25 16:24:33 +00:00
|
|
|
*/
|
2013-01-26 19:00:09 +00:00
|
|
|
abstract public function serializeContent( Content $content, $format = null );
|
2012-04-25 16:24:33 +00:00
|
|
|
|
2014-05-20 17:21:49 +00:00
|
|
|
/**
|
|
|
|
|
* Applies transformations on export (returns the blob unchanged per default).
|
|
|
|
|
* Subclasses may override this to perform transformations such as conversion
|
|
|
|
|
* of legacy formats or filtering of internal meta-data.
|
|
|
|
|
*
|
|
|
|
|
* @param string $blob The blob to be exported
|
|
|
|
|
* @param string|null $format The blob's serialization format
|
|
|
|
|
*
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
public function exportTransform( $blob, $format = null ) {
|
|
|
|
|
return $blob;
|
|
|
|
|
}
|
|
|
|
|
|
2012-04-25 16:24:33 +00:00
|
|
|
/**
|
|
|
|
|
* Unserializes a Content object of the type supported by this ContentHandler.
|
|
|
|
|
*
|
2012-10-05 13:03:24 +00:00
|
|
|
* @since 1.21
|
2012-05-10 19:22:11 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @param string $blob Serialized form of the content
|
2018-06-26 21:14:43 +00:00
|
|
|
* @param string|null $format The format used for serialization
|
2014-03-03 17:08:05 +00:00
|
|
|
*
|
|
|
|
|
* @return Content The Content object created by deserializing $blob
|
2012-04-25 16:24:33 +00:00
|
|
|
*/
|
2013-01-26 19:00:09 +00:00
|
|
|
abstract public function unserializeContent( $blob, $format = null );
|
2012-04-25 16:24:33 +00:00
|
|
|
|
2014-05-20 17:09:51 +00:00
|
|
|
/**
|
|
|
|
|
* Apply import transformation (per default, returns $blob unchanged).
|
|
|
|
|
* This gives subclasses an opportunity to transform data blobs on import.
|
|
|
|
|
*
|
2014-07-04 20:53:47 +00:00
|
|
|
* @since 1.24
|
2014-05-20 17:09:51 +00:00
|
|
|
*
|
|
|
|
|
* @param string $blob
|
|
|
|
|
* @param string|null $format
|
|
|
|
|
*
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
public function importTransform( $blob, $format = null ) {
|
|
|
|
|
return $blob;
|
|
|
|
|
}
|
|
|
|
|
|
2012-04-25 16:24:33 +00:00
|
|
|
/**
|
2012-06-13 07:11:43 +00:00
|
|
|
* Creates an empty Content object of the type supported by this
|
|
|
|
|
* ContentHandler.
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
2012-10-05 13:03:24 +00:00
|
|
|
* @since 1.21
|
2012-05-10 19:22:11 +00:00
|
|
|
*
|
2012-04-25 16:34:36 +00:00
|
|
|
* @return Content
|
2012-04-25 16:24:33 +00:00
|
|
|
*/
|
2013-01-26 19:00:09 +00:00
|
|
|
abstract public function makeEmptyContent();
|
2012-04-25 16:24:33 +00:00
|
|
|
|
2012-10-05 14:50:32 +00:00
|
|
|
/**
|
|
|
|
|
* Creates a new Content object that acts as a redirect to the given page,
|
2014-05-11 19:39:23 +00:00
|
|
|
* or null if redirects are not supported by this content model.
|
2012-10-05 14:50:32 +00:00
|
|
|
*
|
|
|
|
|
* This default implementation always returns null. Subclasses supporting redirects
|
|
|
|
|
* must override this method.
|
|
|
|
|
*
|
2012-12-13 16:26:43 +00:00
|
|
|
* Note that subclasses that override this method to return a Content object
|
|
|
|
|
* should also override supportsRedirects() to return true.
|
|
|
|
|
*
|
2012-10-05 14:50:32 +00:00
|
|
|
* @since 1.21
|
|
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @param Title $destination The page to redirect to.
|
|
|
|
|
* @param string $text Text to include in the redirect, if possible.
|
2012-10-05 14:50:32 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @return Content Always null.
|
2012-10-05 14:50:32 +00:00
|
|
|
*/
|
2013-10-03 15:09:08 +00:00
|
|
|
public function makeRedirectContent( Title $destination, $text = '' ) {
|
2012-10-05 14:50:32 +00:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2012-04-25 16:24:33 +00:00
|
|
|
/**
|
2012-06-13 07:11:43 +00:00
|
|
|
* Returns the model id that identifies the content model this
|
|
|
|
|
* ContentHandler can handle. Use with the CONTENT_MODEL_XXX constants.
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
2012-10-05 13:03:24 +00:00
|
|
|
* @since 1.21
|
2012-05-10 19:22:11 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @return string The model ID
|
2012-04-25 16:24:33 +00:00
|
|
|
*/
|
2012-05-13 22:02:29 +00:00
|
|
|
public function getModelID() {
|
|
|
|
|
return $this->mModelID;
|
2012-04-25 16:24:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2012-10-05 13:03:24 +00:00
|
|
|
* @since 1.21
|
2012-05-10 19:22:11 +00:00
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $model_id The model to check
|
2012-06-03 17:27:23 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @throws MWException If the model ID is not the ID of the content model supported by this
|
|
|
|
|
* ContentHandler.
|
2012-04-25 16:24:33 +00:00
|
|
|
*/
|
2012-05-13 22:02:29 +00:00
|
|
|
protected function checkModelID( $model_id ) {
|
|
|
|
|
if ( $model_id !== $this->mModelID ) {
|
2012-06-13 07:11:43 +00:00
|
|
|
throw new MWException( "Bad content model: " .
|
2012-06-25 21:30:51 +00:00
|
|
|
"expected {$this->mModelID} " .
|
|
|
|
|
"but got $model_id." );
|
2012-04-25 16:24:33 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2012-06-13 07:11:43 +00:00
|
|
|
* Returns a list of serialization formats supported by the
|
|
|
|
|
* serializeContent() and unserializeContent() methods of this
|
|
|
|
|
* ContentHandler.
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
2012-10-05 13:03:24 +00:00
|
|
|
* @since 1.21
|
2012-05-10 19:22:11 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @return string[] List of serialization formats as MIME type like strings
|
2012-04-25 16:24:33 +00:00
|
|
|
*/
|
|
|
|
|
public function getSupportedFormats() {
|
|
|
|
|
return $this->mSupportedFormats;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2012-06-13 07:11:43 +00:00
|
|
|
* The format used for serialization/deserialization by default by this
|
|
|
|
|
* ContentHandler.
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
2012-06-13 07:11:43 +00:00
|
|
|
* This default implementation will return the first element of the array
|
|
|
|
|
* of formats that was passed to the constructor.
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
2012-10-05 13:03:24 +00:00
|
|
|
* @since 1.21
|
2012-05-10 19:22:11 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @return string The name of the default serialization format as a MIME type
|
2012-04-25 16:24:33 +00:00
|
|
|
*/
|
|
|
|
|
public function getDefaultFormat() {
|
|
|
|
|
return $this->mSupportedFormats[0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2012-06-13 07:11:43 +00:00
|
|
|
* Returns true if $format is a serialization format supported by this
|
|
|
|
|
* ContentHandler, and false otherwise.
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
2012-06-13 07:11:43 +00:00
|
|
|
* Note that if $format is null, this method always returns true, because
|
|
|
|
|
* null means "use the default format".
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
2012-10-05 13:03:24 +00:00
|
|
|
* @since 1.21
|
2012-05-10 19:22:11 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @param string $format The serialization format to check
|
|
|
|
|
*
|
2012-04-25 16:24:33 +00:00
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function isSupportedFormat( $format ) {
|
|
|
|
|
if ( !$format ) {
|
2012-04-11 10:49:43 +00:00
|
|
|
return true; // this means "use the default"
|
|
|
|
|
}
|
2012-03-27 12:15:30 +00:00
|
|
|
|
2012-04-25 16:24:33 +00:00
|
|
|
return in_array( $format, $this->mSupportedFormats );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2014-03-03 17:08:05 +00:00
|
|
|
* Convenient for checking whether a format provided as a parameter is actually supported.
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @param string $format The serialization format to check
|
2012-06-03 17:27:23 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @throws MWException If the format is not supported by this content handler.
|
2012-04-25 16:24:33 +00:00
|
|
|
*/
|
|
|
|
|
protected function checkFormat( $format ) {
|
|
|
|
|
if ( !$this->isSupportedFormat( $format ) ) {
|
2012-06-13 07:11:43 +00:00
|
|
|
throw new MWException(
|
|
|
|
|
"Format $format is not supported for content model "
|
|
|
|
|
. $this->getModelID()
|
|
|
|
|
);
|
2012-04-25 16:24:33 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns overrides for action handlers.
|
|
|
|
|
* Classes listed here will be used instead of the default one when
|
|
|
|
|
* (and only when) $wgActions[$action] === true. This allows subclasses
|
|
|
|
|
* to override the default action handlers.
|
|
|
|
|
*
|
2012-10-05 13:03:24 +00:00
|
|
|
* @since 1.21
|
2012-05-10 19:22:11 +00:00
|
|
|
*
|
2016-05-18 13:49:26 +00:00
|
|
|
* @return array An array mapping action names (typically "view", "edit", "history" etc.) to
|
|
|
|
|
* either the full qualified class name of an Action class, a callable taking ( Page $page,
|
|
|
|
|
* IContextSource $context = null ) as parameters and returning an Action object, or an actual
|
|
|
|
|
* Action object. An empty array in this default implementation.
|
|
|
|
|
*
|
|
|
|
|
* @see Action::factory
|
2012-04-25 16:24:33 +00:00
|
|
|
*/
|
|
|
|
|
public function getActionOverrides() {
|
2016-02-17 09:09:32 +00:00
|
|
|
return [];
|
2012-04-25 16:24:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2012-06-13 07:11:43 +00:00
|
|
|
* Factory for creating an appropriate DifferenceEngine for this content model.
|
[MCR] Render multi-slot diffs
Move logic for rendering a diff between two content objects out of
DifferenceEngine, into a new SlotDiffRenderer class. Make
DifferenceEngine use multiple SlotDiffRenderers, one per slot.
This separates the class tree for changing high-level diff properties
such as the header or the revision selection method (same as before:
subclass DifferenceEngine and override ContentHandler::getDiffEngineClass
or implement GetDifferenceEngine) and the one for changing the actual
diff rendering for a given content type (subclass SlotDiffRenderer and
override ContentHandler::getSlotDiffRenderer or implement
GetSlotDiffRenderer). To keep B/C, when SlotDiffRenderer is not overridden
for a given content type but DifferenceEngine is, that DifferenceEngine
will be used instead.
The weak point of the scheme is overriding the DifferenceEngine methods
passing control to the SlotDiffRenderers (the ones calling
getDifferenceEngines), without calling the parent. These are:
showDiffStyle, getDiffBody, getDiffBodyCacheKeyParams. Extensions doing
that will probably break in unpredictable ways (most likely, only
showing the main slot diff). Nothing in gerrit does it, at least.
A new GetSlotDiffRenderer hook is added to modify rendering for content
models not owned by the extension, much like how GetDifferenceEngine
works.
Also deprecates public access to mNewRev/mOldRev and creates public
getters instead. DifferenceEngine never supported external changes to
those properties, this just acknowledges it.
Bug: T194731
Change-Id: I2f8a9dbebd2290b7feafb20e2bb2a2693e18ba11
Depends-On: I04e885a33bfce5bccc807b9bcfe1eaa577a9fd47
Depends-On: I203d8895bf436b7fee53fe4718dede8a3b1768bc
2018-07-11 09:24:07 +00:00
|
|
|
* Since 1.32, this is only used for page-level diffs; to diff two content objects,
|
|
|
|
|
* use getSlotDiffRenderer.
|
2012-06-03 17:27:23 +00:00
|
|
|
*
|
2018-08-13 16:53:59 +00:00
|
|
|
* The DifferenceEngine subclass to use is selected in getDiffEngineClass(). The
|
|
|
|
|
* GetDifferenceEngine hook will receive the DifferenceEngine object and can replace or
|
|
|
|
|
* wrap it.
|
|
|
|
|
* (Note that in older versions of MediaWiki the hook documentation instructed extensions
|
|
|
|
|
* to return false from the hook; you should not rely on always being able to decorate
|
|
|
|
|
* the DifferenceEngine instance from the hook. If the owner of the content type wants to
|
|
|
|
|
* decorare the instance, overriding this method is a safer approach.)
|
|
|
|
|
*
|
[MCR] Render multi-slot diffs
Move logic for rendering a diff between two content objects out of
DifferenceEngine, into a new SlotDiffRenderer class. Make
DifferenceEngine use multiple SlotDiffRenderers, one per slot.
This separates the class tree for changing high-level diff properties
such as the header or the revision selection method (same as before:
subclass DifferenceEngine and override ContentHandler::getDiffEngineClass
or implement GetDifferenceEngine) and the one for changing the actual
diff rendering for a given content type (subclass SlotDiffRenderer and
override ContentHandler::getSlotDiffRenderer or implement
GetSlotDiffRenderer). To keep B/C, when SlotDiffRenderer is not overridden
for a given content type but DifferenceEngine is, that DifferenceEngine
will be used instead.
The weak point of the scheme is overriding the DifferenceEngine methods
passing control to the SlotDiffRenderers (the ones calling
getDifferenceEngines), without calling the parent. These are:
showDiffStyle, getDiffBody, getDiffBodyCacheKeyParams. Extensions doing
that will probably break in unpredictable ways (most likely, only
showing the main slot diff). Nothing in gerrit does it, at least.
A new GetSlotDiffRenderer hook is added to modify rendering for content
models not owned by the extension, much like how GetDifferenceEngine
works.
Also deprecates public access to mNewRev/mOldRev and creates public
getters instead. DifferenceEngine never supported external changes to
those properties, this just acknowledges it.
Bug: T194731
Change-Id: I2f8a9dbebd2290b7feafb20e2bb2a2693e18ba11
Depends-On: I04e885a33bfce5bccc807b9bcfe1eaa577a9fd47
Depends-On: I203d8895bf436b7fee53fe4718dede8a3b1768bc
2018-07-11 09:24:07 +00:00
|
|
|
* @todo This is page-level functionality so it should not belong to ContentHandler.
|
|
|
|
|
* Move it to a better place once one exists (e.g. PageTypeHandler).
|
|
|
|
|
*
|
2012-10-05 13:03:24 +00:00
|
|
|
* @since 1.21
|
2012-05-10 19:22:11 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @param IContextSource $context Context to use, anything else will be ignored.
|
|
|
|
|
* @param int $old Revision ID we want to show and diff with.
|
|
|
|
|
* @param int|string $new Either a revision ID or one of the strings 'cur', 'prev' or 'next'.
|
|
|
|
|
* @param int $rcid FIXME: Deprecated, no longer used. Defaults to 0.
|
|
|
|
|
* @param bool $refreshCache If set, refreshes the diff cache. Defaults to false.
|
|
|
|
|
* @param bool $unhide If set, allow viewing deleted revs. Defaults to false.
|
2012-04-10 17:47:46 +00:00
|
|
|
*
|
|
|
|
|
* @return DifferenceEngine
|
2012-04-25 16:24:33 +00:00
|
|
|
*/
|
2014-03-03 17:08:05 +00:00
|
|
|
public function createDifferenceEngine( IContextSource $context, $old = 0, $new = 0,
|
2015-09-11 13:44:59 +00:00
|
|
|
$rcid = 0, // FIXME: Deprecated, no longer used
|
2017-07-01 08:32:08 +00:00
|
|
|
$refreshCache = false, $unhide = false
|
|
|
|
|
) {
|
2014-10-10 21:14:56 +00:00
|
|
|
$diffEngineClass = $this->getDiffEngineClass();
|
2018-08-13 16:53:59 +00:00
|
|
|
$differenceEngine = new $diffEngineClass( $context, $old, $new, $rcid, $refreshCache, $unhide );
|
|
|
|
|
Hooks::run( 'GetDifferenceEngine', [ $context, $old, $new, $refreshCache, $unhide,
|
|
|
|
|
&$differenceEngine ] );
|
|
|
|
|
return $differenceEngine;
|
2012-04-25 16:24:33 +00:00
|
|
|
}
|
2012-02-01 22:07:47 +00:00
|
|
|
|
[MCR] Render multi-slot diffs
Move logic for rendering a diff between two content objects out of
DifferenceEngine, into a new SlotDiffRenderer class. Make
DifferenceEngine use multiple SlotDiffRenderers, one per slot.
This separates the class tree for changing high-level diff properties
such as the header or the revision selection method (same as before:
subclass DifferenceEngine and override ContentHandler::getDiffEngineClass
or implement GetDifferenceEngine) and the one for changing the actual
diff rendering for a given content type (subclass SlotDiffRenderer and
override ContentHandler::getSlotDiffRenderer or implement
GetSlotDiffRenderer). To keep B/C, when SlotDiffRenderer is not overridden
for a given content type but DifferenceEngine is, that DifferenceEngine
will be used instead.
The weak point of the scheme is overriding the DifferenceEngine methods
passing control to the SlotDiffRenderers (the ones calling
getDifferenceEngines), without calling the parent. These are:
showDiffStyle, getDiffBody, getDiffBodyCacheKeyParams. Extensions doing
that will probably break in unpredictable ways (most likely, only
showing the main slot diff). Nothing in gerrit does it, at least.
A new GetSlotDiffRenderer hook is added to modify rendering for content
models not owned by the extension, much like how GetDifferenceEngine
works.
Also deprecates public access to mNewRev/mOldRev and creates public
getters instead. DifferenceEngine never supported external changes to
those properties, this just acknowledges it.
Bug: T194731
Change-Id: I2f8a9dbebd2290b7feafb20e2bb2a2693e18ba11
Depends-On: I04e885a33bfce5bccc807b9bcfe1eaa577a9fd47
Depends-On: I203d8895bf436b7fee53fe4718dede8a3b1768bc
2018-07-11 09:24:07 +00:00
|
|
|
/**
|
|
|
|
|
* Get an appropriate SlotDiffRenderer for this content model.
|
|
|
|
|
* @since 1.32
|
|
|
|
|
* @param IContextSource $context
|
|
|
|
|
* @return SlotDiffRenderer
|
|
|
|
|
*/
|
|
|
|
|
final public function getSlotDiffRenderer( IContextSource $context ) {
|
|
|
|
|
$slotDiffRenderer = $this->getSlotDiffRendererInternal( $context );
|
|
|
|
|
if ( get_class( $slotDiffRenderer ) === TextSlotDiffRenderer::class ) {
|
|
|
|
|
// To keep B/C, when SlotDiffRenderer is not overridden for a given content type
|
|
|
|
|
// but DifferenceEngine is, use that instead.
|
|
|
|
|
$differenceEngine = $this->createDifferenceEngine( $context );
|
|
|
|
|
if ( get_class( $differenceEngine ) !== DifferenceEngine::class ) {
|
|
|
|
|
// TODO turn this into a deprecation warning in a later release
|
2018-08-26 19:00:02 +00:00
|
|
|
LoggerFactory::getInstance( 'diff' )->info(
|
[MCR] Render multi-slot diffs
Move logic for rendering a diff between two content objects out of
DifferenceEngine, into a new SlotDiffRenderer class. Make
DifferenceEngine use multiple SlotDiffRenderers, one per slot.
This separates the class tree for changing high-level diff properties
such as the header or the revision selection method (same as before:
subclass DifferenceEngine and override ContentHandler::getDiffEngineClass
or implement GetDifferenceEngine) and the one for changing the actual
diff rendering for a given content type (subclass SlotDiffRenderer and
override ContentHandler::getSlotDiffRenderer or implement
GetSlotDiffRenderer). To keep B/C, when SlotDiffRenderer is not overridden
for a given content type but DifferenceEngine is, that DifferenceEngine
will be used instead.
The weak point of the scheme is overriding the DifferenceEngine methods
passing control to the SlotDiffRenderers (the ones calling
getDifferenceEngines), without calling the parent. These are:
showDiffStyle, getDiffBody, getDiffBodyCacheKeyParams. Extensions doing
that will probably break in unpredictable ways (most likely, only
showing the main slot diff). Nothing in gerrit does it, at least.
A new GetSlotDiffRenderer hook is added to modify rendering for content
models not owned by the extension, much like how GetDifferenceEngine
works.
Also deprecates public access to mNewRev/mOldRev and creates public
getters instead. DifferenceEngine never supported external changes to
those properties, this just acknowledges it.
Bug: T194731
Change-Id: I2f8a9dbebd2290b7feafb20e2bb2a2693e18ba11
Depends-On: I04e885a33bfce5bccc807b9bcfe1eaa577a9fd47
Depends-On: I203d8895bf436b7fee53fe4718dede8a3b1768bc
2018-07-11 09:24:07 +00:00
|
|
|
'Falling back to DifferenceEngineSlotDiffRenderer', [
|
|
|
|
|
'modelID' => $this->getModelID(),
|
|
|
|
|
'DifferenceEngine' => get_class( $differenceEngine ),
|
|
|
|
|
] );
|
|
|
|
|
$slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Hooks::run( 'GetSlotDiffRenderer', [ $this, &$slotDiffRenderer, $context ] );
|
|
|
|
|
return $slotDiffRenderer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return the SlotDiffRenderer appropriate for this content handler.
|
|
|
|
|
* @param IContextSource $context
|
|
|
|
|
* @return SlotDiffRenderer
|
|
|
|
|
*/
|
|
|
|
|
protected function getSlotDiffRendererInternal( IContextSource $context ) {
|
|
|
|
|
$contentLanguage = MediaWikiServices::getInstance()->getContentLanguage();
|
|
|
|
|
$statsdDataFactory = MediaWikiServices::getInstance()->getStatsdDataFactory();
|
|
|
|
|
$slotDiffRenderer = new TextSlotDiffRenderer();
|
|
|
|
|
$slotDiffRenderer->setStatsdDataFactory( $statsdDataFactory );
|
|
|
|
|
// XXX using the page language would be better, but it's unclear how that should be injected
|
|
|
|
|
$slotDiffRenderer->setLanguage( $contentLanguage );
|
|
|
|
|
|
|
|
|
|
$engine = DifferenceEngine::getEngine();
|
2019-11-05 15:07:06 +00:00
|
|
|
if ( $engine === 'php' ) {
|
[MCR] Render multi-slot diffs
Move logic for rendering a diff between two content objects out of
DifferenceEngine, into a new SlotDiffRenderer class. Make
DifferenceEngine use multiple SlotDiffRenderers, one per slot.
This separates the class tree for changing high-level diff properties
such as the header or the revision selection method (same as before:
subclass DifferenceEngine and override ContentHandler::getDiffEngineClass
or implement GetDifferenceEngine) and the one for changing the actual
diff rendering for a given content type (subclass SlotDiffRenderer and
override ContentHandler::getSlotDiffRenderer or implement
GetSlotDiffRenderer). To keep B/C, when SlotDiffRenderer is not overridden
for a given content type but DifferenceEngine is, that DifferenceEngine
will be used instead.
The weak point of the scheme is overriding the DifferenceEngine methods
passing control to the SlotDiffRenderers (the ones calling
getDifferenceEngines), without calling the parent. These are:
showDiffStyle, getDiffBody, getDiffBodyCacheKeyParams. Extensions doing
that will probably break in unpredictable ways (most likely, only
showing the main slot diff). Nothing in gerrit does it, at least.
A new GetSlotDiffRenderer hook is added to modify rendering for content
models not owned by the extension, much like how GetDifferenceEngine
works.
Also deprecates public access to mNewRev/mOldRev and creates public
getters instead. DifferenceEngine never supported external changes to
those properties, this just acknowledges it.
Bug: T194731
Change-Id: I2f8a9dbebd2290b7feafb20e2bb2a2693e18ba11
Depends-On: I04e885a33bfce5bccc807b9bcfe1eaa577a9fd47
Depends-On: I203d8895bf436b7fee53fe4718dede8a3b1768bc
2018-07-11 09:24:07 +00:00
|
|
|
$slotDiffRenderer->setEngine( TextSlotDiffRenderer::ENGINE_PHP );
|
|
|
|
|
} elseif ( $engine === 'wikidiff2' ) {
|
|
|
|
|
$slotDiffRenderer->setEngine( TextSlotDiffRenderer::ENGINE_WIKIDIFF2 );
|
|
|
|
|
} else {
|
|
|
|
|
$slotDiffRenderer->setEngine( TextSlotDiffRenderer::ENGINE_EXTERNAL, $engine );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $slotDiffRenderer;
|
|
|
|
|
}
|
|
|
|
|
|
2012-06-26 14:37:42 +00:00
|
|
|
/**
|
|
|
|
|
* Get the language in which the content of the given page is written.
|
|
|
|
|
*
|
2018-07-29 12:24:54 +00:00
|
|
|
* This default implementation just returns the content language (except for pages
|
2013-11-20 04:49:59 +00:00
|
|
|
* in the MediaWiki namespace)
|
2012-06-26 14:37:42 +00:00
|
|
|
*
|
2019-03-06 12:23:06 +00:00
|
|
|
* Note that the page's language is not cacheable, since it may in some
|
2013-11-20 04:49:59 +00:00
|
|
|
* cases depend on user settings.
|
2012-06-26 14:37:42 +00:00
|
|
|
*
|
|
|
|
|
* Also note that the page language may or may not depend on the actual content of the page,
|
|
|
|
|
* that is, this method may load the content in order to determine the language.
|
|
|
|
|
*
|
2012-10-05 13:03:24 +00:00
|
|
|
* @since 1.21
|
2012-06-26 14:37:42 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @param Title $title The page to determine the language for.
|
2018-06-26 21:14:43 +00:00
|
|
|
* @param Content|null $content The page's content, if you have it handy, to avoid reloading it.
|
2012-06-26 14:37:42 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @return Language The page's language
|
2012-06-26 14:37:42 +00:00
|
|
|
*/
|
|
|
|
|
public function getPageLanguage( Title $title, Content $content = null ) {
|
2018-07-29 12:24:54 +00:00
|
|
|
global $wgLang;
|
|
|
|
|
$pageLang = MediaWikiServices::getInstance()->getContentLanguage();
|
2012-09-05 16:27:32 +00:00
|
|
|
|
2019-03-06 12:23:06 +00:00
|
|
|
if ( $title->inNamespace( NS_MEDIAWIKI ) ) {
|
2012-09-05 16:27:32 +00:00
|
|
|
// Parse mediawiki messages with correct target language
|
|
|
|
|
list( /* $unused */, $lang ) = MessageCache::singleton()->figureMessage( $title->getText() );
|
2019-08-26 12:24:37 +00:00
|
|
|
$pageLang = MediaWikiServices::getInstance()->getLanguageFactory()
|
|
|
|
|
->getLanguage( $lang );
|
2012-09-05 16:27:32 +00:00
|
|
|
}
|
|
|
|
|
|
2019-03-07 11:34:26 +00:00
|
|
|
// Simplify hook handlers by only passing objects of one type, in case nothing
|
|
|
|
|
// else has unstubbed the StubUserLang object by now.
|
|
|
|
|
StubObject::unstub( $wgLang );
|
|
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
Hooks::run( 'PageContentLanguage', [ $title, &$pageLang, $wgLang ] );
|
2013-11-19 21:26:16 +00:00
|
|
|
|
2012-10-29 17:08:04 +00:00
|
|
|
return wfGetLangObj( $pageLang );
|
2012-06-26 14:37:42 +00:00
|
|
|
}
|
|
|
|
|
|
2012-09-05 16:27:32 +00:00
|
|
|
/**
|
|
|
|
|
* Get the language in which the content of this page is written when
|
|
|
|
|
* viewed by user. Defaults to $this->getPageLanguage(), but if the user
|
|
|
|
|
* specified a preferred variant, the variant will be used.
|
|
|
|
|
*
|
|
|
|
|
* This default implementation just returns $this->getPageLanguage( $title, $content ) unless
|
|
|
|
|
* the user specified a preferred variant.
|
|
|
|
|
*
|
|
|
|
|
* Note that the pages view language is not cacheable, since it depends on user settings.
|
|
|
|
|
*
|
|
|
|
|
* Also note that the page language may or may not depend on the actual content of the page,
|
|
|
|
|
* that is, this method may load the content in order to determine the language.
|
|
|
|
|
*
|
2012-10-05 13:03:24 +00:00
|
|
|
* @since 1.21
|
2012-09-05 16:27:32 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @param Title $title The page to determine the language for.
|
2018-06-26 21:14:43 +00:00
|
|
|
* @param Content|null $content The page's content, if you have it handy, to avoid reloading it.
|
2012-09-05 16:27:32 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @return Language The page's language for viewing
|
2012-09-05 16:27:32 +00:00
|
|
|
*/
|
|
|
|
|
public function getPageViewLanguage( Title $title, Content $content = null ) {
|
|
|
|
|
$pageLang = $this->getPageLanguage( $title, $content );
|
|
|
|
|
|
|
|
|
|
if ( $title->getNamespace() !== NS_MEDIAWIKI ) {
|
|
|
|
|
// If the user chooses a variant, the content is actually
|
|
|
|
|
// in a language whose code is the variant code.
|
|
|
|
|
$variant = $pageLang->getPreferredVariant();
|
|
|
|
|
if ( $pageLang->getCode() !== $variant ) {
|
2019-08-26 12:24:37 +00:00
|
|
|
$pageLang = MediaWikiServices::getInstance()->getLanguageFactory()
|
|
|
|
|
->getLanguage( $variant );
|
2012-09-05 16:27:32 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $pageLang;
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-11 13:39:52 +00:00
|
|
|
/**
|
|
|
|
|
* Determines whether the content type handled by this ContentHandler
|
2018-11-19 11:39:56 +00:00
|
|
|
* can be used for the main slot of the given page.
|
2012-09-11 13:39:52 +00:00
|
|
|
*
|
|
|
|
|
* This default implementation always returns true.
|
|
|
|
|
* Subclasses may override this to restrict the use of this content model to specific locations,
|
|
|
|
|
* typically based on the namespace or some other aspect of the title, such as a special suffix
|
|
|
|
|
* (e.g. ".svg" for SVG content).
|
|
|
|
|
*
|
2014-07-24 09:30:25 +00:00
|
|
|
* @note this calls the ContentHandlerCanBeUsedOn hook which may be used to override which
|
2013-12-10 13:36:52 +00:00
|
|
|
* content model can be used where.
|
|
|
|
|
*
|
2018-11-19 11:39:56 +00:00
|
|
|
* @see SlotRoleHandler::isAllowedModel
|
|
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @param Title $title The page's title.
|
2012-09-11 13:39:52 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @return bool True if content of this kind can be used on the given page, false otherwise.
|
2012-09-11 13:39:52 +00:00
|
|
|
*/
|
|
|
|
|
public function canBeUsedOn( Title $title ) {
|
2013-12-10 13:36:52 +00:00
|
|
|
$ok = true;
|
|
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
Hooks::run( 'ContentModelCanBeUsedOn', [ $this->getModelID(), $title, &$ok ] );
|
2013-12-10 13:36:52 +00:00
|
|
|
|
|
|
|
|
return $ok;
|
2012-09-11 13:39:52 +00:00
|
|
|
}
|
|
|
|
|
|
2012-04-12 14:01:33 +00:00
|
|
|
/**
|
|
|
|
|
* Returns the name of the diff engine to use.
|
|
|
|
|
*
|
2012-10-05 13:03:24 +00:00
|
|
|
* @since 1.21
|
2012-04-12 14:01:33 +00:00
|
|
|
*
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
protected function getDiffEngineClass() {
|
2016-03-14 15:54:50 +00:00
|
|
|
return DifferenceEngine::class;
|
2012-04-12 14:01:33 +00:00
|
|
|
}
|
|
|
|
|
|
2012-04-25 16:24:33 +00:00
|
|
|
/**
|
2014-03-03 17:08:05 +00:00
|
|
|
* Attempts to merge differences between three versions. Returns a new
|
|
|
|
|
* Content object for a clean merge and false for failure or a conflict.
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
|
|
|
|
* This default implementation always returns false.
|
|
|
|
|
*
|
2012-10-05 13:03:24 +00:00
|
|
|
* @since 1.21
|
2012-05-10 19:22:11 +00:00
|
|
|
*
|
2014-08-14 17:48:55 +00:00
|
|
|
* @param Content $oldContent The page's previous content.
|
|
|
|
|
* @param Content $myContent One of the page's conflicting contents.
|
|
|
|
|
* @param Content $yourContent One of the page's conflicting contents.
|
2012-06-03 17:27:23 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @return Content|bool Always false.
|
2012-04-25 16:24:33 +00:00
|
|
|
*/
|
|
|
|
|
public function merge3( Content $oldContent, Content $myContent, Content $yourContent ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-09 11:13:16 +00:00
|
|
|
/**
|
|
|
|
|
* Return type of change if one exists for the given edit.
|
|
|
|
|
*
|
|
|
|
|
* @since 1.31
|
|
|
|
|
*
|
|
|
|
|
* @param Content|null $oldContent The previous text of the page.
|
|
|
|
|
* @param Content|null $newContent The submitted text of the page.
|
|
|
|
|
* @param int $flags Bit mask: a bit mask of flags submitted for the edit.
|
|
|
|
|
*
|
|
|
|
|
* @return string|null String key representing type of change, or null.
|
|
|
|
|
*/
|
|
|
|
|
private function getChangeType(
|
|
|
|
|
Content $oldContent = null,
|
|
|
|
|
Content $newContent = null,
|
|
|
|
|
$flags = 0
|
|
|
|
|
) {
|
|
|
|
|
$oldTarget = $oldContent !== null ? $oldContent->getRedirectTarget() : null;
|
|
|
|
|
$newTarget = $newContent !== null ? $newContent->getRedirectTarget() : null;
|
|
|
|
|
|
|
|
|
|
// We check for the type of change in the given edit, and return string key accordingly
|
|
|
|
|
|
|
|
|
|
// Blanking of a page
|
|
|
|
|
if ( $oldContent && $oldContent->getSize() > 0 &&
|
|
|
|
|
$newContent && $newContent->getSize() === 0
|
|
|
|
|
) {
|
|
|
|
|
return 'blank';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Redirects
|
|
|
|
|
if ( $newTarget ) {
|
|
|
|
|
if ( !$oldTarget ) {
|
|
|
|
|
// New redirect page (by creating new page or by changing content page)
|
|
|
|
|
return 'new-redirect';
|
|
|
|
|
} elseif ( !$newTarget->equals( $oldTarget ) ||
|
|
|
|
|
$oldTarget->getFragment() !== $newTarget->getFragment()
|
|
|
|
|
) {
|
|
|
|
|
// Redirect target changed
|
|
|
|
|
return 'changed-redirect-target';
|
|
|
|
|
}
|
|
|
|
|
} elseif ( $oldTarget ) {
|
|
|
|
|
// Changing an existing redirect into a non-redirect
|
|
|
|
|
return 'removed-redirect';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// New page created
|
2017-12-12 18:21:02 +00:00
|
|
|
if ( $flags & EDIT_NEW && $newContent ) {
|
|
|
|
|
if ( $newContent->getSize() === 0 ) {
|
|
|
|
|
// New blank page
|
|
|
|
|
return 'newblank';
|
|
|
|
|
} else {
|
|
|
|
|
return 'newpage';
|
|
|
|
|
}
|
2017-11-09 11:13:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Removing more than 90% of the page
|
|
|
|
|
if ( $oldContent && $newContent && $oldContent->getSize() > 10 * $newContent->getSize() ) {
|
|
|
|
|
return 'replace';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Content model changed
|
|
|
|
|
if ( $oldContent && $newContent && $oldContent->getModel() !== $newContent->getModel() ) {
|
|
|
|
|
return 'contentmodelchange';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2012-04-25 16:24:33 +00:00
|
|
|
/**
|
2012-06-03 17:27:23 +00:00
|
|
|
* Return an applicable auto-summary if one exists for the given edit.
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
2012-10-05 13:03:24 +00:00
|
|
|
* @since 1.21
|
2012-05-10 19:22:11 +00:00
|
|
|
*
|
2017-11-09 11:13:16 +00:00
|
|
|
* @param Content|null $oldContent The previous text of the page.
|
|
|
|
|
* @param Content|null $newContent The submitted text of the page.
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param int $flags Bit mask: a bit mask of flags submitted for the edit.
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
2012-06-03 17:27:23 +00:00
|
|
|
* @return string An appropriate auto-summary, or an empty string.
|
2012-04-25 16:24:33 +00:00
|
|
|
*/
|
2017-11-09 11:13:16 +00:00
|
|
|
public function getAutosummary(
|
|
|
|
|
Content $oldContent = null,
|
|
|
|
|
Content $newContent = null,
|
|
|
|
|
$flags = 0
|
|
|
|
|
) {
|
|
|
|
|
$changeType = $this->getChangeType( $oldContent, $newContent, $flags );
|
2012-04-25 16:34:36 +00:00
|
|
|
|
2017-11-09 11:13:16 +00:00
|
|
|
// There's no applicable auto-summary for our case, so our auto-summary is empty.
|
|
|
|
|
if ( !$changeType ) {
|
|
|
|
|
return '';
|
|
|
|
|
}
|
2012-04-25 16:24:33 +00:00
|
|
|
|
2017-11-09 11:13:16 +00:00
|
|
|
// Decide what kind of auto-summary is needed.
|
|
|
|
|
switch ( $changeType ) {
|
|
|
|
|
case 'new-redirect':
|
|
|
|
|
$newTarget = $newContent->getRedirectTarget();
|
2012-06-13 07:11:43 +00:00
|
|
|
$truncatedtext = $newContent->getTextForSummary(
|
|
|
|
|
250
|
2013-11-19 21:26:16 +00:00
|
|
|
- strlen( wfMessage( 'autoredircomment' )->inContentLanguage()->text() )
|
2017-11-09 11:13:16 +00:00
|
|
|
- strlen( $newTarget->getFullText() )
|
|
|
|
|
);
|
2012-06-13 07:11:43 +00:00
|
|
|
|
2017-11-09 11:13:16 +00:00
|
|
|
return wfMessage( 'autoredircomment', $newTarget->getFullText() )
|
2017-12-07 02:54:03 +00:00
|
|
|
->plaintextParams( $truncatedtext )->inContentLanguage()->text();
|
2017-11-09 11:13:16 +00:00
|
|
|
case 'changed-redirect-target':
|
|
|
|
|
$oldTarget = $oldContent->getRedirectTarget();
|
|
|
|
|
$newTarget = $newContent->getRedirectTarget();
|
2012-04-25 16:24:33 +00:00
|
|
|
|
2017-11-09 11:13:16 +00:00
|
|
|
$truncatedtext = $newContent->getTextForSummary(
|
|
|
|
|
250
|
|
|
|
|
- strlen( wfMessage( 'autosumm-changed-redirect-target' )
|
|
|
|
|
->inContentLanguage()->text() )
|
|
|
|
|
- strlen( $oldTarget->getFullText() )
|
|
|
|
|
- strlen( $newTarget->getFullText() )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return wfMessage( 'autosumm-changed-redirect-target',
|
|
|
|
|
$oldTarget->getFullText(),
|
|
|
|
|
$newTarget->getFullText() )
|
|
|
|
|
->rawParams( $truncatedtext )->inContentLanguage()->text();
|
|
|
|
|
case 'removed-redirect':
|
|
|
|
|
$oldTarget = $oldContent->getRedirectTarget();
|
|
|
|
|
$truncatedtext = $newContent->getTextForSummary(
|
|
|
|
|
250
|
|
|
|
|
- strlen( wfMessage( 'autosumm-removed-redirect' )
|
|
|
|
|
->inContentLanguage()->text() )
|
|
|
|
|
- strlen( $oldTarget->getFullText() ) );
|
2012-04-25 16:24:33 +00:00
|
|
|
|
2017-11-09 11:13:16 +00:00
|
|
|
return wfMessage( 'autosumm-removed-redirect', $oldTarget->getFullText() )
|
|
|
|
|
->rawParams( $truncatedtext )->inContentLanguage()->text();
|
|
|
|
|
case 'newpage':
|
|
|
|
|
// If they're making a new article, give its text, truncated, in the summary.
|
|
|
|
|
$truncatedtext = $newContent->getTextForSummary(
|
|
|
|
|
200 - strlen( wfMessage( 'autosumm-new' )->inContentLanguage()->text() ) );
|
2012-04-25 16:24:33 +00:00
|
|
|
|
2017-11-09 11:13:16 +00:00
|
|
|
return wfMessage( 'autosumm-new' )->rawParams( $truncatedtext )
|
|
|
|
|
->inContentLanguage()->text();
|
|
|
|
|
case 'blank':
|
|
|
|
|
return wfMessage( 'autosumm-blank' )->inContentLanguage()->text();
|
|
|
|
|
case 'replace':
|
|
|
|
|
$truncatedtext = $newContent->getTextForSummary(
|
|
|
|
|
200 - strlen( wfMessage( 'autosumm-replace' )->inContentLanguage()->text() ) );
|
|
|
|
|
|
|
|
|
|
return wfMessage( 'autosumm-replace' )->rawParams( $truncatedtext )
|
|
|
|
|
->inContentLanguage()->text();
|
|
|
|
|
case 'newblank':
|
|
|
|
|
return wfMessage( 'autosumm-newblank' )->inContentLanguage()->text();
|
|
|
|
|
default:
|
|
|
|
|
return '';
|
2012-04-25 16:24:33 +00:00
|
|
|
}
|
2017-11-09 11:13:16 +00:00
|
|
|
}
|
2012-04-25 16:24:33 +00:00
|
|
|
|
2017-11-09 11:13:16 +00:00
|
|
|
/**
|
|
|
|
|
* Return an applicable tag if one exists for the given edit or return null.
|
|
|
|
|
*
|
|
|
|
|
* @since 1.31
|
|
|
|
|
*
|
|
|
|
|
* @param Content|null $oldContent The previous text of the page.
|
|
|
|
|
* @param Content|null $newContent The submitted text of the page.
|
|
|
|
|
* @param int $flags Bit mask: a bit mask of flags submitted for the edit.
|
|
|
|
|
*
|
|
|
|
|
* @return string|null An appropriate tag, or null.
|
|
|
|
|
*/
|
|
|
|
|
public function getChangeTag(
|
|
|
|
|
Content $oldContent = null,
|
|
|
|
|
Content $newContent = null,
|
|
|
|
|
$flags = 0
|
|
|
|
|
) {
|
|
|
|
|
$changeType = $this->getChangeType( $oldContent, $newContent, $flags );
|
2012-04-25 16:24:33 +00:00
|
|
|
|
2017-11-09 11:13:16 +00:00
|
|
|
// There's no applicable tag for this change.
|
|
|
|
|
if ( !$changeType ) {
|
|
|
|
|
return null;
|
2012-04-25 16:24:33 +00:00
|
|
|
}
|
|
|
|
|
|
2017-11-09 11:13:16 +00:00
|
|
|
// Core tags use the same keys as ones returned from $this->getChangeType()
|
|
|
|
|
// but prefixed with pseudo namespace 'mw-', so we add the prefix before checking
|
|
|
|
|
// if this type of change should be tagged
|
|
|
|
|
$tag = 'mw-' . $changeType;
|
|
|
|
|
|
|
|
|
|
// Not all change types are tagged, so we check against the list of defined tags.
|
|
|
|
|
if ( in_array( $tag, ChangeTags::getSoftwareTags() ) ) {
|
|
|
|
|
return $tag;
|
2014-07-13 01:36:37 +00:00
|
|
|
}
|
|
|
|
|
|
2017-11-09 11:13:16 +00:00
|
|
|
return null;
|
2012-04-25 16:24:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Auto-generates a deletion reason
|
|
|
|
|
*
|
2012-10-05 13:03:24 +00:00
|
|
|
* @since 1.21
|
2012-05-10 19:22:11 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @param Title $title The page's title
|
|
|
|
|
* @param bool &$hasHistory Whether the page has a history
|
|
|
|
|
*
|
2012-06-13 07:11:43 +00:00
|
|
|
* @return mixed String containing deletion reason or empty string, or
|
|
|
|
|
* boolean false if no revision occurred
|
2012-04-25 16:24:33 +00:00
|
|
|
*
|
2014-07-23 20:04:48 +00:00
|
|
|
* @todo &$hasHistory is extremely ugly, it's here because
|
2013-01-18 12:46:36 +00:00
|
|
|
* WikiPage::getAutoDeleteReason() and Article::generateReason()
|
2012-06-13 07:11:43 +00:00
|
|
|
* have it / want it.
|
2012-04-25 16:24:33 +00:00
|
|
|
*/
|
|
|
|
|
public function getAutoDeleteReason( Title $title, &$hasHistory ) {
|
2016-09-05 19:55:19 +00:00
|
|
|
$dbr = wfGetDB( DB_REPLICA );
|
2012-04-25 16:24:33 +00:00
|
|
|
|
|
|
|
|
// Get the last revision
|
|
|
|
|
$rev = Revision::newFromTitle( $title );
|
|
|
|
|
|
|
|
|
|
if ( is_null( $rev ) ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get the article's contents
|
|
|
|
|
$content = $rev->getContent();
|
|
|
|
|
$blank = false;
|
|
|
|
|
|
|
|
|
|
// If the page is blank, use the text from the previous revision,
|
2012-06-13 07:11:43 +00:00
|
|
|
// which can only be blank if there's a move/import/protect dummy
|
|
|
|
|
// revision involved
|
2012-10-25 07:51:23 +00:00
|
|
|
if ( !$content || $content->isEmpty() ) {
|
2012-04-25 16:24:33 +00:00
|
|
|
$prev = $rev->getPrevious();
|
|
|
|
|
|
2012-10-25 07:51:23 +00:00
|
|
|
if ( $prev ) {
|
|
|
|
|
$rev = $prev;
|
|
|
|
|
$content = $rev->getContent();
|
2012-04-25 16:24:33 +00:00
|
|
|
$blank = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-25 07:51:23 +00:00
|
|
|
$this->checkModelID( $rev->getContentModel() );
|
|
|
|
|
|
2012-04-25 16:24:33 +00:00
|
|
|
// Find out if there was only one contributor
|
|
|
|
|
// Only scan the last 20 revisions
|
2017-09-12 17:12:29 +00:00
|
|
|
$revQuery = Revision::getQueryInfo();
|
|
|
|
|
$res = $dbr->select(
|
|
|
|
|
$revQuery['tables'],
|
|
|
|
|
[ 'rev_user_text' => $revQuery['fields']['rev_user_text'] ],
|
2016-02-17 09:09:32 +00:00
|
|
|
[
|
2012-06-13 07:11:43 +00:00
|
|
|
'rev_page' => $title->getArticleID(),
|
2019-07-21 13:18:14 +00:00
|
|
|
$dbr->bitAnd( 'rev_deleted', RevisionRecord::DELETED_USER ) . ' = 0'
|
2016-02-17 09:09:32 +00:00
|
|
|
],
|
2012-04-25 16:24:33 +00:00
|
|
|
__METHOD__,
|
2017-09-12 17:12:29 +00:00
|
|
|
[ 'LIMIT' => 20 ],
|
|
|
|
|
$revQuery['joins']
|
2012-04-25 16:24:33 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( $res === false ) {
|
|
|
|
|
// This page has no revisions, which is very weird
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$hasHistory = ( $res->numRows() > 1 );
|
2016-04-28 21:26:38 +00:00
|
|
|
$row = $dbr->fetchObject( $res );
|
2012-04-25 16:24:33 +00:00
|
|
|
|
|
|
|
|
if ( $row ) { // $row is false if the only contributor is hidden
|
|
|
|
|
$onlyAuthor = $row->rev_user_text;
|
|
|
|
|
// Try to find a second contributor
|
|
|
|
|
foreach ( $res as $row ) {
|
2017-02-20 22:44:19 +00:00
|
|
|
if ( $row->rev_user_text != $onlyAuthor ) { // T24999
|
2012-04-25 16:24:33 +00:00
|
|
|
$onlyAuthor = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
$onlyAuthor = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate the summary with a '$1' placeholder
|
|
|
|
|
if ( $blank ) {
|
|
|
|
|
// The current revision is blank and the one before is also
|
|
|
|
|
// blank. It's just not our lucky day
|
2012-09-05 15:50:13 +00:00
|
|
|
$reason = wfMessage( 'exbeforeblank', '$1' )->inContentLanguage()->text();
|
2012-04-25 16:24:33 +00:00
|
|
|
} else {
|
|
|
|
|
if ( $onlyAuthor ) {
|
2012-09-05 15:50:13 +00:00
|
|
|
$reason = wfMessage(
|
|
|
|
|
'excontentauthor',
|
|
|
|
|
'$1',
|
|
|
|
|
$onlyAuthor
|
|
|
|
|
)->inContentLanguage()->text();
|
2012-04-25 16:24:33 +00:00
|
|
|
} else {
|
2012-09-05 15:50:13 +00:00
|
|
|
$reason = wfMessage( 'excontent', '$1' )->inContentLanguage()->text();
|
2012-04-25 16:24:33 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $reason == '-' ) {
|
|
|
|
|
// Allow these UI messages to be blanked out cleanly
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Max content length = max comment length - length of the comment (excl. $1)
|
2019-08-21 23:13:16 +00:00
|
|
|
$maxLength = CommentStore::COMMENT_CHARACTER_LIMIT - ( strlen( $reason ) - 2 );
|
|
|
|
|
$text = $content ? $content->getTextForSummary( $maxLength ) : '';
|
2012-04-25 16:24:33 +00:00
|
|
|
|
|
|
|
|
// Now replace the '$1' placeholder
|
|
|
|
|
$reason = str_replace( '$1', $text, $reason );
|
|
|
|
|
|
|
|
|
|
return $reason;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the Content object that needs to be saved in order to undo all revisions
|
|
|
|
|
* between $undo and $undoafter. Revisions must belong to the same page,
|
2012-06-13 07:11:43 +00:00
|
|
|
* must exist and must not be deleted.
|
2012-05-10 19:22:11 +00:00
|
|
|
*
|
2012-10-05 13:03:24 +00:00
|
|
|
* @since 1.21
|
2018-07-17 19:23:25 +00:00
|
|
|
* @since 1.32 accepts Content objects for all parameters instead of Revision objects.
|
|
|
|
|
* Passing Revision objects is deprecated.
|
2012-05-10 19:22:11 +00:00
|
|
|
*
|
2018-07-17 19:23:25 +00:00
|
|
|
* @param Revision|Content $current The current text
|
|
|
|
|
* @param Revision|Content $undo The content of the revision to undo
|
|
|
|
|
* @param Revision|Content $undoafter Must be from an earlier revision than $undo
|
|
|
|
|
* @param bool $undoIsLatest Set true if $undo is from the current revision (since 1.32)
|
2012-05-10 19:22:11 +00:00
|
|
|
*
|
2019-09-01 14:00:35 +00:00
|
|
|
* @return Content|false Content on success, false on failure
|
2012-04-25 16:24:33 +00:00
|
|
|
*/
|
2018-07-17 19:23:25 +00:00
|
|
|
public function getUndoContent( $current, $undo, $undoafter, $undoIsLatest = false ) {
|
|
|
|
|
Assert::parameterType( Revision::class . '|' . Content::class, $current, '$current' );
|
|
|
|
|
if ( $current instanceof Content ) {
|
|
|
|
|
Assert::parameter( $undo instanceof Content, '$undo',
|
|
|
|
|
'Must be Content when $current is Content' );
|
|
|
|
|
Assert::parameter( $undoafter instanceof Content, '$undoafter',
|
|
|
|
|
'Must be Content when $current is Content' );
|
|
|
|
|
$cur_content = $current;
|
|
|
|
|
$undo_content = $undo;
|
|
|
|
|
$undoafter_content = $undoafter;
|
|
|
|
|
} else {
|
|
|
|
|
Assert::parameter( $undo instanceof Revision, '$undo',
|
|
|
|
|
'Must be Revision when $current is Revision' );
|
|
|
|
|
Assert::parameter( $undoafter instanceof Revision, '$undoafter',
|
|
|
|
|
'Must be Revision when $current is Revision' );
|
2012-04-25 16:24:33 +00:00
|
|
|
|
2018-07-17 19:23:25 +00:00
|
|
|
$cur_content = $current->getContent();
|
2012-04-25 16:24:33 +00:00
|
|
|
|
2018-07-17 19:23:25 +00:00
|
|
|
if ( empty( $cur_content ) ) {
|
|
|
|
|
return false; // no page
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$undo_content = $undo->getContent();
|
|
|
|
|
$undoafter_content = $undoafter->getContent();
|
|
|
|
|
|
|
|
|
|
if ( !$undo_content || !$undoafter_content ) {
|
|
|
|
|
return false; // no content to undo
|
|
|
|
|
}
|
2012-04-25 16:24:33 +00:00
|
|
|
|
2018-07-17 19:23:25 +00:00
|
|
|
$undoIsLatest = $current->getId() === $undo->getId();
|
2013-11-05 19:37:45 +00:00
|
|
|
}
|
|
|
|
|
|
2016-09-08 03:52:19 +00:00
|
|
|
try {
|
|
|
|
|
$this->checkModelID( $cur_content->getModel() );
|
|
|
|
|
$this->checkModelID( $undo_content->getModel() );
|
2018-07-17 19:23:25 +00:00
|
|
|
if ( !$undoIsLatest ) {
|
2016-09-08 22:39:03 +00:00
|
|
|
// If we are undoing the most recent revision,
|
|
|
|
|
// its ok to revert content model changes. However
|
|
|
|
|
// if we are undoing a revision in the middle, then
|
|
|
|
|
// doing that will be confusing.
|
|
|
|
|
$this->checkModelID( $undoafter_content->getModel() );
|
|
|
|
|
}
|
2016-09-08 03:52:19 +00:00
|
|
|
} catch ( MWException $e ) {
|
|
|
|
|
// If the revisions have different content models
|
|
|
|
|
// just return false
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2012-06-11 10:35:46 +00:00
|
|
|
|
2012-04-25 16:24:33 +00:00
|
|
|
if ( $cur_content->equals( $undo_content ) ) {
|
|
|
|
|
// No use doing a merge if it's just a straight revert.
|
|
|
|
|
return $undoafter_content;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$undone_content = $this->merge3( $undo_content, $undoafter_content, $cur_content );
|
|
|
|
|
|
|
|
|
|
return $undone_content;
|
|
|
|
|
}
|
2012-04-24 10:06:23 +00:00
|
|
|
|
2012-10-04 11:00:05 +00:00
|
|
|
/**
|
2014-02-01 16:13:15 +00:00
|
|
|
* Get parser options suitable for rendering and caching the article
|
2012-10-04 11:00:05 +00:00
|
|
|
*
|
2018-07-11 16:13:18 +00:00
|
|
|
* @deprecated since 1.32, use WikiPage::makeParserOptions() or
|
|
|
|
|
* ParserOptions::newCanonical() instead.
|
2012-10-04 11:00:05 +00:00
|
|
|
* @param IContextSource|User|string $context One of the following:
|
|
|
|
|
* - IContextSource: Use the User and the Language of the provided
|
|
|
|
|
* context
|
|
|
|
|
* - User: Use the provided User object and $wgLang for the language,
|
|
|
|
|
* so use an IContextSource object if possible.
|
|
|
|
|
* - 'canonical': Canonical options (anonymous user with default
|
|
|
|
|
* preferences and content language).
|
|
|
|
|
*
|
|
|
|
|
* @throws MWException
|
|
|
|
|
* @return ParserOptions
|
|
|
|
|
*/
|
|
|
|
|
public function makeParserOptions( $context ) {
|
2018-07-11 16:14:53 +00:00
|
|
|
wfDeprecated( __METHOD__, '1.32' );
|
2018-07-11 16:13:18 +00:00
|
|
|
return ParserOptions::newCanonical( $context );
|
2012-10-04 11:00:05 +00:00
|
|
|
}
|
|
|
|
|
|
2012-04-24 10:06:23 +00:00
|
|
|
/**
|
2012-06-13 07:11:43 +00:00
|
|
|
* Returns true for content models that support caching using the
|
2015-06-23 03:38:43 +00:00
|
|
|
* ParserCache mechanism. See WikiPage::shouldCheckParserCache().
|
2012-04-24 10:06:23 +00:00
|
|
|
*
|
2012-10-05 13:03:24 +00:00
|
|
|
* @since 1.21
|
2012-05-10 19:22:11 +00:00
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @return bool Always false.
|
2012-04-24 10:06:23 +00:00
|
|
|
*/
|
|
|
|
|
public function isParserCacheSupported() {
|
2012-10-05 17:05:21 +00:00
|
|
|
return false;
|
2012-04-24 10:06:23 +00:00
|
|
|
}
|
2012-04-30 13:32:31 +00:00
|
|
|
|
2012-06-09 11:42:05 +00:00
|
|
|
/**
|
2012-06-13 07:11:43 +00:00
|
|
|
* Returns true if this content model supports sections.
|
|
|
|
|
* This default implementation returns false.
|
2012-06-09 11:42:05 +00:00
|
|
|
*
|
2012-12-13 16:26:43 +00:00
|
|
|
* Content models that return true here should also implement
|
|
|
|
|
* Content::getSection, Content::replaceSection, etc. to handle sections..
|
|
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @return bool Always false.
|
2012-06-09 11:42:05 +00:00
|
|
|
*/
|
|
|
|
|
public function supportsSections() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2012-08-20 17:28:20 +00:00
|
|
|
|
2016-02-15 15:19:58 +00:00
|
|
|
/**
|
|
|
|
|
* Returns true if this content model supports categories.
|
|
|
|
|
* The default implementation returns true.
|
|
|
|
|
*
|
|
|
|
|
* @return bool Always true.
|
|
|
|
|
*/
|
|
|
|
|
public function supportsCategories() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2012-12-13 16:26:43 +00:00
|
|
|
/**
|
|
|
|
|
* Returns true if this content model supports redirects.
|
|
|
|
|
* This default implementation returns false.
|
|
|
|
|
*
|
|
|
|
|
* Content models that return true here should also implement
|
|
|
|
|
* ContentHandler::makeRedirectContent to return a Content object.
|
|
|
|
|
*
|
2014-03-03 17:08:05 +00:00
|
|
|
* @return bool Always false.
|
2012-12-13 16:26:43 +00:00
|
|
|
*/
|
|
|
|
|
public function supportsRedirects() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-15 08:26:22 +00:00
|
|
|
/**
|
|
|
|
|
* Return true if this content model supports direct editing, such as via EditPage.
|
|
|
|
|
*
|
|
|
|
|
* @return bool Default is false, and true for TextContent and it's derivatives.
|
|
|
|
|
*/
|
|
|
|
|
public function supportsDirectEditing() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Whether or not this content model supports direct editing via ApiEditPage
|
|
|
|
|
*
|
|
|
|
|
* @return bool Default is false, and true for TextContent and derivatives.
|
|
|
|
|
*/
|
|
|
|
|
public function supportsDirectApiEditing() {
|
|
|
|
|
return $this->supportsDirectEditing();
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-13 00:10:52 +00:00
|
|
|
/**
|
|
|
|
|
* Get fields definition for search index
|
2016-08-09 17:22:09 +00:00
|
|
|
*
|
|
|
|
|
* @todo Expose title, redirect, namespace, text, source_text, text_bytes
|
|
|
|
|
* field mappings here. (see T142670 and T143409)
|
|
|
|
|
*
|
2016-05-13 00:10:52 +00:00
|
|
|
* @param SearchEngine $engine
|
|
|
|
|
* @return SearchIndexField[] List of fields this content handler can provide.
|
|
|
|
|
* @since 1.28
|
|
|
|
|
*/
|
|
|
|
|
public function getFieldsForSearchIndex( SearchEngine $engine ) {
|
2019-08-30 17:56:27 +00:00
|
|
|
$fields = [];
|
2016-08-09 17:22:09 +00:00
|
|
|
$fields['category'] = $engine->makeSearchFieldMapping(
|
|
|
|
|
'category',
|
|
|
|
|
SearchIndexField::INDEX_TYPE_TEXT
|
|
|
|
|
);
|
|
|
|
|
$fields['category']->setFlag( SearchIndexField::FLAG_CASEFOLD );
|
|
|
|
|
|
|
|
|
|
$fields['external_link'] = $engine->makeSearchFieldMapping(
|
|
|
|
|
'external_link',
|
|
|
|
|
SearchIndexField::INDEX_TYPE_KEYWORD
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$fields['outgoing_link'] = $engine->makeSearchFieldMapping(
|
|
|
|
|
'outgoing_link',
|
|
|
|
|
SearchIndexField::INDEX_TYPE_KEYWORD
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$fields['template'] = $engine->makeSearchFieldMapping(
|
|
|
|
|
'template',
|
|
|
|
|
SearchIndexField::INDEX_TYPE_KEYWORD
|
|
|
|
|
);
|
|
|
|
|
$fields['template']->setFlag( SearchIndexField::FLAG_CASEFOLD );
|
|
|
|
|
|
2017-01-26 19:15:03 +00:00
|
|
|
$fields['content_model'] = $engine->makeSearchFieldMapping(
|
|
|
|
|
'content_model',
|
|
|
|
|
SearchIndexField::INDEX_TYPE_KEYWORD
|
|
|
|
|
);
|
|
|
|
|
|
2016-08-09 17:22:09 +00:00
|
|
|
return $fields;
|
2016-05-13 00:10:52 +00:00
|
|
|
}
|
2016-05-16 20:24:10 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add new field definition to array.
|
2017-08-11 00:23:16 +00:00
|
|
|
* @param SearchIndexField[] &$fields
|
2017-08-11 21:45:25 +00:00
|
|
|
* @param SearchEngine $engine
|
|
|
|
|
* @param string $name
|
|
|
|
|
* @param int $type
|
2016-05-16 20:24:10 +00:00
|
|
|
* @return SearchIndexField[] new field defs
|
|
|
|
|
* @since 1.28
|
|
|
|
|
*/
|
|
|
|
|
protected function addSearchField( &$fields, SearchEngine $engine, $name, $type ) {
|
|
|
|
|
$fields[$name] = $engine->makeSearchFieldMapping( $name, $type );
|
|
|
|
|
return $fields;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return fields to be indexed by search engine
|
|
|
|
|
* as representation of this document.
|
|
|
|
|
* Overriding class should call parent function or take care of calling
|
|
|
|
|
* the SearchDataForIndex hook.
|
2017-08-11 21:45:25 +00:00
|
|
|
* @param WikiPage $page Page to index
|
2016-05-16 20:24:10 +00:00
|
|
|
* @param ParserOutput $output
|
|
|
|
|
* @param SearchEngine $engine Search engine for which we are indexing
|
|
|
|
|
* @return array Map of name=>value for fields
|
|
|
|
|
* @since 1.28
|
|
|
|
|
*/
|
2017-02-25 21:53:36 +00:00
|
|
|
public function getDataForSearchIndex(
|
|
|
|
|
WikiPage $page,
|
|
|
|
|
ParserOutput $output,
|
|
|
|
|
SearchEngine $engine
|
|
|
|
|
) {
|
2016-08-09 17:22:09 +00:00
|
|
|
$fieldData = [];
|
2016-05-16 20:24:10 +00:00
|
|
|
$content = $page->getContent();
|
2016-08-09 17:22:09 +00:00
|
|
|
|
2016-05-16 20:24:10 +00:00
|
|
|
if ( $content ) {
|
2016-08-09 17:22:09 +00:00
|
|
|
$searchDataExtractor = new ParserOutputSearchDataExtractor();
|
|
|
|
|
|
|
|
|
|
$fieldData['category'] = $searchDataExtractor->getCategories( $output );
|
|
|
|
|
$fieldData['external_link'] = $searchDataExtractor->getExternalLinks( $output );
|
|
|
|
|
$fieldData['outgoing_link'] = $searchDataExtractor->getOutgoingLinks( $output );
|
|
|
|
|
$fieldData['template'] = $searchDataExtractor->getTemplates( $output );
|
|
|
|
|
|
2016-05-16 20:24:10 +00:00
|
|
|
$text = $content->getTextForSearchIndex();
|
2016-08-09 17:22:09 +00:00
|
|
|
|
|
|
|
|
$fieldData['text'] = $text;
|
|
|
|
|
$fieldData['source_text'] = $text;
|
|
|
|
|
$fieldData['text_bytes'] = $content->getSize();
|
2017-01-26 19:15:03 +00:00
|
|
|
$fieldData['content_model'] = $content->getModel();
|
2016-05-16 20:24:10 +00:00
|
|
|
}
|
2016-08-09 17:22:09 +00:00
|
|
|
|
|
|
|
|
Hooks::run( 'SearchDataForIndex', [ &$fieldData, $this, $page, $output, $engine ] );
|
|
|
|
|
return $fieldData;
|
2016-05-16 20:24:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Produce page output suitable for indexing.
|
|
|
|
|
*
|
|
|
|
|
* Specific content handlers may override it if they need different content handling.
|
|
|
|
|
*
|
2017-08-11 21:45:25 +00:00
|
|
|
* @param WikiPage $page
|
2018-06-26 21:14:43 +00:00
|
|
|
* @param ParserCache|null $cache
|
2016-05-16 20:24:10 +00:00
|
|
|
* @return ParserOutput
|
|
|
|
|
*/
|
|
|
|
|
public function getParserOutputForIndexing( WikiPage $page, ParserCache $cache = null ) {
|
2018-08-14 16:37:30 +00:00
|
|
|
// TODO: MCR: ContentHandler should be called per slot, not for the whole page.
|
|
|
|
|
// See T190066.
|
2016-05-16 20:24:10 +00:00
|
|
|
$parserOptions = $page->makeParserOptions( 'canonical' );
|
|
|
|
|
if ( $cache ) {
|
|
|
|
|
$parserOutput = $cache->get( $page, $parserOptions );
|
|
|
|
|
}
|
2018-08-14 16:37:30 +00:00
|
|
|
|
2016-05-16 20:24:10 +00:00
|
|
|
if ( empty( $parserOutput ) ) {
|
2018-08-14 16:37:30 +00:00
|
|
|
$renderer = MediaWikiServices::getInstance()->getRevisionRenderer();
|
2016-05-16 20:24:10 +00:00
|
|
|
$parserOutput =
|
2018-08-14 16:37:30 +00:00
|
|
|
$renderer->getRenderedRevision(
|
|
|
|
|
$page->getRevision()->getRevisionRecord(),
|
|
|
|
|
$parserOptions
|
|
|
|
|
)->getRevisionParserOutput();
|
2016-05-16 20:24:10 +00:00
|
|
|
if ( $cache ) {
|
|
|
|
|
$cache->save( $parserOutput, $page, $parserOptions );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $parserOutput;
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-09 22:05:47 +00:00
|
|
|
/**
|
|
|
|
|
* 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 [];
|
|
|
|
|
}
|
|
|
|
|
|
2012-02-01 22:07:47 +00:00
|
|
|
}
|