MediaWiki REL1_29
ChangeTags.php
Go to the documentation of this file.
1<?php
25
32 const MAX_DELETE_USES = 5000;
33
37 private static $coreTags = [ 'mw-contentmodelchange' ];
38
52 public static function formatSummaryRow( $tags, $page, IContextSource $context = null ) {
53 if ( !$tags ) {
54 return [ '', [] ];
55 }
56 if ( !$context ) {
58 }
59
60 $classes = [];
61
62 $tags = explode( ',', $tags );
63 $displayTags = [];
64 foreach ( $tags as $tag ) {
65 if ( !$tag ) {
66 continue;
67 }
68 $description = self::tagDescription( $tag, $context );
69 if ( $description === false ) {
70 continue;
71 }
72 $displayTags[] = Xml::tags(
73 'span',
74 [ 'class' => 'mw-tag-marker ' .
75 Sanitizer::escapeClass( "mw-tag-marker-$tag" ) ],
76 $description
77 );
78 $classes[] = Sanitizer::escapeClass( "mw-tag-$tag" );
79 }
80
81 if ( !$displayTags ) {
82 return [ '', [] ];
83 }
84
85 $markers = $context->msg( 'tag-list-wrapper' )
86 ->numParams( count( $displayTags ) )
87 ->rawParams( $context->getLanguage()->commaList( $displayTags ) )
88 ->parse();
89 $markers = Xml::tags( 'span', [ 'class' => 'mw-tag-markers' ], $markers );
90
91 return [ $markers, $classes ];
92 }
93
107 public static function tagDescription( $tag, IContextSource $context ) {
108 $msg = $context->msg( "tag-$tag" );
109 if ( !$msg->exists() ) {
110 // No such message, so return the HTML-escaped tag name.
111 return htmlspecialchars( $tag );
112 }
113 if ( $msg->isDisabled() ) {
114 // The message exists but is disabled, hide the tag.
115 return false;
116 }
117
118 // Message exists and isn't disabled, use it.
119 return $msg->parse();
120 }
121
136 public static function addTags( $tags, $rc_id = null, $rev_id = null,
137 $log_id = null, $params = null, RecentChange $rc = null
138 ) {
139 $result = self::updateTags( $tags, null, $rc_id, $rev_id, $log_id, $params, $rc );
140 return (bool)$result[0];
141 }
142
173 public static function updateTags( $tagsToAdd, $tagsToRemove, &$rc_id = null,
174 &$rev_id = null, &$log_id = null, $params = null, RecentChange $rc = null,
175 User $user = null
176 ) {
177
178 $tagsToAdd = array_filter( (array)$tagsToAdd ); // Make sure we're submitting all tags...
179 $tagsToRemove = array_filter( (array)$tagsToRemove );
180
181 if ( !$rc_id && !$rev_id && !$log_id ) {
182 throw new MWException( 'At least one of: RCID, revision ID, and log ID MUST be ' .
183 'specified when adding or removing a tag from a change!' );
184 }
185
186 $dbw = wfGetDB( DB_MASTER );
187
188 // Might as well look for rcids and so on.
189 if ( !$rc_id ) {
190 // Info might be out of date, somewhat fractionally, on replica DB.
191 // LogEntry/LogPage and WikiPage match rev/log/rc timestamps,
192 // so use that relation to avoid full table scans.
193 if ( $log_id ) {
194 $rc_id = $dbw->selectField(
195 [ 'logging', 'recentchanges' ],
196 'rc_id',
197 [
198 'log_id' => $log_id,
199 'rc_timestamp = log_timestamp',
200 'rc_logid = log_id'
201 ],
202 __METHOD__
203 );
204 } elseif ( $rev_id ) {
205 $rc_id = $dbw->selectField(
206 [ 'revision', 'recentchanges' ],
207 'rc_id',
208 [
209 'rev_id' => $rev_id,
210 'rc_timestamp = rev_timestamp',
211 'rc_this_oldid = rev_id'
212 ],
213 __METHOD__
214 );
215 }
216 } elseif ( !$log_id && !$rev_id ) {
217 // Info might be out of date, somewhat fractionally, on replica DB.
218 $log_id = $dbw->selectField(
219 'recentchanges',
220 'rc_logid',
221 [ 'rc_id' => $rc_id ],
222 __METHOD__
223 );
224 $rev_id = $dbw->selectField(
225 'recentchanges',
226 'rc_this_oldid',
227 [ 'rc_id' => $rc_id ],
228 __METHOD__
229 );
230 }
231
232 if ( $log_id && !$rev_id ) {
233 $rev_id = $dbw->selectField(
234 'log_search',
235 'ls_value',
236 [ 'ls_field' => 'associated_rev_id', 'ls_log_id' => $log_id ],
237 __METHOD__
238 );
239 } elseif ( !$log_id && $rev_id ) {
240 $log_id = $dbw->selectField(
241 'log_search',
242 'ls_log_id',
243 [ 'ls_field' => 'associated_rev_id', 'ls_value' => $rev_id ],
244 __METHOD__
245 );
246 }
247
248 // update the tag_summary row
249 $prevTags = [];
250 if ( !self::updateTagSummaryRow( $tagsToAdd, $tagsToRemove, $rc_id, $rev_id,
251 $log_id, $prevTags ) ) {
252
253 // nothing to do
254 return [ [], [], $prevTags ];
255 }
256
257 // insert a row into change_tag for each new tag
258 if ( count( $tagsToAdd ) ) {
259 $tagsRows = [];
260 foreach ( $tagsToAdd as $tag ) {
261 // Filter so we don't insert NULLs as zero accidentally.
262 // Keep in mind that $rc_id === null means "I don't care/know about the
263 // rc_id, just delete $tag on this revision/log entry". It doesn't
264 // mean "only delete tags on this revision/log WHERE rc_id IS NULL".
265 $tagsRows[] = array_filter(
266 [
267 'ct_tag' => $tag,
268 'ct_rc_id' => $rc_id,
269 'ct_log_id' => $log_id,
270 'ct_rev_id' => $rev_id,
271 'ct_params' => $params
272 ]
273 );
274 }
275
276 $dbw->insert( 'change_tag', $tagsRows, __METHOD__, [ 'IGNORE' ] );
277 }
278
279 // delete from change_tag
280 if ( count( $tagsToRemove ) ) {
281 foreach ( $tagsToRemove as $tag ) {
282 $conds = array_filter(
283 [
284 'ct_tag' => $tag,
285 'ct_rc_id' => $rc_id,
286 'ct_log_id' => $log_id,
287 'ct_rev_id' => $rev_id
288 ]
289 );
290 $dbw->delete( 'change_tag', $conds, __METHOD__ );
291 }
292 }
293
295
296 Hooks::run( 'ChangeTagsAfterUpdateTags', [ $tagsToAdd, $tagsToRemove, $prevTags,
297 $rc_id, $rev_id, $log_id, $params, $rc, $user ] );
298
299 return [ $tagsToAdd, $tagsToRemove, $prevTags ];
300 }
301
318 protected static function updateTagSummaryRow( &$tagsToAdd, &$tagsToRemove,
319 $rc_id, $rev_id, $log_id, &$prevTags = [] ) {
320
321 $dbw = wfGetDB( DB_MASTER );
322
323 $tsConds = array_filter( [
324 'ts_rc_id' => $rc_id,
325 'ts_rev_id' => $rev_id,
326 'ts_log_id' => $log_id
327 ] );
328
329 // Can't both add and remove a tag at the same time...
330 $tagsToAdd = array_diff( $tagsToAdd, $tagsToRemove );
331
332 // Update the summary row.
333 // $prevTags can be out of date on replica DBs, especially when addTags is called consecutively,
334 // causing loss of tags added recently in tag_summary table.
335 $prevTags = $dbw->selectField( 'tag_summary', 'ts_tags', $tsConds, __METHOD__ );
336 $prevTags = $prevTags ? $prevTags : '';
337 $prevTags = array_filter( explode( ',', $prevTags ) );
338
339 // add tags
340 $tagsToAdd = array_values( array_diff( $tagsToAdd, $prevTags ) );
341 $newTags = array_unique( array_merge( $prevTags, $tagsToAdd ) );
342
343 // remove tags
344 $tagsToRemove = array_values( array_intersect( $tagsToRemove, $newTags ) );
345 $newTags = array_values( array_diff( $newTags, $tagsToRemove ) );
346
347 sort( $prevTags );
348 sort( $newTags );
349 if ( $prevTags == $newTags ) {
350 // No change.
351 return false;
352 }
353
354 if ( !$newTags ) {
355 // no tags left, so delete the row altogether
356 $dbw->delete( 'tag_summary', $tsConds, __METHOD__ );
357 } else {
358 $dbw->replace( 'tag_summary',
359 [ 'ts_rev_id', 'ts_rc_id', 'ts_log_id' ],
360 array_filter( array_merge( $tsConds, [ 'ts_tags' => implode( ',', $newTags ) ] ) ),
361 __METHOD__
362 );
363 }
364
365 return true;
366 }
367
378 protected static function restrictedTagError( $msgOne, $msgMulti, $tags ) {
379 $lang = RequestContext::getMain()->getLanguage();
380 $count = count( $tags );
381 return Status::newFatal( ( $count > 1 ) ? $msgMulti : $msgOne,
382 $lang->commaList( $tags ), $count );
383 }
384
395 public static function canAddTagsAccompanyingChange( array $tags,
396 User $user = null ) {
397
398 if ( !is_null( $user ) ) {
399 if ( !$user->isAllowed( 'applychangetags' ) ) {
400 return Status::newFatal( 'tags-apply-no-permission' );
401 } elseif ( $user->isBlocked() ) {
402 return Status::newFatal( 'tags-apply-blocked', $user->getName() );
403 }
404 }
405
406 // to be applied, a tag has to be explicitly defined
407 // @todo Allow extensions to define tags that can be applied by users...
408 $allowedTags = self::listExplicitlyDefinedTags();
409 $disallowedTags = array_diff( $tags, $allowedTags );
410 if ( $disallowedTags ) {
411 return self::restrictedTagError( 'tags-apply-not-allowed-one',
412 'tags-apply-not-allowed-multi', $disallowedTags );
413 }
414
415 return Status::newGood();
416 }
417
439 array $tags, $rc_id, $rev_id, $log_id, $params, User $user
440 ) {
441
442 // are we allowed to do this?
444 if ( !$result->isOK() ) {
445 $result->value = null;
446 return $result;
447 }
448
449 // do it!
450 self::addTags( $tags, $rc_id, $rev_id, $log_id, $params );
451
452 return Status::newGood( true );
453 }
454
466 public static function canUpdateTags( array $tagsToAdd, array $tagsToRemove,
467 User $user = null ) {
468
469 if ( !is_null( $user ) ) {
470 if ( !$user->isAllowed( 'changetags' ) ) {
471 return Status::newFatal( 'tags-update-no-permission' );
472 } elseif ( $user->isBlocked() ) {
473 return Status::newFatal( 'tags-update-blocked', $user->getName() );
474 }
475 }
476
477 if ( $tagsToAdd ) {
478 // to be added, a tag has to be explicitly defined
479 // @todo Allow extensions to define tags that can be applied by users...
480 $explicitlyDefinedTags = self::listExplicitlyDefinedTags();
481 $diff = array_diff( $tagsToAdd, $explicitlyDefinedTags );
482 if ( $diff ) {
483 return self::restrictedTagError( 'tags-update-add-not-allowed-one',
484 'tags-update-add-not-allowed-multi', $diff );
485 }
486 }
487
488 if ( $tagsToRemove ) {
489 // to be removed, a tag must not be defined by an extension, or equivalently it
490 // has to be either explicitly defined or not defined at all
491 // (assuming no edge case of a tag both explicitly-defined and extension-defined)
492 $softwareDefinedTags = self::listSoftwareDefinedTags();
493 $intersect = array_intersect( $tagsToRemove, $softwareDefinedTags );
494 if ( $intersect ) {
495 return self::restrictedTagError( 'tags-update-remove-not-allowed-one',
496 'tags-update-remove-not-allowed-multi', $intersect );
497 }
498 }
499
500 return Status::newGood();
501 }
502
529 public static function updateTagsWithChecks( $tagsToAdd, $tagsToRemove,
530 $rc_id, $rev_id, $log_id, $params, $reason, User $user ) {
531
532 if ( is_null( $tagsToAdd ) ) {
533 $tagsToAdd = [];
534 }
535 if ( is_null( $tagsToRemove ) ) {
536 $tagsToRemove = [];
537 }
538 if ( !$tagsToAdd && !$tagsToRemove ) {
539 // no-op, don't bother
540 return Status::newGood( (object)[
541 'logId' => null,
542 'addedTags' => [],
543 'removedTags' => [],
544 ] );
545 }
546
547 // are we allowed to do this?
548 $result = self::canUpdateTags( $tagsToAdd, $tagsToRemove, $user );
549 if ( !$result->isOK() ) {
550 $result->value = null;
551 return $result;
552 }
553
554 // basic rate limiting
555 if ( $user->pingLimiter( 'changetag' ) ) {
556 return Status::newFatal( 'actionthrottledtext' );
557 }
558
559 // do it!
560 list( $tagsAdded, $tagsRemoved, $initialTags ) = self::updateTags( $tagsToAdd,
561 $tagsToRemove, $rc_id, $rev_id, $log_id, $params, null, $user );
562 if ( !$tagsAdded && !$tagsRemoved ) {
563 // no-op, don't log it
564 return Status::newGood( (object)[
565 'logId' => null,
566 'addedTags' => [],
567 'removedTags' => [],
568 ] );
569 }
570
571 // log it
572 $logEntry = new ManualLogEntry( 'tag', 'update' );
573 $logEntry->setPerformer( $user );
574 $logEntry->setComment( $reason );
575
576 // find the appropriate target page
577 if ( $rev_id ) {
578 $rev = Revision::newFromId( $rev_id );
579 if ( $rev ) {
580 $logEntry->setTarget( $rev->getTitle() );
581 }
582 } elseif ( $log_id ) {
583 // This function is from revision deletion logic and has nothing to do with
584 // change tags, but it appears to be the only other place in core where we
585 // perform logged actions on log items.
586 $logEntry->setTarget( RevDelLogList::suggestTarget( null, [ $log_id ] ) );
587 }
588
589 if ( !$logEntry->getTarget() ) {
590 // target is required, so we have to set something
591 $logEntry->setTarget( SpecialPage::getTitleFor( 'Tags' ) );
592 }
593
594 $logParams = [
595 '4::revid' => $rev_id,
596 '5::logid' => $log_id,
597 '6:list:tagsAdded' => $tagsAdded,
598 '7:number:tagsAddedCount' => count( $tagsAdded ),
599 '8:list:tagsRemoved' => $tagsRemoved,
600 '9:number:tagsRemovedCount' => count( $tagsRemoved ),
601 'initialTags' => $initialTags,
602 ];
603 $logEntry->setParameters( $logParams );
604 $logEntry->setRelations( [ 'Tag' => array_merge( $tagsAdded, $tagsRemoved ) ] );
605
606 $dbw = wfGetDB( DB_MASTER );
607 $logId = $logEntry->insert( $dbw );
608 // Only send this to UDP, not RC, similar to patrol events
609 $logEntry->publish( $logId, 'udp' );
610
611 return Status::newGood( (object)[
612 'logId' => $logId,
613 'addedTags' => $tagsAdded,
614 'removedTags' => $tagsRemoved,
615 ] );
616 }
617
632 public static function modifyDisplayQuery( &$tables, &$fields, &$conds,
633 &$join_conds, &$options, $filter_tag = false ) {
635
636 if ( $filter_tag === false ) {
637 $filter_tag = $wgRequest->getVal( 'tagfilter' );
638 }
639
640 // Figure out which conditions can be done.
641 if ( in_array( 'recentchanges', $tables ) ) {
642 $join_cond = 'ct_rc_id=rc_id';
643 } elseif ( in_array( 'logging', $tables ) ) {
644 $join_cond = 'ct_log_id=log_id';
645 } elseif ( in_array( 'revision', $tables ) ) {
646 $join_cond = 'ct_rev_id=rev_id';
647 } elseif ( in_array( 'archive', $tables ) ) {
648 $join_cond = 'ct_rev_id=ar_rev_id';
649 } else {
650 throw new MWException( 'Unable to determine appropriate JOIN condition for tagging.' );
651 }
652
653 $fields['ts_tags'] = wfGetDB( DB_REPLICA )->buildGroupConcatField(
654 ',', 'change_tag', 'ct_tag', $join_cond
655 );
656
657 if ( $wgUseTagFilter && $filter_tag ) {
658 // Somebody wants to filter on a tag.
659 // Add an INNER JOIN on change_tag
660
661 $tables[] = 'change_tag';
662 $join_conds['change_tag'] = [ 'INNER JOIN', $join_cond ];
663 $conds['ct_tag'] = $filter_tag;
664 }
665 }
666
678 public static function buildTagFilterSelector(
679 $selected = '', $ooui = false, IContextSource $context = null
680 ) {
681 if ( !$context ) {
683 }
684
685 $config = $context->getConfig();
686 if ( !$config->get( 'UseTagFilter' ) || !count( self::listDefinedTags() ) ) {
687 return [];
688 }
689
690 $data = [
691 Html::rawElement(
692 'label',
693 [ 'for' => 'tagfilter' ],
694 $context->msg( 'tag-filter' )->parse()
695 )
696 ];
697
698 if ( $ooui ) {
699 $data[] = new OOUI\TextInputWidget( [
700 'id' => 'tagfilter',
701 'name' => 'tagfilter',
702 'value' => $selected,
703 'classes' => 'mw-tagfilter-input',
704 ] );
705 } else {
706 $data[] = Xml::input(
707 'tagfilter',
708 20,
709 $selected,
710 [ 'class' => 'mw-tagfilter-input mw-ui-input mw-ui-input-inline', 'id' => 'tagfilter' ]
711 );
712 }
713
714 return $data;
715 }
716
726 public static function defineTag( $tag ) {
727 $dbw = wfGetDB( DB_MASTER );
728 $dbw->replace( 'valid_tag',
729 [ 'vt_tag' ],
730 [ 'vt_tag' => $tag ],
731 __METHOD__ );
732
733 // clear the memcache of defined tags
735 }
736
745 public static function undefineTag( $tag ) {
746 $dbw = wfGetDB( DB_MASTER );
747 $dbw->delete( 'valid_tag', [ 'vt_tag' => $tag ], __METHOD__ );
748
749 // clear the memcache of defined tags
751 }
752
767 protected static function logTagManagementAction( $action, $tag, $reason,
768 User $user, $tagCount = null, array $logEntryTags = [] ) {
769
770 $dbw = wfGetDB( DB_MASTER );
771
772 $logEntry = new ManualLogEntry( 'managetags', $action );
773 $logEntry->setPerformer( $user );
774 // target page is not relevant, but it has to be set, so we just put in
775 // the title of Special:Tags
776 $logEntry->setTarget( Title::newFromText( 'Special:Tags' ) );
777 $logEntry->setComment( $reason );
778
779 $params = [ '4::tag' => $tag ];
780 if ( !is_null( $tagCount ) ) {
781 $params['5:number:count'] = $tagCount;
782 }
783 $logEntry->setParameters( $params );
784 $logEntry->setRelations( [ 'Tag' => $tag ] );
785 $logEntry->setTags( $logEntryTags );
786
787 $logId = $logEntry->insert( $dbw );
788 $logEntry->publish( $logId );
789 return $logId;
790 }
791
801 public static function canActivateTag( $tag, User $user = null ) {
802 if ( !is_null( $user ) ) {
803 if ( !$user->isAllowed( 'managechangetags' ) ) {
804 return Status::newFatal( 'tags-manage-no-permission' );
805 } elseif ( $user->isBlocked() ) {
806 return Status::newFatal( 'tags-manage-blocked', $user->getName() );
807 }
808 }
809
810 // defined tags cannot be activated (a defined tag is either extension-
811 // defined, in which case the extension chooses whether or not to active it;
812 // or user-defined, in which case it is considered active)
813 $definedTags = self::listDefinedTags();
814 if ( in_array( $tag, $definedTags ) ) {
815 return Status::newFatal( 'tags-activate-not-allowed', $tag );
816 }
817
818 // non-existing tags cannot be activated
819 $tagUsage = self::tagUsageStatistics();
820 if ( !isset( $tagUsage[$tag] ) ) { // we already know the tag is undefined
821 return Status::newFatal( 'tags-activate-not-found', $tag );
822 }
823
824 return Status::newGood();
825 }
826
844 public static function activateTagWithChecks( $tag, $reason, User $user,
845 $ignoreWarnings = false, array $logEntryTags = [] ) {
846
847 // are we allowed to do this?
849 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
850 $result->value = null;
851 return $result;
852 }
853
854 // do it!
856
857 // log it
858 $logId = self::logTagManagementAction( 'activate', $tag, $reason, $user,
859 null, $logEntryTags );
860
861 return Status::newGood( $logId );
862 }
863
873 public static function canDeactivateTag( $tag, User $user = null ) {
874 if ( !is_null( $user ) ) {
875 if ( !$user->isAllowed( 'managechangetags' ) ) {
876 return Status::newFatal( 'tags-manage-no-permission' );
877 } elseif ( $user->isBlocked() ) {
878 return Status::newFatal( 'tags-manage-blocked', $user->getName() );
879 }
880 }
881
882 // only explicitly-defined tags can be deactivated
883 $explicitlyDefinedTags = self::listExplicitlyDefinedTags();
884 if ( !in_array( $tag, $explicitlyDefinedTags ) ) {
885 return Status::newFatal( 'tags-deactivate-not-allowed', $tag );
886 }
887 return Status::newGood();
888 }
889
907 public static function deactivateTagWithChecks( $tag, $reason, User $user,
908 $ignoreWarnings = false, array $logEntryTags = [] ) {
909
910 // are we allowed to do this?
912 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
913 $result->value = null;
914 return $result;
915 }
916
917 // do it!
919
920 // log it
921 $logId = self::logTagManagementAction( 'deactivate', $tag, $reason, $user,
922 null, $logEntryTags );
923
924 return Status::newGood( $logId );
925 }
926
936 public static function canCreateTag( $tag, User $user = null ) {
937 if ( !is_null( $user ) ) {
938 if ( !$user->isAllowed( 'managechangetags' ) ) {
939 return Status::newFatal( 'tags-manage-no-permission' );
940 } elseif ( $user->isBlocked() ) {
941 return Status::newFatal( 'tags-manage-blocked', $user->getName() );
942 }
943 }
944
945 // no empty tags
946 if ( $tag === '' ) {
947 return Status::newFatal( 'tags-create-no-name' );
948 }
949
950 // tags cannot contain commas (used as a delimiter in tag_summary table) or
951 // slashes (would break tag description messages in MediaWiki namespace)
952 if ( strpos( $tag, ',' ) !== false || strpos( $tag, '/' ) !== false ) {
953 return Status::newFatal( 'tags-create-invalid-chars' );
954 }
955
956 // could the MediaWiki namespace description messages be created?
957 $title = Title::makeTitleSafe( NS_MEDIAWIKI, "Tag-$tag-description" );
958 if ( is_null( $title ) ) {
959 return Status::newFatal( 'tags-create-invalid-title-chars' );
960 }
961
962 // does the tag already exist?
963 $tagUsage = self::tagUsageStatistics();
964 if ( isset( $tagUsage[$tag] ) || in_array( $tag, self::listDefinedTags() ) ) {
965 return Status::newFatal( 'tags-create-already-exists', $tag );
966 }
967
968 // check with hooks
969 $canCreateResult = Status::newGood();
970 Hooks::run( 'ChangeTagCanCreate', [ $tag, $user, &$canCreateResult ] );
971 return $canCreateResult;
972 }
973
990 public static function createTagWithChecks( $tag, $reason, User $user,
991 $ignoreWarnings = false, array $logEntryTags = [] ) {
992
993 // are we allowed to do this?
995 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
996 $result->value = null;
997 return $result;
998 }
999
1000 // do it!
1002
1003 // log it
1004 $logId = self::logTagManagementAction( 'create', $tag, $reason, $user,
1005 null, $logEntryTags );
1006
1007 return Status::newGood( $logId );
1008 }
1009
1022 public static function deleteTagEverywhere( $tag ) {
1023 $dbw = wfGetDB( DB_MASTER );
1024 $dbw->startAtomic( __METHOD__ );
1025
1026 // delete from valid_tag
1028
1029 // find out which revisions use this tag, so we can delete from tag_summary
1030 $result = $dbw->select( 'change_tag',
1031 [ 'ct_rc_id', 'ct_log_id', 'ct_rev_id', 'ct_tag' ],
1032 [ 'ct_tag' => $tag ],
1033 __METHOD__ );
1034 foreach ( $result as $row ) {
1035 // remove the tag from the relevant row of tag_summary
1036 $tagsToAdd = [];
1037 $tagsToRemove = [ $tag ];
1038 self::updateTagSummaryRow( $tagsToAdd, $tagsToRemove, $row->ct_rc_id,
1039 $row->ct_rev_id, $row->ct_log_id );
1040 }
1041
1042 // delete from change_tag
1043 $dbw->delete( 'change_tag', [ 'ct_tag' => $tag ], __METHOD__ );
1044
1045 $dbw->endAtomic( __METHOD__ );
1046
1047 // give extensions a chance
1048 $status = Status::newGood();
1049 Hooks::run( 'ChangeTagAfterDelete', [ $tag, &$status ] );
1050 // let's not allow error results, as the actual tag deletion succeeded
1051 if ( !$status->isOK() ) {
1052 wfDebug( 'ChangeTagAfterDelete error condition downgraded to warning' );
1053 $status->setOK( true );
1054 }
1055
1056 // clear the memcache of defined tags
1058
1059 return $status;
1060 }
1061
1071 public static function canDeleteTag( $tag, User $user = null ) {
1072 $tagUsage = self::tagUsageStatistics();
1073
1074 if ( !is_null( $user ) ) {
1075 if ( !$user->isAllowed( 'deletechangetags' ) ) {
1076 return Status::newFatal( 'tags-delete-no-permission' );
1077 } elseif ( $user->isBlocked() ) {
1078 return Status::newFatal( 'tags-manage-blocked', $user->getName() );
1079 }
1080 }
1081
1082 if ( !isset( $tagUsage[$tag] ) && !in_array( $tag, self::listDefinedTags() ) ) {
1083 return Status::newFatal( 'tags-delete-not-found', $tag );
1084 }
1085
1086 if ( isset( $tagUsage[$tag] ) && $tagUsage[$tag] > self::MAX_DELETE_USES ) {
1087 return Status::newFatal( 'tags-delete-too-many-uses', $tag, self::MAX_DELETE_USES );
1088 }
1089
1090 $softwareDefined = self::listSoftwareDefinedTags();
1091 if ( in_array( $tag, $softwareDefined ) ) {
1092 // extension-defined tags can't be deleted unless the extension
1093 // specifically allows it
1094 $status = Status::newFatal( 'tags-delete-not-allowed' );
1095 } else {
1096 // user-defined tags are deletable unless otherwise specified
1097 $status = Status::newGood();
1098 }
1099
1100 Hooks::run( 'ChangeTagCanDelete', [ $tag, $user, &$status ] );
1101 return $status;
1102 }
1103
1121 public static function deleteTagWithChecks( $tag, $reason, User $user,
1122 $ignoreWarnings = false, array $logEntryTags = [] ) {
1123
1124 // are we allowed to do this?
1126 if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) {
1127 $result->value = null;
1128 return $result;
1129 }
1130
1131 // store the tag usage statistics
1132 $tagUsage = self::tagUsageStatistics();
1133 $hitcount = isset( $tagUsage[$tag] ) ? $tagUsage[$tag] : 0;
1134
1135 // do it!
1136 $deleteResult = self::deleteTagEverywhere( $tag );
1137 if ( !$deleteResult->isOK() ) {
1138 return $deleteResult;
1139 }
1140
1141 // log it
1142 $logId = self::logTagManagementAction( 'delete', $tag, $reason, $user,
1143 $hitcount, $logEntryTags );
1144
1145 $deleteResult->value = $logId;
1146 return $deleteResult;
1147 }
1148
1155 public static function listSoftwareActivatedTags() {
1156 // core active tags
1157 $tags = self::$coreTags;
1158 if ( !Hooks::isRegistered( 'ChangeTagsListActive' ) ) {
1159 return $tags;
1160 }
1161 return ObjectCache::getMainWANInstance()->getWithSetCallback(
1162 wfMemcKey( 'active-tags' ),
1163 WANObjectCache::TTL_MINUTE * 5,
1164 function ( $oldValue, &$ttl, array &$setOpts ) use ( $tags ) {
1165 $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
1166
1167 // Ask extensions which tags they consider active
1168 Hooks::run( 'ChangeTagsListActive', [ &$tags ] );
1169 return $tags;
1170 },
1171 [
1172 'checkKeys' => [ wfMemcKey( 'active-tags' ) ],
1173 'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
1174 'pcTTL' => WANObjectCache::TTL_PROC_LONG
1175 ]
1176 );
1177 }
1178
1184 public static function listExtensionActivatedTags() {
1185 wfDeprecated( __METHOD__, '1.28' );
1187 }
1188
1196 public static function listDefinedTags() {
1199 return array_values( array_unique( array_merge( $tags1, $tags2 ) ) );
1200 }
1201
1212 public static function listExplicitlyDefinedTags() {
1213 $fname = __METHOD__;
1214
1215 return ObjectCache::getMainWANInstance()->getWithSetCallback(
1216 wfMemcKey( 'valid-tags-db' ),
1217 WANObjectCache::TTL_MINUTE * 5,
1218 function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
1219 $dbr = wfGetDB( DB_REPLICA );
1220
1221 $setOpts += Database::getCacheSetOptions( $dbr );
1222
1223 $tags = $dbr->selectFieldValues( 'valid_tag', 'vt_tag', [], $fname );
1224
1225 return array_filter( array_unique( $tags ) );
1226 },
1227 [
1228 'checkKeys' => [ wfMemcKey( 'valid-tags-db' ) ],
1229 'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
1230 'pcTTL' => WANObjectCache::TTL_PROC_LONG
1231 ]
1232 );
1233 }
1234
1244 public static function listSoftwareDefinedTags() {
1245 // core defined tags
1246 $tags = self::$coreTags;
1247 if ( !Hooks::isRegistered( 'ListDefinedTags' ) ) {
1248 return $tags;
1249 }
1250 return ObjectCache::getMainWANInstance()->getWithSetCallback(
1251 wfMemcKey( 'valid-tags-hook' ),
1252 WANObjectCache::TTL_MINUTE * 5,
1253 function ( $oldValue, &$ttl, array &$setOpts ) use ( $tags ) {
1254 $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
1255
1256 Hooks::run( 'ListDefinedTags', [ &$tags ] );
1257 return array_filter( array_unique( $tags ) );
1258 },
1259 [
1260 'checkKeys' => [ wfMemcKey( 'valid-tags-hook' ) ],
1261 'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
1262 'pcTTL' => WANObjectCache::TTL_PROC_LONG
1263 ]
1264 );
1265 }
1266
1273 public static function listExtensionDefinedTags() {
1274 wfDeprecated( __METHOD__, '1.28' );
1276 }
1277
1283 public static function purgeTagCacheAll() {
1284 $cache = ObjectCache::getMainWANInstance();
1285
1286 $cache->touchCheckKey( wfMemcKey( 'active-tags' ) );
1287 $cache->touchCheckKey( wfMemcKey( 'valid-tags-db' ) );
1288 $cache->touchCheckKey( wfMemcKey( 'valid-tags-hook' ) );
1289
1291 }
1292
1297 public static function purgeTagUsageCache() {
1298 $cache = ObjectCache::getMainWANInstance();
1299
1300 $cache->touchCheckKey( wfMemcKey( 'change-tag-statistics' ) );
1301 }
1302
1313 public static function tagUsageStatistics() {
1314 $fname = __METHOD__;
1315 return ObjectCache::getMainWANInstance()->getWithSetCallback(
1316 wfMemcKey( 'change-tag-statistics' ),
1317 WANObjectCache::TTL_MINUTE * 5,
1318 function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
1319 $dbr = wfGetDB( DB_REPLICA, 'vslow' );
1320
1321 $setOpts += Database::getCacheSetOptions( $dbr );
1322
1323 $res = $dbr->select(
1324 'change_tag',
1325 [ 'ct_tag', 'hitcount' => 'count(*)' ],
1326 [],
1327 $fname,
1328 [ 'GROUP BY' => 'ct_tag', 'ORDER BY' => 'hitcount DESC' ]
1329 );
1330
1331 $out = [];
1332 foreach ( $res as $row ) {
1333 $out[$row->ct_tag] = $row->hitcount;
1334 }
1335
1336 return $out;
1337 },
1338 [
1339 'checkKeys' => [ wfMemcKey( 'change-tag-statistics' ) ],
1340 'lockTSE' => WANObjectCache::TTL_MINUTE * 5,
1341 'pcTTL' => WANObjectCache::TTL_PROC_LONG
1342 ]
1343 );
1344 }
1345
1360 public static function showTagEditingUI( User $user ) {
1361 return $user->isAllowed( 'changetags' ) && (bool)self::listExplicitlyDefinedTags();
1362 }
1363}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
$wgUseTagFilter
Allow filtering by change tag in recentchanges, history, etc Has no effect if no tags are defined in ...
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfMemcKey()
Make a cache key for the local wiki.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined.
Definition Setup.php:36
if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:639
static createTagWithChecks( $tag, $reason, User $user, $ignoreWarnings=false, array $logEntryTags=[])
Creates a tag by adding a row to the valid_tag table.
static listSoftwareDefinedTags()
Lists tags defined by core or extensions using the ListDefinedTags hook.
static buildTagFilterSelector( $selected='', $ooui=false, IContextSource $context=null)
Build a text box to select a change tag.
static purgeTagUsageCache()
Invalidates the tag statistics cache only.
static deactivateTagWithChecks( $tag, $reason, User $user, $ignoreWarnings=false, array $logEntryTags=[])
Deactivates a tag, checking whether it is allowed first, and adding a log entry afterwards.
static logTagManagementAction( $action, $tag, $reason, User $user, $tagCount=null, array $logEntryTags=[])
Writes a tag action into the tag management log.
static updateTags( $tagsToAdd, $tagsToRemove, &$rc_id=null, &$rev_id=null, &$log_id=null, $params=null, RecentChange $rc=null, User $user=null)
Add and remove tags to/from a change given its rc_id, rev_id and/or log_id, without verifying that th...
static updateTagSummaryRow(&$tagsToAdd, &$tagsToRemove, $rc_id, $rev_id, $log_id, &$prevTags=[])
Adds or removes a given set of tags to/from the relevant row of the tag_summary table.
static restrictedTagError( $msgOne, $msgMulti, $tags)
Helper function to generate a fatal status with a 'not-allowed' type error.
static activateTagWithChecks( $tag, $reason, User $user, $ignoreWarnings=false, array $logEntryTags=[])
Activates a tag, checking whether it is allowed first, and adding a log entry afterwards.
static tagDescription( $tag, IContextSource $context)
Get a short description for a tag.
static listSoftwareActivatedTags()
Lists those tags which core or extensions report as being "active".
static undefineTag( $tag)
Removes a tag from the valid_tag table.
static canCreateTag( $tag, User $user=null)
Is it OK to allow the user to create this tag?
static purgeTagCacheAll()
Invalidates the short-term cache of defined tags used by the list*DefinedTags functions,...
static addTags( $tags, $rc_id=null, $rev_id=null, $log_id=null, $params=null, RecentChange $rc=null)
Add tags to a change given its rc_id, rev_id and/or log_id.
static tagUsageStatistics()
Returns a map of any tags used on the wiki to number of edits tagged with them, ordered descending by...
static addTagsAccompanyingChangeWithChecks(array $tags, $rc_id, $rev_id, $log_id, $params, User $user)
Adds tags to a given change, checking whether it is allowed first, but without adding a log entry.
static formatSummaryRow( $tags, $page, IContextSource $context=null)
Creates HTML for the given tags.
static listExtensionActivatedTags()
static updateTagsWithChecks( $tagsToAdd, $tagsToRemove, $rc_id, $rev_id, $log_id, $params, $reason, User $user)
Adds and/or removes tags to/from a given change, checking whether it is allowed first,...
static canActivateTag( $tag, User $user=null)
Is it OK to allow the user to activate this tag?
static listDefinedTags()
Basically lists defined tags which count even if they aren't applied to anything.
static defineTag( $tag)
Defines a tag in the valid_tag table, without checking that the tag name is valid.
static canDeactivateTag( $tag, User $user=null)
Is it OK to allow the user to deactivate this tag?
static showTagEditingUI(User $user)
Indicate whether change tag editing UI is relevant.
static canDeleteTag( $tag, User $user=null)
Is it OK to allow the user to delete this tag?
static deleteTagWithChecks( $tag, $reason, User $user, $ignoreWarnings=false, array $logEntryTags=[])
Deletes a tag, checking whether it is allowed first, and adding a log entry afterwards.
static modifyDisplayQuery(&$tables, &$fields, &$conds, &$join_conds, &$options, $filter_tag=false)
Applies all tags-related changes to a query.
static deleteTagEverywhere( $tag)
Permanently removes all traces of a tag from the DB.
const MAX_DELETE_USES
Can't delete tags with more than this many uses.
static listExtensionDefinedTags()
Call listSoftwareDefinedTags directly.
static canUpdateTags(array $tagsToAdd, array $tagsToRemove, User $user=null)
Is it OK to allow the user to adds and remove the given tags tags to/from a change?
static listExplicitlyDefinedTags()
Lists tags explicitly defined in the valid_tag table of the database.
static string[] $coreTags
static canAddTagsAccompanyingChange(array $tags, User $user=null)
Is it OK to allow the user to apply all the specified tags at the same time as they edit/make the cha...
getConfig()
Get the Config object.
msg()
Get a Message object with context set Parameters are the same as wfMessage()
getLanguage()
Get the Language object.
MediaWiki exception.
Class for creating log entries manually, to inject them into the database.
Definition LogEntry.php:396
Utility class for creating new RC entries.
static getMain()
Static methods.
static suggestTarget( $target, array $ids)
Suggest a target for the revision deletion Optionally override this function.
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition Revision.php:116
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:50
Relational database abstraction object.
Definition Database.php:45
$res
Definition database.txt:21
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
when a variable name is used in a it is silently declared as a new local masking the global
Definition design.txt:95
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
const NS_MEDIAWIKI
Definition Defines.php:70
the array() calling protocol came about after MediaWiki 1.4rc1.
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition hooks.txt:249
error also a ContextSource you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition hooks.txt:2728
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached $page
Definition hooks.txt:2578
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition hooks.txt:1954
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist & $tables
Definition hooks.txt:1018
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition hooks.txt:1102
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books $tag
Definition hooks.txt:1033
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:964
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition hooks.txt:864
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition hooks.txt:1049
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition hooks.txt:1751
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
Interface for objects which can provide a MediaWiki context on request.
$cache
Definition mcc.php:33
const DB_REPLICA
Definition defines.php:25
const DB_MASTER
Definition defines.php:26
$params
if(!isset( $args[0])) $lang