Merge "Remove gen from RawAction."
[lhc/web/wiklou.git] / includes / changetags / ChangeTags.php
index a730116..5aac495 100644 (file)
@@ -153,8 +153,10 @@ class ChangeTags {
         *
         * @since 1.25
         */
-       public static function updateTags( $tagsToAdd, $tagsToRemove, &$rc_id = null,
-               &$rev_id = null, &$log_id = null, $params = null ) {
+       public static function updateTags(
+               $tagsToAdd, $tagsToRemove,
+               &$rc_id = null, &$rev_id = null, &$log_id = null, $params = null
+       ) {
 
                $tagsToAdd = array_filter( (array)$tagsToAdd ); // Make sure we're submitting all tags...
                $tagsToRemove = array_filter( (array)$tagsToRemove );
@@ -169,18 +171,28 @@ class ChangeTags {
                // Might as well look for rcids and so on.
                if ( !$rc_id ) {
                        // Info might be out of date, somewhat fractionally, on slave.
+                       // LogEntry/LogPage and WikiPage match rev/log/rc timestamps,
+                       // so use that relation to avoid full table scans.
                        if ( $log_id ) {
                                $rc_id = $dbw->selectField(
-                                       'recentchanges',
+                                       array( 'logging', 'recentchanges' ),
                                        'rc_id',
-                                       array( 'rc_logid' => $log_id ),
+                                       array(
+                                               'log_id' => $log_id,
+                                               'rc_timestamp = log_timestamp',
+                                               'rc_logid = log_id'
+                                       ),
                                        __METHOD__
                                );
                        } elseif ( $rev_id ) {
                                $rc_id = $dbw->selectField(
-                                       'recentchanges',
+                                       array( 'revision', 'recentchanges' ),
                                        'rc_id',
-                                       array( 'rc_this_oldid' => $rev_id ),
+                                       array(
+                                               'rev_id' => $rev_id,
+                                               'rc_timestamp = rev_timestamp',
+                                               'rc_this_oldid = rev_id'
+                                       ),
                                        __METHOD__
                                );
                        }
@@ -346,8 +358,12 @@ class ChangeTags {
        public static function canAddTagsAccompanyingChange( array $tags,
                User $user = null ) {
 
-               if ( !is_null( $user ) && !$user->isAllowed( 'applychangetags' ) ) {
-                       return Status::newFatal( 'tags-apply-no-permission' );
+               if ( !is_null( $user ) ) {
+                       if ( !$user->isAllowed( 'applychangetags' ) ) {
+                               return Status::newFatal( 'tags-apply-no-permission' );
+                       } elseif ( $user->isBlocked() ) {
+                               return Status::newFatal( 'tags-apply-blocked' );
+                       }
                }
 
                // to be applied, a tag has to be explicitly defined
@@ -413,25 +429,31 @@ class ChangeTags {
        public static function canUpdateTags( array $tagsToAdd, array $tagsToRemove,
                User $user = null ) {
 
-               if ( !is_null( $user ) && !$user->isAllowed( 'changetags' ) ) {
-                       return Status::newFatal( 'tags-update-no-permission' );
+               if ( !is_null( $user ) ) {
+                       if ( !$user->isAllowed( 'changetags' ) ) {
+                               return Status::newFatal( 'tags-update-no-permission' );
+                       } elseif ( $user->isBlocked() ) {
+                               return Status::newFatal( 'tags-update-blocked' );
+                       }
                }
 
-               // to be added, a tag has to be explicitly defined
-               // @todo Allow extensions to define tags that can be applied by users...
-               $explicitlyDefinedTags = self::listExplicitlyDefinedTags();
-               $diff = array_diff( $tagsToAdd, $explicitlyDefinedTags );
-               if ( $diff ) {
-                       return self::restrictedTagError( 'tags-update-add-not-allowed-one',
-                               'tags-update-add-not-allowed-multi', $diff );
+               if ( $tagsToAdd ) {
+                       // to be added, a tag has to be explicitly defined
+                       // @todo Allow extensions to define tags that can be applied by users...
+                       $explicitlyDefinedTags = self::listExplicitlyDefinedTags();
+                       $diff = array_diff( $tagsToAdd, $explicitlyDefinedTags );
+                       if ( $diff ) {
+                               return self::restrictedTagError( 'tags-update-add-not-allowed-one',
+                                       'tags-update-add-not-allowed-multi', $diff );
+                       }
                }
 
-               // to be removed, a tag has to be either explicitly defined or not defined
-               // at all
-               $definedTags = self::listDefinedTags();
-               $diff = array_diff( $tagsToRemove, $explicitlyDefinedTags );
-               if ( $diff ) {
-                       $intersect = array_intersect( $diff, $definedTags );
+               if ( $tagsToRemove ) {
+                       // to be removed, a tag must not be defined by an extension, or equivalently it
+                       // has to be either explicitly defined or not defined at all
+                       // (assuming no edge case of a tag both explicitly-defined and extension-defined)
+                       $extensionDefinedTags = self::listExtensionDefinedTags();
+                       $intersect = array_intersect( $tagsToRemove, $extensionDefinedTags );
                        if ( $intersect ) {
                                return self::restrictedTagError( 'tags-update-remove-not-allowed-one',
                                        'tags-update-remove-not-allowed-multi', $intersect );
@@ -609,17 +631,16 @@ class ChangeTags {
         * Build a text box to select a change tag
         *
         * @param string $selected Tag to select by default
-        * @param bool $fullForm
-        *        - if false, then it returns an array of (label, form).
-        *        - if true, it returns an entire form around the selector.
-        * @param Title $title Title object to send the form to.
-        *        Used when, and only when $fullForm is true.
+        * @param bool $fullForm Affects return value, see below
+        * @param Title $title Title object to send the form to. Used only if $fullForm is true.
+        * @param bool $ooui Use an OOUI TextInputWidget as selector instead of a non-OOUI input field
+        *        You need to call OutputPage::enableOOUI() yourself.
         * @return string|array
-        *        - if $fullForm is false: Array with
-        *        - if $fullForm is true: String, html fragment
+        *        - if $fullForm is false: an array of (label, selector).
+        *        - if $fullForm is true: HTML of entire form built around the selector.
         */
        public static function buildTagFilterSelector( $selected = '',
-               $fullForm = false, Title $title = null
+               $fullForm = false, Title $title = null, $ooui = false
        ) {
                global $wgUseTagFilter;
 
@@ -632,14 +653,24 @@ class ChangeTags {
                                'label',
                                array( 'for' => 'tagfilter' ),
                                wfMessage( 'tag-filter' )->parse()
-                       ),
-                       Xml::input(
+                       )
+               );
+
+               if ( $ooui ) {
+                       $data[] = new OOUI\TextInputWidget( array(
+                               'id' => 'tagfilter',
+                               'name' => 'tagfilter',
+                               'value' => $selected,
+                               'classes' => 'mw-tagfilter-input',
+                       ) );
+               } else {
+                       $data[] = Xml::input(
                                'tagfilter',
                                20,
                                $selected,
                                array( 'class' => 'mw-tagfilter-input mw-ui-input mw-ui-input-inline', 'id' => 'tagfilter' )
-                       )
-               );
+                       );
+               }
 
                if ( !$fullForm ) {
                        return $data;
@@ -743,14 +774,12 @@ class ChangeTags {
         * @since 1.25
         */
        public static function canActivateTag( $tag, User $user = null ) {
-               if ( !is_null( $user ) && !$user->isAllowed( 'managechangetags' ) ) {
-                       return Status::newFatal( 'tags-manage-no-permission' );
-               }
-
-               // non-existing tags cannot be activated
-               $tagUsage = self::tagUsageStatistics();
-               if ( !isset( $tagUsage[$tag] ) ) {
-                       return Status::newFatal( 'tags-activate-not-found', $tag );
+               if ( !is_null( $user ) ) {
+                       if ( !$user->isAllowed( 'managechangetags' ) ) {
+                               return Status::newFatal( 'tags-manage-no-permission' );
+                       } elseif ( $user->isBlocked() ) {
+                               return Status::newFatal( 'tags-manage-blocked' );
+                       }
                }
 
                // defined tags cannot be activated (a defined tag is either extension-
@@ -761,6 +790,12 @@ class ChangeTags {
                        return Status::newFatal( 'tags-activate-not-allowed', $tag );
                }
 
+               // non-existing tags cannot be activated
+               $tagUsage = self::tagUsageStatistics();
+               if ( !isset( $tagUsage[$tag] ) ) { // we already know the tag is undefined
+                       return Status::newFatal( 'tags-activate-not-found', $tag );
+               }
+
                return Status::newGood();
        }
 
@@ -807,8 +842,12 @@ class ChangeTags {
         * @since 1.25
         */
        public static function canDeactivateTag( $tag, User $user = null ) {
-               if ( !is_null( $user ) && !$user->isAllowed( 'managechangetags' ) ) {
-                       return Status::newFatal( 'tags-manage-no-permission' );
+               if ( !is_null( $user ) ) {
+                       if ( !$user->isAllowed( 'managechangetags' ) ) {
+                               return Status::newFatal( 'tags-manage-no-permission' );
+                       } elseif ( $user->isBlocked() ) {
+                               return Status::newFatal( 'tags-manage-blocked' );
+                       }
                }
 
                // only explicitly-defined tags can be deactivated
@@ -862,8 +901,12 @@ class ChangeTags {
         * @since 1.25
         */
        public static function canCreateTag( $tag, User $user = null ) {
-               if ( !is_null( $user ) && !$user->isAllowed( 'managechangetags' ) ) {
-                       return Status::newFatal( 'tags-manage-no-permission' );
+               if ( !is_null( $user ) ) {
+                       if ( !$user->isAllowed( 'managechangetags' ) ) {
+                               return Status::newFatal( 'tags-manage-no-permission' );
+                       } elseif ( $user->isBlocked() ) {
+                               return Status::newFatal( 'tags-manage-blocked' );
+                       }
                }
 
                // no empty tags
@@ -885,7 +928,7 @@ class ChangeTags {
 
                // does the tag already exist?
                $tagUsage = self::tagUsageStatistics();
-               if ( isset( $tagUsage[$tag] ) ) {
+               if ( isset( $tagUsage[$tag] ) || in_array( $tag, self::listDefinedTags() ) ) {
                        return Status::newFatal( 'tags-create-already-exists', $tag );
                }
 
@@ -991,15 +1034,19 @@ class ChangeTags {
        public static function canDeleteTag( $tag, User $user = null ) {
                $tagUsage = self::tagUsageStatistics();
 
-               if ( !is_null( $user ) && !$user->isAllowed( 'managechangetags' ) ) {
-                       return Status::newFatal( 'tags-manage-no-permission' );
+               if ( !is_null( $user ) ) {
+                       if ( !$user->isAllowed( 'managechangetags' ) ) {
+                               return Status::newFatal( 'tags-manage-no-permission' );
+                       } elseif ( $user->isBlocked() ) {
+                               return Status::newFatal( 'tags-manage-blocked' );
+                       }
                }
 
-               if ( !isset( $tagUsage[$tag] ) ) {
+               if ( !isset( $tagUsage[$tag] ) && !in_array( $tag, self::listDefinedTags() ) ) {
                        return Status::newFatal( 'tags-delete-not-found', $tag );
                }
 
-               if ( $tagUsage[$tag] > self::MAX_DELETE_USES ) {
+               if ( isset( $tagUsage[$tag] ) && $tagUsage[$tag] > self::MAX_DELETE_USES ) {
                        return Status::newFatal( 'tags-delete-too-many-uses', $tag, self::MAX_DELETE_USES );
                }
 
@@ -1044,6 +1091,7 @@ class ChangeTags {
 
                // store the tag usage statistics
                $tagUsage = self::tagUsageStatistics();
+               $hitcount = isset( $tagUsage[$tag] ) ? $tagUsage[$tag] : 0;
 
                // do it!
                $deleteResult = self::deleteTagEverywhere( $tag );
@@ -1052,7 +1100,7 @@ class ChangeTags {
                }
 
                // log it
-               $logId = self::logTagManagementAction( 'delete', $tag, $reason, $user, $tagUsage[$tag] );
+               $logId = self::logTagManagementAction( 'delete', $tag, $reason, $user, $hitcount );
                $deleteResult->value = $logId;
                return $deleteResult;
        }
@@ -1066,15 +1114,20 @@ class ChangeTags {
        public static function listExtensionActivatedTags() {
                return ObjectCache::getMainWANInstance()->getWithSetCallback(
                        wfMemcKey( 'active-tags' ),
-                       function() {
+                       300,
+                       function ( $oldValue, &$ttl, array &$setOpts ) {
+                               $setOpts += Database::getCacheSetOptions( wfGetDB( DB_SLAVE ) );
+
                                // Ask extensions which tags they consider active
                                $extensionActive = array();
                                Hooks::run( 'ChangeTagsListActive', array( &$extensionActive ) );
                                return $extensionActive;
                        },
-                       300,
-                       array( wfMemcKey( 'active-tags' ) ),
-                       array( 'lockTSE' => INF )
+                       array(
+                               'checkKeys' => array( wfMemcKey( 'active-tags' ) ),
+                               'lockTSE' => 300,
+                               'pcTTL' => 30
+                       )
                );
        }
 
@@ -1106,16 +1159,21 @@ class ChangeTags {
 
                return ObjectCache::getMainWANInstance()->getWithSetCallback(
                        wfMemcKey( 'valid-tags-db' ),
-                       function() use ( $fname ) {
+                       300,
+                       function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
                                $dbr = wfGetDB( DB_SLAVE );
-                               $tags = $dbr->selectFieldValues(
-                                       'valid_tag', 'vt_tag', array(), $fname );
+
+                               $setOpts += Database::getCacheSetOptions( $dbr );
+
+                               $tags = $dbr->selectFieldValues( 'valid_tag', 'vt_tag', array(), $fname );
 
                                return array_filter( array_unique( $tags ) );
                        },
-                       300,
-                       array( wfMemcKey( 'valid-tags-db' ) ),
-                       array( 'lockTSE' => INF )
+                       array(
+                               'checkKeys' => array( wfMemcKey( 'valid-tags-db' ) ),
+                               'lockTSE' => 300,
+                               'pcTTL' => 30
+                       )
                );
        }
 
@@ -1131,14 +1189,19 @@ class ChangeTags {
        public static function listExtensionDefinedTags() {
                return ObjectCache::getMainWANInstance()->getWithSetCallback(
                        wfMemcKey( 'valid-tags-hook' ),
-                       function() {
+                       300,
+                       function ( $oldValue, &$ttl, array &$setOpts ) {
+                               $setOpts += Database::getCacheSetOptions( wfGetDB( DB_SLAVE ) );
+
                                $tags = array();
                                Hooks::run( 'ListDefinedTags', array( &$tags ) );
                                return array_filter( array_unique( $tags ) );
                        },
-                       300,
-                       array( wfMemcKey( 'valid-tags-hook' ) ),
-                       array( 'lockTSE' => INF )
+                       array(
+                               'checkKeys' => array( wfMemcKey( 'valid-tags-hook' ) ),
+                               'lockTSE' => 300,
+                               'pcTTL' => 30
+                       )
                );
        }
 
@@ -1170,6 +1233,7 @@ class ChangeTags {
        /**
         * Returns a map of any tags used on the wiki to number of edits
         * tagged with them, ordered descending by the hitcount.
+        * This does not include tags defined somewhere that have never been applied.
         *
         * Keeps a short-term cache in memory, so calling this multiple times in the
         * same request should be fine.
@@ -1178,13 +1242,14 @@ class ChangeTags {
         */
        public static function tagUsageStatistics() {
                $fname = __METHOD__;
-
                return ObjectCache::getMainWANInstance()->getWithSetCallback(
                        wfMemcKey( 'change-tag-statistics' ),
-                       function() use ( $fname ) {
-                               $out = array();
-
+                       300,
+                       function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
                                $dbr = wfGetDB( DB_SLAVE, 'vslow' );
+
+                               $setOpts += Database::getCacheSetOptions( $dbr );
+
                                $res = $dbr->select(
                                        'change_tag',
                                        array( 'ct_tag', 'hitcount' => 'count(*)' ),
@@ -1193,21 +1258,18 @@ class ChangeTags {
                                        array( 'GROUP BY' => 'ct_tag', 'ORDER BY' => 'hitcount DESC' )
                                );
 
+                               $out = array();
                                foreach ( $res as $row ) {
                                        $out[$row->ct_tag] = $row->hitcount;
                                }
 
-                               foreach ( ChangeTags::listDefinedTags() as $tag ) {
-                                       if ( !isset( $out[$tag] ) ) {
-                                               $out[$tag] = 0;
-                                       }
-                               }
-
                                return $out;
                        },
-                       300,
-                       array( wfMemcKey( 'change-tag-statistics' ) ),
-                       array( 'lockTSE' => INF )
+                       array(
+                               'checkKeys' => array( wfMemcKey( 'change-tag-statistics' ) ),
+                               'lockTSE' => 300,
+                               'pcTTL' => 30
+                       )
                );
        }