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 === 'php' ) {
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 = MediaWikiServices::getInstance()->getLanguageFactory()
692  ->getLanguage( $lang );
693  }
694 
695  // Simplify hook handlers by only passing objects of one type, in case nothing
696  // else has unstubbed the StubUserLang object by now.
697  StubObject::unstub( $wgLang );
698 
699  Hooks::run( 'PageContentLanguage', [ $title, &$pageLang, $wgLang ] );
700 
701  return wfGetLangObj( $pageLang );
702  }
703 
724  public function getPageViewLanguage( Title $title, Content $content = null ) {
725  $pageLang = $this->getPageLanguage( $title, $content );
726 
727  if ( $title->getNamespace() !== NS_MEDIAWIKI ) {
728  // If the user chooses a variant, the content is actually
729  // in a language whose code is the variant code.
730  $variant = $pageLang->getPreferredVariant();
731  if ( $pageLang->getCode() !== $variant ) {
732  $pageLang = MediaWikiServices::getInstance()->getLanguageFactory()
733  ->getLanguage( $variant );
734  }
735  }
736 
737  return $pageLang;
738  }
739 
758  public function canBeUsedOn( Title $title ) {
759  $ok = true;
760 
761  Hooks::run( 'ContentModelCanBeUsedOn', [ $this->getModelID(), $title, &$ok ] );
762 
763  return $ok;
764  }
765 
773  protected function getDiffEngineClass() {
774  return DifferenceEngine::class;
775  }
776 
791  public function merge3( Content $oldContent, Content $myContent, Content $yourContent ) {
792  return false;
793  }
794 
806  private function getChangeType(
807  Content $oldContent = null,
808  Content $newContent = null,
809  $flags = 0
810  ) {
811  $oldTarget = $oldContent !== null ? $oldContent->getRedirectTarget() : null;
812  $newTarget = $newContent !== null ? $newContent->getRedirectTarget() : null;
813 
814  // We check for the type of change in the given edit, and return string key accordingly
815 
816  // Blanking of a page
817  if ( $oldContent && $oldContent->getSize() > 0 &&
818  $newContent && $newContent->getSize() === 0
819  ) {
820  return 'blank';
821  }
822 
823  // Redirects
824  if ( $newTarget ) {
825  if ( !$oldTarget ) {
826  // New redirect page (by creating new page or by changing content page)
827  return 'new-redirect';
828  } elseif ( !$newTarget->equals( $oldTarget ) ||
829  $oldTarget->getFragment() !== $newTarget->getFragment()
830  ) {
831  // Redirect target changed
832  return 'changed-redirect-target';
833  }
834  } elseif ( $oldTarget ) {
835  // Changing an existing redirect into a non-redirect
836  return 'removed-redirect';
837  }
838 
839  // New page created
840  if ( $flags & EDIT_NEW && $newContent ) {
841  if ( $newContent->getSize() === 0 ) {
842  // New blank page
843  return 'newblank';
844  } else {
845  return 'newpage';
846  }
847  }
848 
849  // Removing more than 90% of the page
850  if ( $oldContent && $newContent && $oldContent->getSize() > 10 * $newContent->getSize() ) {
851  return 'replace';
852  }
853 
854  // Content model changed
855  if ( $oldContent && $newContent && $oldContent->getModel() !== $newContent->getModel() ) {
856  return 'contentmodelchange';
857  }
858 
859  return null;
860  }
861 
873  public function getAutosummary(
874  Content $oldContent = null,
875  Content $newContent = null,
876  $flags = 0
877  ) {
878  $changeType = $this->getChangeType( $oldContent, $newContent, $flags );
879 
880  // There's no applicable auto-summary for our case, so our auto-summary is empty.
881  if ( !$changeType ) {
882  return '';
883  }
884 
885  // Decide what kind of auto-summary is needed.
886  switch ( $changeType ) {
887  case 'new-redirect':
888  $newTarget = $newContent->getRedirectTarget();
889  $truncatedtext = $newContent->getTextForSummary(
890  250
891  - strlen( wfMessage( 'autoredircomment' )->inContentLanguage()->text() )
892  - strlen( $newTarget->getFullText() )
893  );
894 
895  return wfMessage( 'autoredircomment', $newTarget->getFullText() )
896  ->plaintextParams( $truncatedtext )->inContentLanguage()->text();
897  case 'changed-redirect-target':
898  $oldTarget = $oldContent->getRedirectTarget();
899  $newTarget = $newContent->getRedirectTarget();
900 
901  $truncatedtext = $newContent->getTextForSummary(
902  250
903  - strlen( wfMessage( 'autosumm-changed-redirect-target' )
904  ->inContentLanguage()->text() )
905  - strlen( $oldTarget->getFullText() )
906  - strlen( $newTarget->getFullText() )
907  );
908 
909  return wfMessage( 'autosumm-changed-redirect-target',
910  $oldTarget->getFullText(),
911  $newTarget->getFullText() )
912  ->rawParams( $truncatedtext )->inContentLanguage()->text();
913  case 'removed-redirect':
914  $oldTarget = $oldContent->getRedirectTarget();
915  $truncatedtext = $newContent->getTextForSummary(
916  250
917  - strlen( wfMessage( 'autosumm-removed-redirect' )
918  ->inContentLanguage()->text() )
919  - strlen( $oldTarget->getFullText() ) );
920 
921  return wfMessage( 'autosumm-removed-redirect', $oldTarget->getFullText() )
922  ->rawParams( $truncatedtext )->inContentLanguage()->text();
923  case 'newpage':
924  // If they're making a new article, give its text, truncated, in the summary.
925  $truncatedtext = $newContent->getTextForSummary(
926  200 - strlen( wfMessage( 'autosumm-new' )->inContentLanguage()->text() ) );
927 
928  return wfMessage( 'autosumm-new' )->rawParams( $truncatedtext )
929  ->inContentLanguage()->text();
930  case 'blank':
931  return wfMessage( 'autosumm-blank' )->inContentLanguage()->text();
932  case 'replace':
933  $truncatedtext = $newContent->getTextForSummary(
934  200 - strlen( wfMessage( 'autosumm-replace' )->inContentLanguage()->text() ) );
935 
936  return wfMessage( 'autosumm-replace' )->rawParams( $truncatedtext )
937  ->inContentLanguage()->text();
938  case 'newblank':
939  return wfMessage( 'autosumm-newblank' )->inContentLanguage()->text();
940  default:
941  return '';
942  }
943  }
944 
956  public function getChangeTag(
957  Content $oldContent = null,
958  Content $newContent = null,
959  $flags = 0
960  ) {
961  $changeType = $this->getChangeType( $oldContent, $newContent, $flags );
962 
963  // There's no applicable tag for this change.
964  if ( !$changeType ) {
965  return null;
966  }
967 
968  // Core tags use the same keys as ones returned from $this->getChangeType()
969  // but prefixed with pseudo namespace 'mw-', so we add the prefix before checking
970  // if this type of change should be tagged
971  $tag = 'mw-' . $changeType;
972 
973  // Not all change types are tagged, so we check against the list of defined tags.
974  if ( in_array( $tag, ChangeTags::getSoftwareTags() ) ) {
975  return $tag;
976  }
977 
978  return null;
979  }
980 
996  public function getAutoDeleteReason( Title $title, &$hasHistory ) {
997  $dbr = wfGetDB( DB_REPLICA );
998 
999  // Get the last revision
1000  $rev = Revision::newFromTitle( $title );
1001 
1002  if ( is_null( $rev ) ) {
1003  return false;
1004  }
1005 
1006  // Get the article's contents
1007  $content = $rev->getContent();
1008  $blank = false;
1009 
1010  // If the page is blank, use the text from the previous revision,
1011  // which can only be blank if there's a move/import/protect dummy
1012  // revision involved
1013  if ( !$content || $content->isEmpty() ) {
1014  $prev = $rev->getPrevious();
1015 
1016  if ( $prev ) {
1017  $rev = $prev;
1018  $content = $rev->getContent();
1019  $blank = true;
1020  }
1021  }
1022 
1023  $this->checkModelID( $rev->getContentModel() );
1024 
1025  // Find out if there was only one contributor
1026  // Only scan the last 20 revisions
1028  $res = $dbr->select(
1029  $revQuery['tables'],
1030  [ 'rev_user_text' => $revQuery['fields']['rev_user_text'] ],
1031  [
1032  'rev_page' => $title->getArticleID(),
1033  $dbr->bitAnd( 'rev_deleted', RevisionRecord::DELETED_USER ) . ' = 0'
1034  ],
1035  __METHOD__,
1036  [ 'LIMIT' => 20 ],
1037  $revQuery['joins']
1038  );
1039 
1040  if ( $res === false ) {
1041  // This page has no revisions, which is very weird
1042  return false;
1043  }
1044 
1045  $hasHistory = ( $res->numRows() > 1 );
1046  $row = $dbr->fetchObject( $res );
1047 
1048  if ( $row ) { // $row is false if the only contributor is hidden
1049  $onlyAuthor = $row->rev_user_text;
1050  // Try to find a second contributor
1051  foreach ( $res as $row ) {
1052  if ( $row->rev_user_text != $onlyAuthor ) { // T24999
1053  $onlyAuthor = false;
1054  break;
1055  }
1056  }
1057  } else {
1058  $onlyAuthor = false;
1059  }
1060 
1061  // Generate the summary with a '$1' placeholder
1062  if ( $blank ) {
1063  // The current revision is blank and the one before is also
1064  // blank. It's just not our lucky day
1065  $reason = wfMessage( 'exbeforeblank', '$1' )->inContentLanguage()->text();
1066  } else {
1067  if ( $onlyAuthor ) {
1068  $reason = wfMessage(
1069  'excontentauthor',
1070  '$1',
1071  $onlyAuthor
1072  )->inContentLanguage()->text();
1073  } else {
1074  $reason = wfMessage( 'excontent', '$1' )->inContentLanguage()->text();
1075  }
1076  }
1077 
1078  if ( $reason == '-' ) {
1079  // Allow these UI messages to be blanked out cleanly
1080  return '';
1081  }
1082 
1083  // Max content length = max comment length - length of the comment (excl. $1)
1084  $maxLength = CommentStore::COMMENT_CHARACTER_LIMIT - ( strlen( $reason ) - 2 );
1085  $text = $content ? $content->getTextForSummary( $maxLength ) : '';
1086 
1087  // Now replace the '$1' placeholder
1088  $reason = str_replace( '$1', $text, $reason );
1089 
1090  return $reason;
1091  }
1092 
1109  public function getUndoContent( $current, $undo, $undoafter, $undoIsLatest = false ) {
1110  Assert::parameterType( Revision::class . '|' . Content::class, $current, '$current' );
1111  if ( $current instanceof Content ) {
1112  Assert::parameter( $undo instanceof Content, '$undo',
1113  'Must be Content when $current is Content' );
1114  Assert::parameter( $undoafter instanceof Content, '$undoafter',
1115  'Must be Content when $current is Content' );
1116  $cur_content = $current;
1117  $undo_content = $undo;
1118  $undoafter_content = $undoafter;
1119  } else {
1120  Assert::parameter( $undo instanceof Revision, '$undo',
1121  'Must be Revision when $current is Revision' );
1122  Assert::parameter( $undoafter instanceof Revision, '$undoafter',
1123  'Must be Revision when $current is Revision' );
1124 
1125  $cur_content = $current->getContent();
1126 
1127  if ( empty( $cur_content ) ) {
1128  return false; // no page
1129  }
1130 
1131  $undo_content = $undo->getContent();
1132  $undoafter_content = $undoafter->getContent();
1133 
1134  if ( !$undo_content || !$undoafter_content ) {
1135  return false; // no content to undo
1136  }
1137 
1138  $undoIsLatest = $current->getId() === $undo->getId();
1139  }
1140 
1141  try {
1142  $this->checkModelID( $cur_content->getModel() );
1143  $this->checkModelID( $undo_content->getModel() );
1144  if ( !$undoIsLatest ) {
1145  // If we are undoing the most recent revision,
1146  // its ok to revert content model changes. However
1147  // if we are undoing a revision in the middle, then
1148  // doing that will be confusing.
1149  $this->checkModelID( $undoafter_content->getModel() );
1150  }
1151  } catch ( MWException $e ) {
1152  // If the revisions have different content models
1153  // just return false
1154  return false;
1155  }
1156 
1157  if ( $cur_content->equals( $undo_content ) ) {
1158  // No use doing a merge if it's just a straight revert.
1159  return $undoafter_content;
1160  }
1161 
1162  $undone_content = $this->merge3( $undo_content, $undoafter_content, $cur_content );
1163 
1164  return $undone_content;
1165  }
1166 
1183  public function makeParserOptions( $context ) {
1184  wfDeprecated( __METHOD__, '1.32' );
1186  }
1187 
1196  public function isParserCacheSupported() {
1197  return false;
1198  }
1199 
1209  public function supportsSections() {
1210  return false;
1211  }
1212 
1219  public function supportsCategories() {
1220  return true;
1221  }
1222 
1232  public function supportsRedirects() {
1233  return false;
1234  }
1235 
1241  public function supportsDirectEditing() {
1242  return false;
1243  }
1244 
1250  public function supportsDirectApiEditing() {
1251  return $this->supportsDirectEditing();
1252  }
1253 
1264  public function getFieldsForSearchIndex( SearchEngine $engine ) {
1265  $fields = [];
1266  $fields['category'] = $engine->makeSearchFieldMapping(
1267  'category',
1269  );
1270  $fields['category']->setFlag( SearchIndexField::FLAG_CASEFOLD );
1271 
1272  $fields['external_link'] = $engine->makeSearchFieldMapping(
1273  'external_link',
1275  );
1276 
1277  $fields['outgoing_link'] = $engine->makeSearchFieldMapping(
1278  'outgoing_link',
1280  );
1281 
1282  $fields['template'] = $engine->makeSearchFieldMapping(
1283  'template',
1285  );
1286  $fields['template']->setFlag( SearchIndexField::FLAG_CASEFOLD );
1287 
1288  $fields['content_model'] = $engine->makeSearchFieldMapping(
1289  'content_model',
1291  );
1292 
1293  return $fields;
1294  }
1295 
1305  protected function addSearchField( &$fields, SearchEngine $engine, $name, $type ) {
1306  $fields[$name] = $engine->makeSearchFieldMapping( $name, $type );
1307  return $fields;
1308  }
1309 
1321  public function getDataForSearchIndex(
1322  WikiPage $page,
1323  ParserOutput $output,
1324  SearchEngine $engine
1325  ) {
1326  $fieldData = [];
1327  $content = $page->getContent();
1328 
1329  if ( $content ) {
1330  $searchDataExtractor = new ParserOutputSearchDataExtractor();
1331 
1332  $fieldData['category'] = $searchDataExtractor->getCategories( $output );
1333  $fieldData['external_link'] = $searchDataExtractor->getExternalLinks( $output );
1334  $fieldData['outgoing_link'] = $searchDataExtractor->getOutgoingLinks( $output );
1335  $fieldData['template'] = $searchDataExtractor->getTemplates( $output );
1336 
1337  $text = $content->getTextForSearchIndex();
1338 
1339  $fieldData['text'] = $text;
1340  $fieldData['source_text'] = $text;
1341  $fieldData['text_bytes'] = $content->getSize();
1342  $fieldData['content_model'] = $content->getModel();
1343  }
1344 
1345  Hooks::run( 'SearchDataForIndex', [ &$fieldData, $this, $page, $output, $engine ] );
1346  return $fieldData;
1347  }
1348 
1358  public function getParserOutputForIndexing( WikiPage $page, ParserCache $cache = null ) {
1359  // TODO: MCR: ContentHandler should be called per slot, not for the whole page.
1360  // See T190066.
1361  $parserOptions = $page->makeParserOptions( 'canonical' );
1362  if ( $cache ) {
1363  $parserOutput = $cache->get( $page, $parserOptions );
1364  }
1365 
1366  if ( empty( $parserOutput ) ) {
1367  $renderer = MediaWikiServices::getInstance()->getRevisionRenderer();
1368  $parserOutput =
1369  $renderer->getRenderedRevision(
1370  $page->getRevision()->getRevisionRecord(),
1371  $parserOptions
1372  )->getRevisionParserOutput();
1373  if ( $cache ) {
1374  $cache->save( $parserOutput, $page, $parserOptions );
1375  }
1376  }
1377  return $parserOutput;
1378  }
1379 
1410  public function getSecondaryDataUpdates(
1411  Title $title,
1412  Content $content,
1413  $role,
1414  SlotRenderingProvider $slotOutput
1415  ) {
1416  return [];
1417  }
1418 
1447  public function getDeletionUpdates( Title $title, $role ) {
1448  return [];
1449  }
1450 
1451 }
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:3161
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:996
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:1296
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:139
$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...
__construct( $modelId, $formats)
Constructor, initializing the ContentHandler instance with its model ID and a list of supported forma...
$wgLang
Definition: Setup.php:858
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 DiffEngine config 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:63
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:316
getNamespace()
Get the namespace index, i.e.
Definition: Title.php:1035
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:1047
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
$content
Definition: router.php:78
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...