MediaWiki  master
ContentHandler.php
Go to the documentation of this file.
1 <?php
2 
35 
55 abstract class ContentHandler {
85  public static function getContentText( Content $content = null ) {
87 
88  if ( is_null( $content ) ) {
89  return '';
90  }
91 
92  if ( $content instanceof TextContent ) {
93  return $content->getText();
94  }
95 
96  wfDebugLog( 'ContentHandler', 'Accessing ' . $content->getModel() . ' content as text!' );
97 
98  if ( $wgContentHandlerTextFallback == 'fail' ) {
99  throw new MWException(
100  "Attempt to get text from Content with model " .
101  $content->getModel()
102  );
103  }
104 
105  if ( $wgContentHandlerTextFallback == 'serialize' ) {
106  return $content->serialize();
107  }
108 
109  return null;
110  }
111 
135  public static function makeContent( $text, Title $title = null,
136  $modelId = null, $format = null ) {
137  if ( is_null( $modelId ) ) {
138  if ( is_null( $title ) ) {
139  throw new MWException( "Must provide a Title object or a content model ID." );
140  }
141 
142  $modelId = $title->getContentModel();
143  }
144 
145  $handler = self::getForModelID( $modelId );
146 
147  return $handler->unserializeContent( $text, $format );
148  }
149 
186  public static function getDefaultModelFor( Title $title ) {
187  $slotRoleregistry = MediaWikiServices::getInstance()->getSlotRoleRegistry();
188  $mainSlotHandler = $slotRoleregistry->getRoleHandler( 'main' );
189  return $mainSlotHandler->getDefaultModel( $title );
190  }
191 
201  public static function getForTitle( Title $title ) {
202  $modelId = $title->getContentModel();
203 
204  return self::getForModelID( $modelId );
205  }
206 
217  public static function getForContent( Content $content ) {
218  $modelId = $content->getModel();
219 
220  return self::getForModelID( $modelId );
221  }
222 
226  protected static $handlers;
227 
254  public static function getForModelID( $modelId ) {
255  global $wgContentHandlers;
256 
257  if ( isset( self::$handlers[$modelId] ) ) {
258  return self::$handlers[$modelId];
259  }
260 
261  if ( empty( $wgContentHandlers[$modelId] ) ) {
262  $handler = null;
263 
264  Hooks::run( 'ContentHandlerForModelID', [ $modelId, &$handler ] );
265 
266  if ( $handler === null ) {
267  throw new MWUnknownContentModelException( $modelId );
268  }
269 
270  if ( !( $handler instanceof ContentHandler ) ) {
271  throw new MWException( "ContentHandlerForModelID must supply a ContentHandler instance" );
272  }
273  } else {
274  $classOrCallback = $wgContentHandlers[$modelId];
275 
276  if ( is_callable( $classOrCallback ) ) {
277  $handler = call_user_func( $classOrCallback, $modelId );
278  } else {
279  $handler = new $classOrCallback( $modelId );
280  }
281 
282  if ( !( $handler instanceof ContentHandler ) ) {
283  throw new MWException(
284  var_export( $classOrCallback, true ) . " from \$wgContentHandlers is not " .
285  "compatible with ContentHandler"
286  );
287  }
288  }
289 
290  wfDebugLog( 'ContentHandler', 'Created handler for ' . $modelId
291  . ': ' . get_class( $handler ) );
292 
293  self::$handlers[$modelId] = $handler;
294 
295  return self::$handlers[$modelId];
296  }
297 
301  public static function cleanupHandlersCache() {
302  self::$handlers = [];
303  }
304 
318  public static function getLocalizedName( $name, Language $lang = null ) {
319  // Messages: content-model-wikitext, content-model-text,
320  // content-model-javascript, content-model-css
321  $key = "content-model-$name";
322 
323  $msg = wfMessage( $key );
324  if ( $lang ) {
325  $msg->inLanguage( $lang );
326  }
327 
328  return $msg->exists() ? $msg->plain() : $name;
329  }
330 
331  public static function getContentModels() {
332  global $wgContentHandlers;
333 
334  $models = array_keys( $wgContentHandlers );
335  Hooks::run( 'GetContentModels', [ &$models ] );
336  return $models;
337  }
338 
339  public static function getAllContentFormats() {
340  global $wgContentHandlers;
341 
342  $formats = [];
343 
344  foreach ( $wgContentHandlers as $model => $class ) {
345  $handler = self::getForModelID( $model );
346  $formats = array_merge( $formats, $handler->getSupportedFormats() );
347  }
348 
349  $formats = array_unique( $formats );
350 
351  return $formats;
352  }
353 
354  // ------------------------------------------------------------------------
355 
359  protected $mModelID;
360 
365 
375  public function __construct( $modelId, $formats ) {
376  $this->mModelID = $modelId;
377  $this->mSupportedFormats = $formats;
378  }
379 
390  abstract public function serializeContent( Content $content, $format = null );
391 
402  public function exportTransform( $blob, $format = null ) {
403  return $blob;
404  }
405 
416  abstract public function unserializeContent( $blob, $format = null );
417 
429  public function importTransform( $blob, $format = null ) {
430  return $blob;
431  }
432 
441  abstract public function makeEmptyContent();
442 
460  public function makeRedirectContent( Title $destination, $text = '' ) {
461  return null;
462  }
463 
472  public function getModelID() {
473  return $this->mModelID;
474  }
475 
484  protected function checkModelID( $model_id ) {
485  if ( $model_id !== $this->mModelID ) {
486  throw new MWException( "Bad content model: " .
487  "expected {$this->mModelID} " .
488  "but got $model_id." );
489  }
490  }
491 
501  public function getSupportedFormats() {
503  }
504 
516  public function getDefaultFormat() {
517  return $this->mSupportedFormats[0];
518  }
519 
533  public function isSupportedFormat( $format ) {
534  if ( !$format ) {
535  return true; // this means "use the default"
536  }
537 
538  return in_array( $format, $this->mSupportedFormats );
539  }
540 
548  protected function checkFormat( $format ) {
549  if ( !$this->isSupportedFormat( $format ) ) {
550  throw new MWException(
551  "Format $format is not supported for content model "
552  . $this->getModelID()
553  );
554  }
555  }
556 
572  public function getActionOverrides() {
573  return [];
574  }
575 
603  public function createDifferenceEngine( IContextSource $context, $old = 0, $new = 0,
604  $rcid = 0, // FIXME: Deprecated, no longer used
605  $refreshCache = false, $unhide = false
606  ) {
607  $diffEngineClass = $this->getDiffEngineClass();
608  $differenceEngine = new $diffEngineClass( $context, $old, $new, $rcid, $refreshCache, $unhide );
609  Hooks::run( 'GetDifferenceEngine', [ $context, $old, $new, $refreshCache, $unhide,
610  &$differenceEngine ] );
611  return $differenceEngine;
612  }
613 
620  final public function getSlotDiffRenderer( IContextSource $context ) {
621  $slotDiffRenderer = $this->getSlotDiffRendererInternal( $context );
622  if ( get_class( $slotDiffRenderer ) === TextSlotDiffRenderer::class ) {
623  // To keep B/C, when SlotDiffRenderer is not overridden for a given content type
624  // but DifferenceEngine is, use that instead.
625  $differenceEngine = $this->createDifferenceEngine( $context );
626  if ( get_class( $differenceEngine ) !== DifferenceEngine::class ) {
627  // TODO turn this into a deprecation warning in a later release
628  LoggerFactory::getInstance( 'diff' )->info(
629  'Falling back to DifferenceEngineSlotDiffRenderer', [
630  'modelID' => $this->getModelID(),
631  'DifferenceEngine' => get_class( $differenceEngine ),
632  ] );
633  $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
634  }
635  }
636  Hooks::run( 'GetSlotDiffRenderer', [ $this, &$slotDiffRenderer, $context ] );
637  return $slotDiffRenderer;
638  }
639 
646  $contentLanguage = MediaWikiServices::getInstance()->getContentLanguage();
647  $statsdDataFactory = MediaWikiServices::getInstance()->getStatsdDataFactory();
648  $slotDiffRenderer = new TextSlotDiffRenderer();
649  $slotDiffRenderer->setStatsdDataFactory( $statsdDataFactory );
650  // XXX using the page language would be better, but it's unclear how that should be injected
651  $slotDiffRenderer->setLanguage( $contentLanguage );
652 
653  $engine = DifferenceEngine::getEngine();
654  if ( $engine === false ) {
655  $slotDiffRenderer->setEngine( TextSlotDiffRenderer::ENGINE_PHP );
656  } elseif ( $engine === 'wikidiff2' ) {
657  $slotDiffRenderer->setEngine( TextSlotDiffRenderer::ENGINE_WIKIDIFF2 );
658  } else {
659  $slotDiffRenderer->setEngine( TextSlotDiffRenderer::ENGINE_EXTERNAL, $engine );
660  }
661 
662  return $slotDiffRenderer;
663  }
664 
684  public function getPageLanguage( Title $title, Content $content = null ) {
685  global $wgLang;
686  $pageLang = MediaWikiServices::getInstance()->getContentLanguage();
687 
688  if ( $title->inNamespace( NS_MEDIAWIKI ) ) {
689  // Parse mediawiki messages with correct target language
690  list( /* $unused */, $lang ) = MessageCache::singleton()->figureMessage( $title->getText() );
691  $pageLang = Language::factory( $lang );
692  }
693 
694  // Simplify hook handlers by only passing objects of one type, in case nothing
695  // else has unstubbed the StubUserLang object by now.
696  StubObject::unstub( $wgLang );
697 
698  Hooks::run( 'PageContentLanguage', [ $title, &$pageLang, $wgLang ] );
699 
700  return wfGetLangObj( $pageLang );
701  }
702 
723  public function getPageViewLanguage( Title $title, Content $content = null ) {
724  $pageLang = $this->getPageLanguage( $title, $content );
725 
726  if ( $title->getNamespace() !== NS_MEDIAWIKI ) {
727  // If the user chooses a variant, the content is actually
728  // in a language whose code is the variant code.
729  $variant = $pageLang->getPreferredVariant();
730  if ( $pageLang->getCode() !== $variant ) {
731  $pageLang = Language::factory( $variant );
732  }
733  }
734 
735  return $pageLang;
736  }
737 
756  public function canBeUsedOn( Title $title ) {
757  $ok = true;
758 
759  Hooks::run( 'ContentModelCanBeUsedOn', [ $this->getModelID(), $title, &$ok ] );
760 
761  return $ok;
762  }
763 
771  protected function getDiffEngineClass() {
772  return DifferenceEngine::class;
773  }
774 
789  public function merge3( Content $oldContent, Content $myContent, Content $yourContent ) {
790  return false;
791  }
792 
804  private function getChangeType(
805  Content $oldContent = null,
806  Content $newContent = null,
807  $flags = 0
808  ) {
809  $oldTarget = $oldContent !== null ? $oldContent->getRedirectTarget() : null;
810  $newTarget = $newContent !== null ? $newContent->getRedirectTarget() : null;
811 
812  // We check for the type of change in the given edit, and return string key accordingly
813 
814  // Blanking of a page
815  if ( $oldContent && $oldContent->getSize() > 0 &&
816  $newContent && $newContent->getSize() === 0
817  ) {
818  return 'blank';
819  }
820 
821  // Redirects
822  if ( $newTarget ) {
823  if ( !$oldTarget ) {
824  // New redirect page (by creating new page or by changing content page)
825  return 'new-redirect';
826  } elseif ( !$newTarget->equals( $oldTarget ) ||
827  $oldTarget->getFragment() !== $newTarget->getFragment()
828  ) {
829  // Redirect target changed
830  return 'changed-redirect-target';
831  }
832  } elseif ( $oldTarget ) {
833  // Changing an existing redirect into a non-redirect
834  return 'removed-redirect';
835  }
836 
837  // New page created
838  if ( $flags & EDIT_NEW && $newContent ) {
839  if ( $newContent->getSize() === 0 ) {
840  // New blank page
841  return 'newblank';
842  } else {
843  return 'newpage';
844  }
845  }
846 
847  // Removing more than 90% of the page
848  if ( $oldContent && $newContent && $oldContent->getSize() > 10 * $newContent->getSize() ) {
849  return 'replace';
850  }
851 
852  // Content model changed
853  if ( $oldContent && $newContent && $oldContent->getModel() !== $newContent->getModel() ) {
854  return 'contentmodelchange';
855  }
856 
857  return null;
858  }
859 
871  public function getAutosummary(
872  Content $oldContent = null,
873  Content $newContent = null,
874  $flags = 0
875  ) {
876  $changeType = $this->getChangeType( $oldContent, $newContent, $flags );
877 
878  // There's no applicable auto-summary for our case, so our auto-summary is empty.
879  if ( !$changeType ) {
880  return '';
881  }
882 
883  // Decide what kind of auto-summary is needed.
884  switch ( $changeType ) {
885  case 'new-redirect':
886  $newTarget = $newContent->getRedirectTarget();
887  $truncatedtext = $newContent->getTextForSummary(
888  250
889  - strlen( wfMessage( 'autoredircomment' )->inContentLanguage()->text() )
890  - strlen( $newTarget->getFullText() )
891  );
892 
893  return wfMessage( 'autoredircomment', $newTarget->getFullText() )
894  ->plaintextParams( $truncatedtext )->inContentLanguage()->text();
895  case 'changed-redirect-target':
896  $oldTarget = $oldContent->getRedirectTarget();
897  $newTarget = $newContent->getRedirectTarget();
898 
899  $truncatedtext = $newContent->getTextForSummary(
900  250
901  - strlen( wfMessage( 'autosumm-changed-redirect-target' )
902  ->inContentLanguage()->text() )
903  - strlen( $oldTarget->getFullText() )
904  - strlen( $newTarget->getFullText() )
905  );
906 
907  return wfMessage( 'autosumm-changed-redirect-target',
908  $oldTarget->getFullText(),
909  $newTarget->getFullText() )
910  ->rawParams( $truncatedtext )->inContentLanguage()->text();
911  case 'removed-redirect':
912  $oldTarget = $oldContent->getRedirectTarget();
913  $truncatedtext = $newContent->getTextForSummary(
914  250
915  - strlen( wfMessage( 'autosumm-removed-redirect' )
916  ->inContentLanguage()->text() )
917  - strlen( $oldTarget->getFullText() ) );
918 
919  return wfMessage( 'autosumm-removed-redirect', $oldTarget->getFullText() )
920  ->rawParams( $truncatedtext )->inContentLanguage()->text();
921  case 'newpage':
922  // If they're making a new article, give its text, truncated, in the summary.
923  $truncatedtext = $newContent->getTextForSummary(
924  200 - strlen( wfMessage( 'autosumm-new' )->inContentLanguage()->text() ) );
925 
926  return wfMessage( 'autosumm-new' )->rawParams( $truncatedtext )
927  ->inContentLanguage()->text();
928  case 'blank':
929  return wfMessage( 'autosumm-blank' )->inContentLanguage()->text();
930  case 'replace':
931  $truncatedtext = $newContent->getTextForSummary(
932  200 - strlen( wfMessage( 'autosumm-replace' )->inContentLanguage()->text() ) );
933 
934  return wfMessage( 'autosumm-replace' )->rawParams( $truncatedtext )
935  ->inContentLanguage()->text();
936  case 'newblank':
937  return wfMessage( 'autosumm-newblank' )->inContentLanguage()->text();
938  default:
939  return '';
940  }
941  }
942 
954  public function getChangeTag(
955  Content $oldContent = null,
956  Content $newContent = null,
957  $flags = 0
958  ) {
959  $changeType = $this->getChangeType( $oldContent, $newContent, $flags );
960 
961  // There's no applicable tag for this change.
962  if ( !$changeType ) {
963  return null;
964  }
965 
966  // Core tags use the same keys as ones returned from $this->getChangeType()
967  // but prefixed with pseudo namespace 'mw-', so we add the prefix before checking
968  // if this type of change should be tagged
969  $tag = 'mw-' . $changeType;
970 
971  // Not all change types are tagged, so we check against the list of defined tags.
972  if ( in_array( $tag, ChangeTags::getSoftwareTags() ) ) {
973  return $tag;
974  }
975 
976  return null;
977  }
978 
994  public function getAutoDeleteReason( Title $title, &$hasHistory ) {
995  $dbr = wfGetDB( DB_REPLICA );
996 
997  // Get the last revision
998  $rev = Revision::newFromTitle( $title );
999 
1000  if ( is_null( $rev ) ) {
1001  return false;
1002  }
1003 
1004  // Get the article's contents
1005  $content = $rev->getContent();
1006  $blank = false;
1007 
1008  // If the page is blank, use the text from the previous revision,
1009  // which can only be blank if there's a move/import/protect dummy
1010  // revision involved
1011  if ( !$content || $content->isEmpty() ) {
1012  $prev = $rev->getPrevious();
1013 
1014  if ( $prev ) {
1015  $rev = $prev;
1016  $content = $rev->getContent();
1017  $blank = true;
1018  }
1019  }
1020 
1021  $this->checkModelID( $rev->getContentModel() );
1022 
1023  // Find out if there was only one contributor
1024  // Only scan the last 20 revisions
1026  $res = $dbr->select(
1027  $revQuery['tables'],
1028  [ 'rev_user_text' => $revQuery['fields']['rev_user_text'] ],
1029  [
1030  'rev_page' => $title->getArticleID(),
1031  $dbr->bitAnd( 'rev_deleted', RevisionRecord::DELETED_USER ) . ' = 0'
1032  ],
1033  __METHOD__,
1034  [ 'LIMIT' => 20 ],
1035  $revQuery['joins']
1036  );
1037 
1038  if ( $res === false ) {
1039  // This page has no revisions, which is very weird
1040  return false;
1041  }
1042 
1043  $hasHistory = ( $res->numRows() > 1 );
1044  $row = $dbr->fetchObject( $res );
1045 
1046  if ( $row ) { // $row is false if the only contributor is hidden
1047  $onlyAuthor = $row->rev_user_text;
1048  // Try to find a second contributor
1049  foreach ( $res as $row ) {
1050  if ( $row->rev_user_text != $onlyAuthor ) { // T24999
1051  $onlyAuthor = false;
1052  break;
1053  }
1054  }
1055  } else {
1056  $onlyAuthor = false;
1057  }
1058 
1059  // Generate the summary with a '$1' placeholder
1060  if ( $blank ) {
1061  // The current revision is blank and the one before is also
1062  // blank. It's just not our lucky day
1063  $reason = wfMessage( 'exbeforeblank', '$1' )->inContentLanguage()->text();
1064  } else {
1065  if ( $onlyAuthor ) {
1066  $reason = wfMessage(
1067  'excontentauthor',
1068  '$1',
1069  $onlyAuthor
1070  )->inContentLanguage()->text();
1071  } else {
1072  $reason = wfMessage( 'excontent', '$1' )->inContentLanguage()->text();
1073  }
1074  }
1075 
1076  if ( $reason == '-' ) {
1077  // Allow these UI messages to be blanked out cleanly
1078  return '';
1079  }
1080 
1081  // Max content length = max comment length - length of the comment (excl. $1)
1082  $maxLength = CommentStore::COMMENT_CHARACTER_LIMIT - ( strlen( $reason ) - 2 );
1083  $text = $content ? $content->getTextForSummary( $maxLength ) : '';
1084 
1085  // Now replace the '$1' placeholder
1086  $reason = str_replace( '$1', $text, $reason );
1087 
1088  return $reason;
1089  }
1090 
1107  public function getUndoContent( $current, $undo, $undoafter, $undoIsLatest = false ) {
1108  Assert::parameterType( Revision::class . '|' . Content::class, $current, '$current' );
1109  if ( $current instanceof Content ) {
1110  Assert::parameter( $undo instanceof Content, '$undo',
1111  'Must be Content when $current is Content' );
1112  Assert::parameter( $undoafter instanceof Content, '$undoafter',
1113  'Must be Content when $current is Content' );
1114  $cur_content = $current;
1115  $undo_content = $undo;
1116  $undoafter_content = $undoafter;
1117  } else {
1118  Assert::parameter( $undo instanceof Revision, '$undo',
1119  'Must be Revision when $current is Revision' );
1120  Assert::parameter( $undoafter instanceof Revision, '$undoafter',
1121  'Must be Revision when $current is Revision' );
1122 
1123  $cur_content = $current->getContent();
1124 
1125  if ( empty( $cur_content ) ) {
1126  return false; // no page
1127  }
1128 
1129  $undo_content = $undo->getContent();
1130  $undoafter_content = $undoafter->getContent();
1131 
1132  if ( !$undo_content || !$undoafter_content ) {
1133  return false; // no content to undo
1134  }
1135 
1136  $undoIsLatest = $current->getId() === $undo->getId();
1137  }
1138 
1139  try {
1140  $this->checkModelID( $cur_content->getModel() );
1141  $this->checkModelID( $undo_content->getModel() );
1142  if ( !$undoIsLatest ) {
1143  // If we are undoing the most recent revision,
1144  // its ok to revert content model changes. However
1145  // if we are undoing a revision in the middle, then
1146  // doing that will be confusing.
1147  $this->checkModelID( $undoafter_content->getModel() );
1148  }
1149  } catch ( MWException $e ) {
1150  // If the revisions have different content models
1151  // just return false
1152  return false;
1153  }
1154 
1155  if ( $cur_content->equals( $undo_content ) ) {
1156  // No use doing a merge if it's just a straight revert.
1157  return $undoafter_content;
1158  }
1159 
1160  $undone_content = $this->merge3( $undo_content, $undoafter_content, $cur_content );
1161 
1162  return $undone_content;
1163  }
1164 
1181  public function makeParserOptions( $context ) {
1182  wfDeprecated( __METHOD__, '1.32' );
1184  }
1185 
1194  public function isParserCacheSupported() {
1195  return false;
1196  }
1197 
1207  public function supportsSections() {
1208  return false;
1209  }
1210 
1217  public function supportsCategories() {
1218  return true;
1219  }
1220 
1230  public function supportsRedirects() {
1231  return false;
1232  }
1233 
1239  public function supportsDirectEditing() {
1240  return false;
1241  }
1242 
1248  public function supportsDirectApiEditing() {
1249  return $this->supportsDirectEditing();
1250  }
1251 
1262  public function getFieldsForSearchIndex( SearchEngine $engine ) {
1263  $fields = [];
1264  $fields['category'] = $engine->makeSearchFieldMapping(
1265  'category',
1267  );
1268  $fields['category']->setFlag( SearchIndexField::FLAG_CASEFOLD );
1269 
1270  $fields['external_link'] = $engine->makeSearchFieldMapping(
1271  'external_link',
1273  );
1274 
1275  $fields['outgoing_link'] = $engine->makeSearchFieldMapping(
1276  'outgoing_link',
1278  );
1279 
1280  $fields['template'] = $engine->makeSearchFieldMapping(
1281  'template',
1283  );
1284  $fields['template']->setFlag( SearchIndexField::FLAG_CASEFOLD );
1285 
1286  $fields['content_model'] = $engine->makeSearchFieldMapping(
1287  'content_model',
1289  );
1290 
1291  return $fields;
1292  }
1293 
1303  protected function addSearchField( &$fields, SearchEngine $engine, $name, $type ) {
1304  $fields[$name] = $engine->makeSearchFieldMapping( $name, $type );
1305  return $fields;
1306  }
1307 
1319  public function getDataForSearchIndex(
1320  WikiPage $page,
1321  ParserOutput $output,
1322  SearchEngine $engine
1323  ) {
1324  $fieldData = [];
1325  $content = $page->getContent();
1326 
1327  if ( $content ) {
1328  $searchDataExtractor = new ParserOutputSearchDataExtractor();
1329 
1330  $fieldData['category'] = $searchDataExtractor->getCategories( $output );
1331  $fieldData['external_link'] = $searchDataExtractor->getExternalLinks( $output );
1332  $fieldData['outgoing_link'] = $searchDataExtractor->getOutgoingLinks( $output );
1333  $fieldData['template'] = $searchDataExtractor->getTemplates( $output );
1334 
1335  $text = $content->getTextForSearchIndex();
1336 
1337  $fieldData['text'] = $text;
1338  $fieldData['source_text'] = $text;
1339  $fieldData['text_bytes'] = $content->getSize();
1340  $fieldData['content_model'] = $content->getModel();
1341  }
1342 
1343  Hooks::run( 'SearchDataForIndex', [ &$fieldData, $this, $page, $output, $engine ] );
1344  return $fieldData;
1345  }
1346 
1356  public function getParserOutputForIndexing( WikiPage $page, ParserCache $cache = null ) {
1357  // TODO: MCR: ContentHandler should be called per slot, not for the whole page.
1358  // See T190066.
1359  $parserOptions = $page->makeParserOptions( 'canonical' );
1360  if ( $cache ) {
1361  $parserOutput = $cache->get( $page, $parserOptions );
1362  }
1363 
1364  if ( empty( $parserOutput ) ) {
1365  $renderer = MediaWikiServices::getInstance()->getRevisionRenderer();
1366  $parserOutput =
1367  $renderer->getRenderedRevision(
1368  $page->getRevision()->getRevisionRecord(),
1369  $parserOptions
1370  )->getRevisionParserOutput();
1371  if ( $cache ) {
1372  $cache->save( $parserOutput, $page, $parserOptions );
1373  }
1374  }
1375  return $parserOutput;
1376  }
1377 
1408  public function getSecondaryDataUpdates(
1409  Title $title,
1410  Content $content,
1411  $role,
1412  SlotRenderingProvider $slotOutput
1413  ) {
1414  return [];
1415  }
1416 
1445  public function getDeletionUpdates( Title $title, $role ) {
1446  return [];
1447  }
1448 
1449 }
merge3(Content $oldContent, Content $myContent, Content $yourContent)
Attempts to merge differences between three versions.
getAutoDeleteReason(Title $title, &$hasHistory)
Auto-generates a deletion reason.
supportsSections()
Returns true if this content model supports sections.
getArticleID( $flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition: Title.php:3123
getSecondaryDataUpdates(Title $title, Content $content, $role, SlotRenderingProvider $slotOutput)
Returns a list of DeferrableUpdate objects for recording information about the given Content in some ...
getChangeTag(Content $oldContent=null, Content $newContent=null, $flags=0)
Return an applicable tag if one exists for the given edit or return null.
$context
Definition: load.php:45
getAutosummary(Content $oldContent=null, Content $newContent=null, $flags=0)
Return an applicable auto-summary if one exists for the given edit.
getText()
Get the text form (spaces not underscores) of the main part.
Definition: Title.php:995
supportsRedirects()
Returns true if this content model supports redirects.
A lazy provider of ParserOutput objects for a revision&#39;s individual slots.
static getAllContentFormats()
static array $handlers
A Cache of ContentHandler instances by model id.
unserializeContent( $blob, $format=null)
Unserializes a Content object of the type supported by this ContentHandler.
static cleanupHandlersCache()
Clean up handlers cache.
if(!isset( $args[0])) $lang
static getContentModels()
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
static getDefaultModelFor(Title $title)
Returns the name of the default content model to be used for the page with the given title...
importTransform( $blob, $format=null)
Apply import transformation (per default, returns $blob unchanged).
getModelID()
Returns the model id that identifies the content model this ContentHandler can handle.
inNamespace( $ns)
Returns true if the title is inside the specified namespace.
Definition: Title.php:1261
const COMMENT_CHARACTER_LIMIT
Maximum length of a comment in UTF-8 characters.
getTextForSearchIndex()
getParserOutputForIndexing(WikiPage $page, ParserCache $cache=null)
Produce page output suitable for indexing.
static getLocalizedName( $name, Language $lang=null)
Returns the localized name for a given content model.
B/C adapter for turning a DifferenceEngine into a SlotDiffRenderer.
getPageLanguage(Title $title, Content $content=null)
Get the language in which the content of the given page is written.
static newFromTitle(LinkTarget $linkTarget, $id=0, $flags=0)
Load either the current, or a specified, revision that&#39;s attached to a given link target...
Definition: Revision.php:138
$wgContentHandlerTextFallback
How to react if a plain text version of a non-text Content object is requested using ContentHandler::...
wfGetLangObj( $langcode=false)
Return a Language object from $langcode.
static newCanonical( $context=null, $userLang=null)
Creates a "canonical" ParserOptions object.
getSize()
Returns the content&#39;s nominal size in "bogo-bytes".
checkModelID( $model_id)
getRevision()
Get the latest revision.
Definition: WikiPage.php:787
exportTransform( $blob, $format=null)
Applies transformations on export (returns the blob unchanged per default).
const INDEX_TYPE_TEXT
TEXT fields are suitable for natural language and may be subject to analysis such as stemming...
getDeletionUpdates(Title $title, $role)
Returns a list of DeferrableUpdate objects for removing information about content in some secondary d...
Content object implementation for representing flat text.
Definition: TextContent.php:37
__construct( $modelId, $formats)
Constructor, initializing the ContentHandler instance with its model ID and a list of supported forma...
$wgLang
Definition: Setup.php:871
static getForModelID( $modelId)
Returns the ContentHandler singleton for the given model ID.
Renders a slot diff by doing a text diff on the native representation.
Extracts data from ParserOutput for indexing in the search engine.
getActionOverrides()
Returns overrides for action handlers.
static getForContent(Content $content)
Returns the appropriate ContentHandler singleton for the given Content object.
static getContentText(Content $content=null)
Convenience function for getting flat text from a Content object.
getDiffEngineClass()
Returns the name of the diff engine to use.
isEmpty()
Returns true if this Content object represents empty content.
static getEngine()
Process $wgExternalDiffEngine and get a sane, usable engine.
getSlotDiffRenderer(IContextSource $context)
Get an appropriate SlotDiffRenderer for this content model.
makeRedirectContent(Title $destination, $text='')
Creates a new Content object that acts as a redirect to the given page, or null if redirects are not ...
$cache
Definition: mcc.php:33
static getSoftwareTags( $all=false)
Loads defined core tags, checks for invalid types (if not array), and filters for supported and enabl...
Definition: ChangeTags.php:58
makeSearchFieldMapping( $name, $type)
Create a search field definition.
makeEmptyContent()
Creates an empty Content object of the type supported by this ContentHandler.
const FLAG_CASEFOLD
Generic field flags.
static getQueryInfo( $options=[])
Return the tables, fields, and join conditions to be selected to create a new revision object...
Definition: Revision.php:315
static factory( $code)
Get a cached or new language object for a given language code.
Definition: Language.php:218
getNamespace()
Get the namespace index, i.e.
Definition: Title.php:1037
static getForTitle(Title $title)
Returns the appropriate ContentHandler singleton for the given title.
const NS_MEDIAWIKI
Definition: Defines.php:68
isSupportedFormat( $format)
Returns true if $format is a serialization format supported by this ContentHandler, and false otherwise.
supportsDirectApiEditing()
Whether or not this content model supports direct editing via ApiEditPage.
const ENGINE_PHP
Use the PHP diff implementation (DiffEngine).
canBeUsedOn(Title $title)
Determines whether the content type handled by this ContentHandler can be used for the main slot of t...
makeParserOptions( $context)
Get parser options suitable for rendering the primary article wikitext.
Definition: WikiPage.php:1961
static unstub(&$obj)
Unstubs an object, if it is a stub object.
Definition: StubObject.php:93
addSearchField(&$fields, SearchEngine $engine, $name, $type)
Add new field definition to array.
checkFormat( $format)
Convenient for checking whether a format provided as a parameter is actually supported.
const INDEX_TYPE_KEYWORD
KEYWORD fields are indexed without any processing, so are appropriate for e.g.
string [] $mSupportedFormats
const ENGINE_WIKIDIFF2
Use the wikidiff2 PHP module.
const EDIT_NEW
Definition: Defines.php:132
getContentModel( $flags=0)
Get the page&#39;s content model id, see the CONTENT_MODEL_XXX constants.
Definition: Title.php:1049
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not...
$wgContentHandlers
Plugins for page content model handling.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
$revQuery
serializeContent(Content $content, $format=null)
Serializes a Content object of the type supported by this ContentHandler.
getContent( $audience=RevisionRecord::FOR_PUBLIC, User $user=null)
Get the content of the current revision.
Definition: WikiPage.php:820
isParserCacheSupported()
Returns true for content models that support caching using the ParserCache mechanism.
const ENGINE_EXTERNAL
Use an external executable.
getSlotDiffRendererInternal(IContextSource $context)
Return the SlotDiffRenderer appropriate for this content handler.
getTextForSummary( $maxLength=250)
Returns a textual representation of the content suitable for use in edit summaries and log messages...
const DB_REPLICA
Definition: defines.php:25
createDifferenceEngine(IContextSource $context, $old=0, $new=0, $rcid=0, $refreshCache=false, $unhide=false)
Factory for creating an appropriate DifferenceEngine for this content model.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
getSupportedFormats()
Returns a list of serialization formats supported by the serializeContent() and unserializeContent() ...
getModel()
Returns the ID of the content model used by this Content object.
supportsDirectEditing()
Return true if this content model supports direct editing, such as via EditPage.
getDataForSearchIndex(WikiPage $page, ParserOutput $output, SearchEngine $engine)
Return fields to be indexed by search engine as representation of this document.
supportsCategories()
Returns true if this content model supports categories.
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
getChangeType(Content $oldContent=null, Content $newContent=null, $flags=0)
Return type of change if one exists for the given edit.
getFieldsForSearchIndex(SearchEngine $engine)
Get fields definition for search index.
makeParserOptions( $context)
Get parser options suitable for rendering and caching the article.
getPageViewLanguage(Title $title, Content $content=null)
Get the language in which the content of this page is written when viewed by user.
getDefaultFormat()
The format used for serialization/deserialization by default by this ContentHandler.
static singleton()
Get the singleton instance of this class.
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
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...