MediaWiki REL1_34
ContentHandler.php
Go to the documentation of this file.
1<?php
2
30use Wikimedia\Assert\Assert;
35
55abstract 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() {
502 return $this->mSupportedFormats;
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 = 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.
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 ) {
996
997 // Get the last revision
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' );
1183 return ParserOptions::newCanonical( $context );
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
1409 Title $title,
1411 $role,
1412 SlotRenderingProvider $slotOutput
1413 ) {
1414 return [];
1415 }
1416
1445 public function getDeletionUpdates( Title $title, $role ) {
1446 return [];
1447 }
1448
1449}
$wgContentHandlerTextFallback
How to react if a plain text version of a non-text Content object is requested using ContentHandler::...
$wgContentHandlers
Plugins for page content model handling.
wfGetLangObj( $langcode=false)
Return a Language object from $langcode.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
$wgLang
Definition Setup.php:880
static getSoftwareTags( $all=false)
Loads defined core tags, checks for invalid types (if not array), and filters for supported and enabl...
A content handler knows how do deal with a specific type of content on a wiki page.
getParserOutputForIndexing(WikiPage $page, ParserCache $cache=null)
Produce page output suitable for indexing.
getModelID()
Returns the model id that identifies the content model this ContentHandler can handle.
makeRedirectContent(Title $destination, $text='')
Creates a new Content object that acts as a redirect to the given page, or null if redirects are not ...
__construct( $modelId, $formats)
Constructor, initializing the ContentHandler instance with its model ID and a list of supported forma...
static getAllContentFormats()
string[] $mSupportedFormats
getChangeType(Content $oldContent=null, Content $newContent=null, $flags=0)
Return type of change if one exists for the given edit.
checkModelID( $model_id)
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
importTransform( $blob, $format=null)
Apply import transformation (per default, returns $blob unchanged).
isParserCacheSupported()
Returns true for content models that support caching using the ParserCache mechanism.
static getForModelID( $modelId)
Returns the ContentHandler singleton for the given model ID.
supportsDirectApiEditing()
Whether or not this content model supports direct editing via ApiEditPage.
getAutoDeleteReason(Title $title, &$hasHistory)
Auto-generates a deletion reason.
getChangeTag(Content $oldContent=null, Content $newContent=null, $flags=0)
Return an applicable tag if one exists for the given edit or return null.
getDiffEngineClass()
Returns the name of the diff engine to use.
static getForTitle(Title $title)
Returns the appropriate ContentHandler singleton for the given title.
exportTransform( $blob, $format=null)
Applies transformations on export (returns the blob unchanged per default).
merge3(Content $oldContent, Content $myContent, Content $yourContent)
Attempts to merge differences between three versions.
getAutosummary(Content $oldContent=null, Content $newContent=null, $flags=0)
Return an applicable auto-summary if one exists for the given edit.
checkFormat( $format)
Convenient for checking whether a format provided as a parameter is actually supported.
addSearchField(&$fields, SearchEngine $engine, $name, $type)
Add new field definition to array.
getActionOverrides()
Returns overrides for action handlers.
createDifferenceEngine(IContextSource $context, $old=0, $new=0, $rcid=0, $refreshCache=false, $unhide=false)
Factory for creating an appropriate DifferenceEngine for this content model.
getSecondaryDataUpdates(Title $title, Content $content, $role, SlotRenderingProvider $slotOutput)
Returns a list of DeferrableUpdate objects for recording information about the given Content in some ...
static getContentModels()
getPageLanguage(Title $title, Content $content=null)
Get the language in which the content of the given page is written.
getDefaultFormat()
The format used for serialization/deserialization by default by this ContentHandler.
getSlotDiffRendererInternal(IContextSource $context)
Return the SlotDiffRenderer appropriate for this content handler.
static getDefaultModelFor(Title $title)
Returns the name of the default content model to be used for the page with the given title.
unserializeContent( $blob, $format=null)
Unserializes a Content object of the type supported by this ContentHandler.
static getLocalizedName( $name, Language $lang=null)
Returns the localized name for a given content model.
supportsDirectEditing()
Return true if this content model supports direct editing, such as via EditPage.
getDeletionUpdates(Title $title, $role)
Returns a list of DeferrableUpdate objects for removing information about content in some secondary d...
isSupportedFormat( $format)
Returns true if $format is a serialization format supported by this ContentHandler,...
getSupportedFormats()
Returns a list of serialization formats supported by the serializeContent() and unserializeContent() ...
supportsSections()
Returns true if this content model supports sections.
getSlotDiffRenderer(IContextSource $context)
Get an appropriate SlotDiffRenderer for this content model.
static cleanupHandlersCache()
Clean up handlers cache.
static getContentText(Content $content=null)
Convenience function for getting flat text from a Content object.
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 getForContent(Content $content)
Returns the appropriate ContentHandler singleton for the given Content object.
supportsRedirects()
Returns true if this content model supports redirects.
canBeUsedOn(Title $title)
Determines whether the content type handled by this ContentHandler can be used for the main slot of t...
serializeContent(Content $content, $format=null)
Serializes a Content object of the type supported by this ContentHandler.
makeEmptyContent()
Creates an empty Content object of the type supported by this ContentHandler.
static array $handlers
A Cache of ContentHandler instances by model id.
makeParserOptions( $context)
Get parser options suitable for rendering and caching the article.
getFieldsForSearchIndex(SearchEngine $engine)
Get fields definition for search index.
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...
getPageViewLanguage(Title $title, Content $content=null)
Get the language in which the content of this page is written when viewed by user.
B/C adapter for turning a DifferenceEngine into a SlotDiffRenderer.
static getEngine()
Process DiffEngine config and get a sane, usable engine.
Internationalisation code.
Definition Language.php:37
MediaWiki exception.
Exception thrown when an unregistered content model is requested.
PSR-3 logger instance factory.
MediaWikiServices is the service locator for the application scope of MediaWiki.
Page revision base class.
Extracts data from ParserOutput for indexing in the search engine.
static getQueryInfo( $options=[])
Return the tables, fields, and join conditions to be selected to create a new revision object.
Definition Revision.php:315
static newFromTitle(LinkTarget $linkTarget, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given link target.
Definition Revision.php:138
Contain a class for special pages.
makeSearchFieldMapping( $name, $type)
Create a search field definition.
static unstub(&$obj)
Unstubs an object, if it is a stub object.
Content object implementation for representing flat text.
Renders a slot diff by doing a text diff on the native representation.
const ENGINE_PHP
Use the PHP diff implementation (DiffEngine).
const ENGINE_EXTERNAL
Use an external executable.
const ENGINE_WIKIDIFF2
Use the wikidiff2 PHP module.
Represents a title within MediaWiki.
Definition Title.php:42
Class representing a MediaWiki article and history.
Definition WikiPage.php:47
getRevision()
Get the latest revision.
Definition WikiPage.php:787
getContent( $audience=RevisionRecord::FOR_PUBLIC, User $user=null)
Get the content of the current revision.
Definition WikiPage.php:820
makeParserOptions( $context)
Get parser options suitable for rendering the primary article wikitext.
const NS_MEDIAWIKI
Definition Defines.php:77
const EDIT_NEW
Definition Defines.php:141
Base interface for content objects.
Definition Content.php:34
Interface for objects which can provide a MediaWiki context on request.
A lazy provider of ParserOutput objects for a revision's individual slots.
const INDEX_TYPE_TEXT
TEXT fields are suitable for natural language and may be subject to analysis such as stemming.
const INDEX_TYPE_KEYWORD
KEYWORD fields are indexed without any processing, so are appropriate for e.g.
const FLAG_CASEFOLD
Generic field flags.
$context
Definition load.php:45
$cache
Definition mcc.php:33
const DB_REPLICA
Definition defines.php:25
$content
Definition router.php:78
if(!isset( $args[0])) $lang