MediaWiki  master
ContentHandler.php
Go to the documentation of this file.
1 <?php
2 
29 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
36 use Wikimedia\Assert\Assert;
37 
59 abstract class ContentHandler {
60  use ProtectedHookAccessorTrait;
61 
91  public static function getContentText( Content $content = null ) {
93 
94  if ( $content === null ) {
95  return '';
96  }
97 
98  if ( $content instanceof TextContent ) {
99  return $content->getText();
100  }
101 
102  wfDebugLog( 'ContentHandler', 'Accessing ' . $content->getModel() . ' content as text!' );
103 
104  if ( $wgContentHandlerTextFallback == 'fail' ) {
105  throw new MWException(
106  "Attempt to get text from Content with model " .
107  $content->getModel()
108  );
109  }
110 
111  if ( $wgContentHandlerTextFallback == 'serialize' ) {
112  return $content->serialize();
113  }
114 
115  return null;
116  }
117 
142  public static function makeContent( $text, Title $title = null,
143  $modelId = null, $format = null ) {
144  if ( $modelId === null ) {
145  if ( $title === null ) {
146  throw new MWException( "Must provide a Title object or a content model ID." );
147  }
148 
149  $modelId = $title->getContentModel();
150  }
151 
152  return MediaWikiServices::getInstance()
153  ->getContentHandlerFactory()
154  ->getContentHandler( $modelId )
155  ->unserializeContent( $text, $format );
156  }
157 
194  public static function getDefaultModelFor( Title $title ) {
195  $slotRoleregistry = MediaWikiServices::getInstance()->getSlotRoleRegistry();
196  $mainSlotHandler = $slotRoleregistry->getRoleHandler( 'main' );
197  return $mainSlotHandler->getDefaultModel( $title );
198  }
199 
213  public static function getForTitle( Title $title ) {
214  return MediaWikiServices::getInstance()
215  ->getContentHandlerFactory()
216  ->getContentHandler( $title->getContentModel() );
217  }
218 
234  public static function getForContent( Content $content ) {
235  return MediaWikiServices::getInstance()
236  ->getContentHandlerFactory()
237  ->getContentHandler( $content->getModel() );
238  }
239 
269  public static function getForModelID( $modelId ) {
270  return MediaWikiServices::getInstance()
271  ->getContentHandlerFactory()
272  ->getContentHandler( $modelId );
273  }
274 
281  public static function cleanupHandlersCache() {
282  // No-op: no longer needed, since the instance cache is in the
283  // ContentHandlerFactory service, and services get reset between tests
284  }
285 
299  public static function getLocalizedName( $name, Language $lang = null ) {
300  // Messages: content-model-wikitext, content-model-text,
301  // content-model-javascript, content-model-css
302  $key = "content-model-$name";
303 
304  $msg = wfMessage( $key );
305  if ( $lang ) {
306  $msg->inLanguage( $lang );
307  }
308 
309  return $msg->exists() ? $msg->plain() : $name;
310  }
311 
320  public static function getContentModels() {
321  return MediaWikiServices::getInstance()->getContentHandlerFactory()->getContentModels();
322  }
323 
332  public static function getAllContentFormats() {
333  return MediaWikiServices::getInstance()->getContentHandlerFactory()->getAllContentFormats();
334  }
335 
336  // ------------------------------------------------------------------------
337 
341  protected $mModelID;
342 
347 
359  public function __construct( $modelId, $formats ) {
360  $this->mModelID = $modelId;
361  $this->mSupportedFormats = $formats;
362  }
363 
376  abstract public function serializeContent( Content $content, $format = null );
377 
390  public function exportTransform( $blob, $format = null ) {
391  return $blob;
392  }
393 
406  abstract public function unserializeContent( $blob, $format = null );
407 
420  public function importTransform( $blob, $format = null ) {
421  return $blob;
422  }
423 
433  abstract public function makeEmptyContent();
434 
453  public function makeRedirectContent( Title $destination, $text = '' ) {
454  return null;
455  }
456 
465  public function getModelID() {
466  return $this->mModelID;
467  }
468 
477  protected function checkModelID( $model_id ) {
478  if ( $model_id !== $this->mModelID ) {
479  throw new MWException( "Bad content model: " .
480  "expected {$this->mModelID} " .
481  "but got $model_id." );
482  }
483  }
484 
495  public function getSupportedFormats() {
497  }
498 
511  public function getDefaultFormat() {
512  return $this->mSupportedFormats[0];
513  }
514 
529  public function isSupportedFormat( $format ) {
530  if ( !$format ) {
531  return true; // this means "use the default"
532  }
533 
534  return in_array( $format, $this->mSupportedFormats );
535  }
536 
544  protected function checkFormat( $format ) {
545  if ( !$this->isSupportedFormat( $format ) ) {
546  throw new MWException(
547  "Format $format is not supported for content model "
548  . $this->getModelID()
549  );
550  }
551  }
552 
569  public function getActionOverrides() {
570  return [];
571  }
572 
600  public function createDifferenceEngine( IContextSource $context, $old = 0, $new = 0,
601  $rcid = 0, // FIXME: Deprecated, no longer used
602  $refreshCache = false, $unhide = false
603  ) {
604  $diffEngineClass = $this->getDiffEngineClass();
605  $differenceEngine = new $diffEngineClass( $context, $old, $new, $rcid, $refreshCache, $unhide );
606  $this->getHookRunner()->onGetDifferenceEngine(
607  $context, $old, $new, $refreshCache, $unhide, $differenceEngine );
608  return $differenceEngine;
609  }
610 
621  final public function getSlotDiffRenderer( IContextSource $context, array $options = [] ) {
622  $slotDiffRenderer = $this->getSlotDiffRendererWithOptions( $context, $options );
623  if ( get_class( $slotDiffRenderer ) === TextSlotDiffRenderer::class ) {
624  // To keep B/C, when SlotDiffRenderer is not overridden for a given content type
625  // but DifferenceEngine is, use that instead.
626  $differenceEngine = $this->createDifferenceEngine( $context );
627  if ( get_class( $differenceEngine ) !== DifferenceEngine::class ) {
628  // TODO turn this into a deprecation warning in a later release
629  LoggerFactory::getInstance( 'diff' )->info(
630  'Falling back to DifferenceEngineSlotDiffRenderer', [
631  'modelID' => $this->getModelID(),
632  'DifferenceEngine' => get_class( $differenceEngine ),
633  ] );
634  $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
635  }
636  }
637  $this->getHookRunner()->onGetSlotDiffRenderer( $this, $slotDiffRenderer, $context );
638  return $slotDiffRenderer;
639  }
640 
647  protected function getSlotDiffRendererInternal( IContextSource $context ) {
648  return null;
649  }
650 
660  protected function getSlotDiffRendererWithOptions( IContextSource $context, $options = [] ) {
661  $internalRenderer = $this->getSlotDiffRendererInternal( $context );
662  // `getSlotDiffRendererInternal` has been overriden by a class using the deprecated method.
663  // Options will not work so exit early!
664  if ( $internalRenderer !== null ) {
665  return $internalRenderer;
666  }
667 
668  $contentLanguage = MediaWikiServices::getInstance()->getContentLanguage();
669  $statsdDataFactory = MediaWikiServices::getInstance()->getStatsdDataFactory();
670  $slotDiffRenderer = new TextSlotDiffRenderer();
671  $slotDiffRenderer->setStatsdDataFactory( $statsdDataFactory );
672  // XXX using the page language would be better, but it's unclear how that should be injected
673  $slotDiffRenderer->setLanguage( $contentLanguage );
674 
675  $inline = ( $options['diff-type'] ?? '' ) === 'inline';
676  $engine = 'wikidiff2';
677  $engine = DifferenceEngine::getEngine();
678 
679  if ( $engine === 'php' ) {
680  $slotDiffRenderer->setEngine( TextSlotDiffRenderer::ENGINE_PHP );
681  } elseif ( $engine === 'wikidiff2' ) {
682  if ( $inline ) {
683  $slotDiffRenderer->setEngine( TextSlotDiffRenderer::ENGINE_WIKIDIFF2_INLINE );
684  } else {
685  $slotDiffRenderer->setEngine( TextSlotDiffRenderer::ENGINE_WIKIDIFF2 );
686  }
687  } else {
688  $slotDiffRenderer->setEngine( TextSlotDiffRenderer::ENGINE_EXTERNAL, $engine );
689  }
690 
691  return $slotDiffRenderer;
692  }
693 
714  public function getPageLanguage( Title $title, Content $content = null ) {
715  global $wgLang;
716  $services = MediaWikiServices::getInstance();
717  $pageLang = $services->getContentLanguage();
718 
719  if ( $title->inNamespace( NS_MEDIAWIKI ) ) {
720  // Parse mediawiki messages with correct target language
721  list( /* $unused */, $lang ) = $services->getMessageCache()->figureMessage( $title->getText() );
722  $pageLang = $services->getLanguageFactory()->getLanguage( $lang );
723  }
724 
725  // Simplify hook handlers by only passing objects of one type, in case nothing
726  // else has unstubbed the StubUserLang object by now.
728 
729  $this->getHookRunner()->onPageContentLanguage( $title, $pageLang, $wgLang );
730 
731  return wfGetLangObj( $pageLang );
732  }
733 
755  public function getPageViewLanguage( Title $title, Content $content = null ) {
756  $pageLang = $this->getPageLanguage( $title, $content );
757 
758  if ( $title->getNamespace() !== NS_MEDIAWIKI ) {
759  // If the user chooses a variant, the content is actually
760  // in a language whose code is the variant code.
761  $variant = $this->getLanguageConverter( $pageLang )->getPreferredVariant();
762  if ( $pageLang->getCode() !== $variant ) {
763  $pageLang = MediaWikiServices::getInstance()->getLanguageFactory()
764  ->getLanguage( $variant );
765  }
766  }
767 
768  return $pageLang;
769  }
770 
791  public function canBeUsedOn( Title $title ) {
792  $ok = true;
793 
794  $this->getHookRunner()->onContentModelCanBeUsedOn( $this->getModelID(), $title, $ok );
795 
796  return $ok;
797  }
798 
807  protected function getDiffEngineClass() {
808  return DifferenceEngine::class;
809  }
810 
826  public function merge3( Content $oldContent, Content $myContent, Content $yourContent ) {
827  return false;
828  }
829 
835  private function getLanguageConverter( $language ) : ILanguageConverter {
836  return MediaWikiServices::getInstance()->getLanguageConverterFactory()
837  ->getLanguageConverter( $language );
838  }
839 
852  private function getChangeType(
853  Content $oldContent = null,
854  Content $newContent = null,
855  $flags = 0
856  ) {
857  $oldTarget = $oldContent !== null ? $oldContent->getRedirectTarget() : null;
858  $newTarget = $newContent !== null ? $newContent->getRedirectTarget() : null;
859 
860  // We check for the type of change in the given edit, and return string key accordingly
861 
862  // Blanking of a page
863  if ( $oldContent && $oldContent->getSize() > 0 &&
864  $newContent && $newContent->getSize() === 0
865  ) {
866  return 'blank';
867  }
868 
869  // Redirects
870  if ( $newTarget ) {
871  if ( !$oldTarget ) {
872  // New redirect page (by creating new page or by changing content page)
873  return 'new-redirect';
874  } elseif ( !$newTarget->equals( $oldTarget ) ||
875  $oldTarget->getFragment() !== $newTarget->getFragment()
876  ) {
877  // Redirect target changed
878  return 'changed-redirect-target';
879  }
880  } elseif ( $oldTarget ) {
881  // Changing an existing redirect into a non-redirect
882  return 'removed-redirect';
883  }
884 
885  // New page created
886  if ( $flags & EDIT_NEW && $newContent ) {
887  if ( $newContent->getSize() === 0 ) {
888  // New blank page
889  return 'newblank';
890  } else {
891  return 'newpage';
892  }
893  }
894 
895  // Removing more than 90% of the page
896  if ( $oldContent && $newContent && $oldContent->getSize() > 10 * $newContent->getSize() ) {
897  return 'replace';
898  }
899 
900  // Content model changed
901  if ( $oldContent && $newContent && $oldContent->getModel() !== $newContent->getModel() ) {
902  return 'contentmodelchange';
903  }
904 
905  return null;
906  }
907 
920  public function getAutosummary(
921  Content $oldContent = null,
922  Content $newContent = null,
923  $flags = 0
924  ) {
925  $changeType = $this->getChangeType( $oldContent, $newContent, $flags );
926 
927  // There's no applicable auto-summary for our case, so our auto-summary is empty.
928  if ( !$changeType ) {
929  return '';
930  }
931 
932  // Decide what kind of auto-summary is needed.
933  switch ( $changeType ) {
934  case 'new-redirect':
935  $newTarget = $newContent->getRedirectTarget();
936  $truncatedtext = $newContent->getTextForSummary(
937  250
938  - strlen( wfMessage( 'autoredircomment' )->inContentLanguage()->text() )
939  - strlen( $newTarget->getFullText() )
940  );
941 
942  return wfMessage( 'autoredircomment', $newTarget->getFullText() )
943  ->plaintextParams( $truncatedtext )->inContentLanguage()->text();
944  case 'changed-redirect-target':
945  $oldTarget = $oldContent->getRedirectTarget();
946  $newTarget = $newContent->getRedirectTarget();
947 
948  $truncatedtext = $newContent->getTextForSummary(
949  250
950  - strlen( wfMessage( 'autosumm-changed-redirect-target' )
951  ->inContentLanguage()->text() )
952  - strlen( $oldTarget->getFullText() )
953  - strlen( $newTarget->getFullText() )
954  );
955 
956  return wfMessage( 'autosumm-changed-redirect-target',
957  $oldTarget->getFullText(),
958  $newTarget->getFullText() )
959  ->rawParams( $truncatedtext )->inContentLanguage()->text();
960  case 'removed-redirect':
961  $oldTarget = $oldContent->getRedirectTarget();
962  $truncatedtext = $newContent->getTextForSummary(
963  250
964  - strlen( wfMessage( 'autosumm-removed-redirect' )
965  ->inContentLanguage()->text() )
966  - strlen( $oldTarget->getFullText() ) );
967 
968  return wfMessage( 'autosumm-removed-redirect', $oldTarget->getFullText() )
969  ->rawParams( $truncatedtext )->inContentLanguage()->text();
970  case 'newpage':
971  // If they're making a new article, give its text, truncated, in the summary.
972  $truncatedtext = $newContent->getTextForSummary(
973  200 - strlen( wfMessage( 'autosumm-new' )->inContentLanguage()->text() ) );
974 
975  return wfMessage( 'autosumm-new' )->rawParams( $truncatedtext )
976  ->inContentLanguage()->text();
977  case 'blank':
978  return wfMessage( 'autosumm-blank' )->inContentLanguage()->text();
979  case 'replace':
980  $truncatedtext = $newContent->getTextForSummary(
981  200 - strlen( wfMessage( 'autosumm-replace' )->inContentLanguage()->text() ) );
982 
983  return wfMessage( 'autosumm-replace' )->rawParams( $truncatedtext )
984  ->inContentLanguage()->text();
985  case 'newblank':
986  return wfMessage( 'autosumm-newblank' )->inContentLanguage()->text();
987  default:
988  return '';
989  }
990  }
991 
1004  public function getChangeTag(
1005  Content $oldContent = null,
1006  Content $newContent = null,
1007  $flags = 0
1008  ) {
1009  $changeType = $this->getChangeType( $oldContent, $newContent, $flags );
1010 
1011  // There's no applicable tag for this change.
1012  if ( !$changeType ) {
1013  return null;
1014  }
1015 
1016  // Core tags use the same keys as ones returned from $this->getChangeType()
1017  // but prefixed with pseudo namespace 'mw-', so we add the prefix before checking
1018  // if this type of change should be tagged
1019  $tag = 'mw-' . $changeType;
1020 
1021  // Not all change types are tagged, so we check against the list of defined tags.
1022  if ( in_array( $tag, ChangeTags::getSoftwareTags() ) ) {
1023  return $tag;
1024  }
1025 
1026  return null;
1027  }
1028 
1045  public function getAutoDeleteReason( Title $title, &$hasHistory ) {
1046  $dbr = wfGetDB( DB_REPLICA );
1047  $revLookup = MediaWikiServices::getInstance()->getRevisionLookup();
1048 
1049  // Get the last revision
1050  $revRecord = $revLookup->getRevisionByTitle( $title );
1051 
1052  if ( $revRecord === null ) {
1053  return false;
1054  }
1055 
1056  // Get the article's contents
1057  $content = $revRecord->getContent( SlotRecord::MAIN );
1058  $blank = false;
1059 
1060  // If the page is blank, use the text from the previous revision,
1061  // which can only be blank if there's a move/import/protect dummy
1062  // revision involved
1063  if ( !$content || $content->isEmpty() ) {
1064  $prev = $revLookup->getPreviousRevision( $revRecord );
1065 
1066  if ( $prev ) {
1067  $revRecord = $prev;
1068  $content = $prev->getContent( SlotRecord::MAIN );
1069  $blank = true;
1070  }
1071  }
1072 
1073  $this->checkModelID( $revRecord->getSlot( SlotRecord::MAIN )->getModel() );
1074 
1075  // Find out if there was only one contributor
1076  // Only scan the last 20 revisions
1077  $revQuery = MediaWikiServices::getInstance()->getRevisionStore()->getQueryInfo();
1078  $res = $dbr->select(
1079  $revQuery['tables'],
1080  [ 'rev_user_text' => $revQuery['fields']['rev_user_text'] ],
1081  [
1082  'rev_page' => $title->getArticleID(),
1083  $dbr->bitAnd( 'rev_deleted', RevisionRecord::DELETED_USER ) . ' = 0'
1084  ],
1085  __METHOD__,
1086  [ 'LIMIT' => 20 ],
1087  $revQuery['joins']
1088  );
1089 
1090  if ( $res === false ) {
1091  // This page has no revisions, which is very weird
1092  return false;
1093  }
1094 
1095  $hasHistory = ( $res->numRows() > 1 );
1096  $row = $dbr->fetchObject( $res );
1097 
1098  if ( $row ) { // $row is false if the only contributor is hidden
1099  $onlyAuthor = $row->rev_user_text;
1100  // Try to find a second contributor
1101  foreach ( $res as $row ) {
1102  if ( $row->rev_user_text != $onlyAuthor ) { // T24999
1103  $onlyAuthor = false;
1104  break;
1105  }
1106  }
1107  } else {
1108  $onlyAuthor = false;
1109  }
1110 
1111  // Generate the summary with a '$1' placeholder
1112  if ( $blank ) {
1113  // The current revision is blank and the one before is also
1114  // blank. It's just not our lucky day
1115  $reason = wfMessage( 'exbeforeblank', '$1' )->inContentLanguage()->text();
1116  } else {
1117  if ( $onlyAuthor ) {
1118  $reason = wfMessage(
1119  'excontentauthor',
1120  '$1',
1121  $onlyAuthor
1122  )->inContentLanguage()->text();
1123  } else {
1124  $reason = wfMessage( 'excontent', '$1' )->inContentLanguage()->text();
1125  }
1126  }
1127 
1128  if ( $reason == '-' ) {
1129  // Allow these UI messages to be blanked out cleanly
1130  return '';
1131  }
1132 
1133  // Max content length = max comment length - length of the comment (excl. $1)
1134  $maxLength = CommentStore::COMMENT_CHARACTER_LIMIT - ( strlen( $reason ) - 2 );
1135  $text = $content ? $content->getTextForSummary( $maxLength ) : '';
1136 
1137  // Now replace the '$1' placeholder
1138  $reason = str_replace( '$1', $text, $reason );
1139 
1140  return $reason;
1141  }
1142 
1163  public function getUndoContent( $current, $undo, $undoafter, $undoIsLatest = false ) {
1164  Assert::parameterType( Revision::class . '|' . Content::class, $current, '$current' );
1165  if ( $current instanceof Content ) {
1166  Assert::parameter( $undo instanceof Content, '$undo',
1167  'Must be Content when $current is Content' );
1168  Assert::parameter( $undoafter instanceof Content, '$undoafter',
1169  'Must be Content when $current is Content' );
1170  $cur_content = $current;
1171  $undo_content = $undo;
1172  $undoafter_content = $undoafter;
1173  } else {
1174  Assert::parameter( $undo instanceof Revision, '$undo',
1175  'Must be Revision when $current is Revision' );
1176  Assert::parameter( $undoafter instanceof Revision, '$undoafter',
1177  'Must be Revision when $current is Revision' );
1178 
1179  wfDeprecated( __METHOD__ . ' with Revision objects', '1.32' );
1180 
1181  $cur_content = $current->getContent();
1182 
1183  if ( empty( $cur_content ) ) {
1184  return false; // no page
1185  }
1186 
1187  $undo_content = $undo->getContent();
1188  $undoafter_content = $undoafter->getContent();
1189 
1190  if ( !$undo_content || !$undoafter_content ) {
1191  return false; // no content to undo
1192  }
1193 
1194  $undoIsLatest = $current->getId() === $undo->getId();
1195  }
1196 
1197  try {
1198  $this->checkModelID( $cur_content->getModel() );
1199  $this->checkModelID( $undo_content->getModel() );
1200  if ( !$undoIsLatest ) {
1201  // If we are undoing the most recent revision,
1202  // its ok to revert content model changes. However
1203  // if we are undoing a revision in the middle, then
1204  // doing that will be confusing.
1205  $this->checkModelID( $undoafter_content->getModel() );
1206  }
1207  } catch ( MWException $e ) {
1208  // If the revisions have different content models
1209  // just return false
1210  return false;
1211  }
1212 
1213  if ( $cur_content->equals( $undo_content ) ) {
1214  // No use doing a merge if it's just a straight revert.
1215  return $undoafter_content;
1216  }
1217 
1218  $undone_content = $this->merge3( $undo_content, $undoafter_content, $cur_content );
1219 
1220  return $undone_content;
1221  }
1222 
1232  public function isParserCacheSupported() {
1233  return false;
1234  }
1235 
1247  public function supportsSections() {
1248  return false;
1249  }
1250 
1259  public function supportsCategories() {
1260  return true;
1261  }
1262 
1274  public function supportsRedirects() {
1275  return false;
1276  }
1277 
1285  public function supportsDirectEditing() {
1286  return false;
1287  }
1288 
1296  public function supportsDirectApiEditing() {
1297  return $this->supportsDirectEditing();
1298  }
1299 
1312  public function getFieldsForSearchIndex( SearchEngine $engine ) {
1313  $fields = [];
1314  $fields['category'] = $engine->makeSearchFieldMapping(
1315  'category',
1317  );
1318  $fields['category']->setFlag( SearchIndexField::FLAG_CASEFOLD );
1319 
1320  $fields['external_link'] = $engine->makeSearchFieldMapping(
1321  'external_link',
1323  );
1324 
1325  $fields['outgoing_link'] = $engine->makeSearchFieldMapping(
1326  'outgoing_link',
1328  );
1329 
1330  $fields['template'] = $engine->makeSearchFieldMapping(
1331  'template',
1333  );
1334  $fields['template']->setFlag( SearchIndexField::FLAG_CASEFOLD );
1335 
1336  $fields['content_model'] = $engine->makeSearchFieldMapping(
1337  'content_model',
1339  );
1340 
1341  return $fields;
1342  }
1343 
1353  protected function addSearchField( &$fields, SearchEngine $engine, $name, $type ) {
1354  $fields[$name] = $engine->makeSearchFieldMapping( $name, $type );
1355  return $fields;
1356  }
1357 
1371  public function getDataForSearchIndex(
1372  WikiPage $page,
1373  ParserOutput $output,
1374  SearchEngine $engine
1375  ) {
1376  $fieldData = [];
1377  $content = $page->getContent();
1378 
1379  if ( $content ) {
1380  $searchDataExtractor = new ParserOutputSearchDataExtractor();
1381 
1382  $fieldData['category'] = $searchDataExtractor->getCategories( $output );
1383  $fieldData['external_link'] = $searchDataExtractor->getExternalLinks( $output );
1384  $fieldData['outgoing_link'] = $searchDataExtractor->getOutgoingLinks( $output );
1385  $fieldData['template'] = $searchDataExtractor->getTemplates( $output );
1386 
1387  $text = $content->getTextForSearchIndex();
1388 
1389  $fieldData['text'] = $text;
1390  $fieldData['source_text'] = $text;
1391  $fieldData['text_bytes'] = $content->getSize();
1392  $fieldData['content_model'] = $content->getModel();
1393  }
1394 
1395  $this->getHookRunner()->onSearchDataForIndex( $fieldData, $this, $page, $output, $engine );
1396  return $fieldData;
1397  }
1398 
1410  public function getParserOutputForIndexing( WikiPage $page, ParserCache $cache = null ) {
1411  // TODO: MCR: ContentHandler should be called per slot, not for the whole page.
1412  // See T190066.
1413  $parserOptions = $page->makeParserOptions( 'canonical' );
1414  if ( $cache ) {
1415  $parserOutput = $cache->get( $page, $parserOptions );
1416  }
1417 
1418  if ( empty( $parserOutput ) ) {
1419  $renderer = MediaWikiServices::getInstance()->getRevisionRenderer();
1420  $revisionRecord = $this->latestRevision( $page );
1421  $parserOutput =
1422  $renderer->getRenderedRevision(
1423  $revisionRecord,
1424  $parserOptions
1425  )->getRevisionParserOutput();
1426  if ( $cache ) {
1427  $cache->save( $parserOutput, $page, $parserOptions );
1428  }
1429  }
1430  return $parserOutput;
1431  }
1432 
1433  private function latestRevision( WikiPage $page ): RevisionRecord {
1434  $revRecord = $page->getRevisionRecord();
1435  if ( $revRecord == null ) {
1436  // If the content represents a brand new page it's possible
1437  // we need to fetch it from the master.
1438  $page->loadPageData( WikiPage::READ_LATEST );
1439  $revRecord = $page->getRevisionRecord();
1440  if ( $revRecord == null ) {
1441  $text = $page->getTitle()->getPrefixedText();
1442  throw new MWException(
1443  "No revision could be loaded for page: $text" );
1444  }
1445  }
1446 
1447  return $revRecord;
1448  }
1449 
1482  public function getSecondaryDataUpdates(
1483  Title $title,
1484  Content $content,
1485  $role,
1486  SlotRenderingProvider $slotOutput
1487  ) {
1488  return [];
1489  }
1490 
1521  public function getDeletionUpdates( Title $title, $role ) {
1522  return [];
1523  }
1524 
1525 }
SearchIndexField\INDEX_TYPE_KEYWORD
const INDEX_TYPE_KEYWORD
KEYWORD fields are indexed without any processing, so are appropriate for e.g.
Definition: SearchIndexField.php:24
ContentHandler\getSecondaryDataUpdates
getSecondaryDataUpdates(Title $title, Content $content, $role, SlotRenderingProvider $slotOutput)
Returns a list of DeferrableUpdate objects for recording information about the given Content in some ...
Definition: ContentHandler.php:1482
ContentHandler
A content handler knows how do deal with a specific type of content on a wiki page.
Definition: ContentHandler.php:59
ContentHandler\getForModelID
static getForModelID( $modelId)
Returns the ContentHandler singleton for the given model ID.
Definition: ContentHandler.php:269
Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:46
ContentHandler\getAllContentFormats
static getAllContentFormats()
Definition: ContentHandler.php:332
WikiPage\loadPageData
loadPageData( $from='fromdb')
Load the object from a given source by title.
Definition: WikiPage.php:428
DifferenceEngine\getEngine
static getEngine()
Process DiffEngine config and get a sane, usable engine.
Definition: DifferenceEngine.php:1470
WikiPage\getRevisionRecord
getRevisionRecord()
Get the latest revision.
Definition: WikiPage.php:744
ParserOutput
Definition: ParserOutput.php:25
ContentHandler\supportsDirectEditing
supportsDirectEditing()
Return true if this content model supports direct editing, such as via EditPage.
Definition: ContentHandler.php:1285
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:154
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:37
ContentHandler\getSlotDiffRendererInternal
getSlotDiffRendererInternal(IContextSource $context)
Return the SlotDiffRenderer appropriate for this content handler.
Definition: ContentHandler.php:647
ContentHandler\getActionOverrides
getActionOverrides()
Returns overrides for action handlers.
Definition: ContentHandler.php:569
ContentHandler\checkModelID
checkModelID( $model_id)
Definition: ContentHandler.php:477
ContentHandler\getAutoDeleteReason
getAutoDeleteReason(Title $title, &$hasHistory)
Auto-generates a deletion reason.
Definition: ContentHandler.php:1045
ContentHandler\unserializeContent
unserializeContent( $blob, $format=null)
Unserializes a Content object of the type supported by this ContentHandler.
ContentHandler\getPageViewLanguage
getPageViewLanguage(Title $title, Content $content=null)
Get the language in which the content of this page is written when viewed by user.
Definition: ContentHandler.php:755
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:52
ContentHandler\$mSupportedFormats
string[] $mSupportedFormats
Definition: ContentHandler.php:346
WikiPage\makeParserOptions
makeParserOptions( $context)
Get parser options suitable for rendering the primary article wikitext.
Definition: WikiPage.php:2016
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1219
ContentHandler\getAutosummary
getAutosummary(Content $oldContent=null, Content $newContent=null, $flags=0)
Return an applicable auto-summary if one exists for the given edit.
Definition: ContentHandler.php:920
TextSlotDiffRenderer\ENGINE_EXTERNAL
const ENGINE_EXTERNAL
Use an external executable.
Definition: TextSlotDiffRenderer.php:50
ContentHandler\getDeletionUpdates
getDeletionUpdates(Title $title, $role)
Returns a list of DeferrableUpdate objects for removing information about content in some secondary d...
Definition: ContentHandler.php:1521
ContentHandler\getForTitle
static getForTitle(Title $title)
Returns the appropriate ContentHandler singleton for the given title.
Definition: ContentHandler.php:213
TextSlotDiffRenderer\ENGINE_WIKIDIFF2
const ENGINE_WIKIDIFF2
Use the wikidiff2 PHP module.
Definition: TextSlotDiffRenderer.php:44
SearchIndexField\FLAG_CASEFOLD
const FLAG_CASEFOLD
Generic field flags.
Definition: SearchIndexField.php:45
ContentHandler\getChangeTag
getChangeTag(Content $oldContent=null, Content $newContent=null, $flags=0)
Return an applicable tag if one exists for the given edit or return null.
Definition: ContentHandler.php:1004
$res
$res
Definition: testCompression.php:57
ContentHandler\serializeContent
serializeContent(Content $content, $format=null)
Serializes a Content object of the type supported by this ContentHandler.
$revQuery
$revQuery
Definition: testCompression.php:56
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:988
ContentHandler\supportsSections
supportsSections()
Returns true if this content model supports sections.
Definition: ContentHandler.php:1247
Revision\SlotRenderingProvider
A lazy provider of ParserOutput objects for a revision's individual slots.
Definition: SlotRenderingProvider.php:12
ContentHandler\isSupportedFormat
isSupportedFormat( $format)
Returns true if $format is a serialization format supported by this ContentHandler,...
Definition: ContentHandler.php:529
$dbr
$dbr
Definition: testCompression.php:54
ContentHandler\createDifferenceEngine
createDifferenceEngine(IContextSource $context, $old=0, $new=0, $rcid=0, $refreshCache=false, $unhide=false)
Factory for creating an appropriate DifferenceEngine for this content model.
Definition: ContentHandler.php:600
ContentHandler\canBeUsedOn
canBeUsedOn(Title $title)
Determines whether the content type handled by this ContentHandler can be used for the main slot of t...
Definition: ContentHandler.php:791
Revision
Definition: Revision.php:40
MediaWiki\Search\ParserOutputSearchDataExtractor
Extracts data from ParserOutput for indexing in the search engine.
Definition: ParserOutputSearchDataExtractor.php:29
ContentHandler\importTransform
importTransform( $blob, $format=null)
Apply import transformation (per default, returns $blob unchanged).
Definition: ContentHandler.php:420
ContentHandler\supportsRedirects
supportsRedirects()
Returns true if this content model supports redirects.
Definition: ContentHandler.php:1274
$wgContentHandlerTextFallback
$wgContentHandlerTextFallback
How to react if a plain text version of a non-text Content object is requested using ContentHandler::...
Definition: DefaultSettings.php:9093
ContentHandler\getDefaultModelFor
static getDefaultModelFor(Title $title)
Returns the name of the default content model to be used for the page with the given title.
Definition: ContentHandler.php:194
MWException
MediaWiki exception.
Definition: MWException.php:29
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
Definition: GlobalFunctions.php:1026
MediaWiki\Logger\LoggerFactory
PSR-3 logger instance factory.
Definition: LoggerFactory.php:45
ContentHandler\getLanguageConverter
getLanguageConverter( $language)
Shorthand for getting a Language Converter for specific language.
Definition: ContentHandler.php:835
ContentHandler\getContentModels
static getContentModels()
Definition: ContentHandler.php:320
$blob
$blob
Definition: testCompression.php:70
ContentHandler\supportsDirectApiEditing
supportsDirectApiEditing()
Whether or not this content model supports direct editing via ApiEditPage.
Definition: ContentHandler.php:1296
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2467
$wgLang
$wgLang
Definition: Setup.php:776
WikiPage\getTitle
getTitle()
Get the title object of the article.
Definition: WikiPage.php:281
ContentHandler\getSlotDiffRendererWithOptions
getSlotDiffRendererWithOptions(IContextSource $context, $options=[])
Return the SlotDiffRenderer appropriate for this content handler.
Definition: ContentHandler.php:660
wfGetLangObj
wfGetLangObj( $langcode=false)
Return a Language object from $langcode.
Definition: GlobalFunctions.php:1169
TextSlotDiffRenderer\ENGINE_PHP
const ENGINE_PHP
Use the PHP diff implementation (DiffEngine).
Definition: TextSlotDiffRenderer.php:41
ContentHandler\merge3
merge3(Content $oldContent, Content $myContent, Content $yourContent)
Attempts to merge differences between three versions.
Definition: ContentHandler.php:826
ChangeTags\getSoftwareTags
static getSoftwareTags( $all=false)
Loads defined core tags, checks for invalid types (if not array), and filters for supported and enabl...
Definition: ChangeTags.php:77
ContentHandler\makeEmptyContent
makeEmptyContent()
Creates an empty Content object of the type supported by this ContentHandler.
$title
$title
Definition: testCompression.php:38
ContentHandler\getDataForSearchIndex
getDataForSearchIndex(WikiPage $page, ParserOutput $output, SearchEngine $engine)
Return fields to be indexed by search engine as representation of this document.
Definition: ContentHandler.php:1371
TextSlotDiffRenderer\ENGINE_WIKIDIFF2_INLINE
const ENGINE_WIKIDIFF2_INLINE
Use the wikidiff2 PHP module.
Definition: TextSlotDiffRenderer.php:47
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
ContentHandler\getSlotDiffRenderer
getSlotDiffRenderer(IContextSource $context, array $options=[])
Get an appropriate SlotDiffRenderer for this content model.
Definition: ContentHandler.php:621
ContentHandler\makeContent
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
Definition: ContentHandler.php:142
ContentHandler\getPageLanguage
getPageLanguage(Title $title, Content $content=null)
Get the language in which the content of the given page is written.
Definition: ContentHandler.php:714
ContentHandler\isParserCacheSupported
isParserCacheSupported()
Returns true for content models that support caching using the ParserCache mechanism.
Definition: ContentHandler.php:1232
$content
$content
Definition: router.php:76
ContentHandler\getDefaultFormat
getDefaultFormat()
The format used for serialization/deserialization by default by this ContentHandler.
Definition: ContentHandler.php:511
ILanguageConverter
The shared interface for all language converters.
Definition: ILanguageConverter.php:28
ContentHandler\getChangeType
getChangeType(Content $oldContent=null, Content $newContent=null, $flags=0)
Return type of change if one exists for the given edit.
Definition: ContentHandler.php:852
ContentHandler\cleanupHandlersCache
static cleanupHandlersCache()
Definition: ContentHandler.php:281
ContentHandler\getLocalizedName
static getLocalizedName( $name, Language $lang=null)
Returns the localized name for a given content model.
Definition: ContentHandler.php:299
ContentHandler\latestRevision
latestRevision(WikiPage $page)
Definition: ContentHandler.php:1433
ContentHandler\getDiffEngineClass
getDiffEngineClass()
Returns the name of the diff engine to use.
Definition: ContentHandler.php:807
ContentHandler\supportsCategories
supportsCategories()
Returns true if this content model supports categories.
Definition: ContentHandler.php:1259
ContentHandler\$mModelID
string $mModelID
Definition: ContentHandler.php:341
TextContent
Content object implementation for representing flat text.
Definition: TextContent.php:39
SearchEngine
Contain a class for special pages Stable to extend.
Definition: SearchEngine.php:37
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:55
ContentHandler\exportTransform
exportTransform( $blob, $format=null)
Applies transformations on export (returns the blob unchanged per default).
Definition: ContentHandler.php:390
Content
Base interface for content objects.
Definition: Content.php:35
ContentHandler\getFieldsForSearchIndex
getFieldsForSearchIndex(SearchEngine $engine)
Get fields definition for search index.
Definition: ContentHandler.php:1312
CommentStore\COMMENT_CHARACTER_LIMIT
const COMMENT_CHARACTER_LIMIT
Maximum length of a comment in UTF-8 characters.
Definition: CommentStore.php:48
EDIT_NEW
const EDIT_NEW
Definition: Defines.php:141
SearchEngine\makeSearchFieldMapping
makeSearchFieldMapping( $name, $type)
Create a search field definition.
Definition: SearchEngine.php:774
Title
Represents a title within MediaWiki.
Definition: Title.php:42
ContentHandler\makeRedirectContent
makeRedirectContent(Title $destination, $text='')
Creates a new Content object that acts as a redirect to the given page, or null if redirects are not ...
Definition: ContentHandler.php:453
ContentHandler\checkFormat
checkFormat( $format)
Convenient for checking whether a format provided as a parameter is actually supported.
Definition: ContentHandler.php:544
ContentHandler\getContentText
static getContentText(Content $content=null)
Convenience function for getting flat text from a Content object.
Definition: ContentHandler.php:91
$cache
$cache
Definition: mcc.php:33
DifferenceEngineSlotDiffRenderer
B/C adapter for turning a DifferenceEngine into a SlotDiffRenderer.
Definition: DifferenceEngineSlotDiffRenderer.php:32
ParserCache
Definition: ParserCache.php:32
ContentHandler\addSearchField
addSearchField(&$fields, SearchEngine $engine, $name, $type)
Add new field definition to array.
Definition: ContentHandler.php:1353
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:77
ContentHandler\__construct
__construct( $modelId, $formats)
Constructor, initializing the ContentHandler instance with its model ID and a list of supported forma...
Definition: ContentHandler.php:359
StubObject\unstub
static unstub(&$obj)
Unstubs an object, if it is a stub object.
Definition: StubObject.php:97
ContentHandler\getUndoContent
getUndoContent( $current, $undo, $undoafter, $undoIsLatest=false)
Get the Content object that needs to be saved in order to undo all revisions between $undo and $undoa...
Definition: ContentHandler.php:1163
ContentHandler\getSupportedFormats
getSupportedFormats()
Returns a list of serialization formats supported by the serializeContent() and unserializeContent() ...
Definition: ContentHandler.php:495
WikiPage\getContent
getContent( $audience=RevisionRecord::FOR_PUBLIC, User $user=null)
Get the content of the current revision.
Definition: WikiPage.php:765
Language
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition: Language.php:41
ContentHandler\getModelID
getModelID()
Returns the model id that identifies the content model this ContentHandler can handle.
Definition: ContentHandler.php:465
Revision\SlotRecord
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:39
ContentHandler\getForContent
static getForContent(Content $content)
Returns the appropriate ContentHandler singleton for the given Content object.
Definition: ContentHandler.php:234
ContentHandler\getParserOutputForIndexing
getParserOutputForIndexing(WikiPage $page, ParserCache $cache=null)
Produce page output suitable for indexing.
Definition: ContentHandler.php:1410
TextSlotDiffRenderer
Renders a slot diff by doing a text diff on the native representation.
Definition: TextSlotDiffRenderer.php:38
SearchIndexField\INDEX_TYPE_TEXT
const INDEX_TYPE_TEXT
TEXT fields are suitable for natural language and may be subject to analysis such as stemming.
Definition: SearchIndexField.php:19
$type
$type
Definition: testCompression.php:52