RCFilters: Filter duplicates when filtering for multiple tags
[lhc/web/wiklou.git] / includes / specials / SpecialRecentchanges.php
index acfc1c0..45235aa 100644 (file)
@@ -138,7 +138,9 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
         * @param string $subpage
         */
        public function execute( $subpage ) {
-               global $wgStructuredChangeFiltersEnableSaving;
+               global $wgStructuredChangeFiltersEnableSaving,
+                       $wgStructuredChangeFiltersEnableExperimentalViews,
+                       $wgStructuredChangeFiltersEnableLiveUpdate;
 
                // Backwards-compatibility: redirect to new feed URLs
                $feedFormat = $this->getRequest()->getVal( 'feed' );
@@ -184,9 +186,64 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                                'wgStructuredChangeFiltersEnableSaving',
                                $wgStructuredChangeFiltersEnableSaving
                        );
+                       $out->addJsConfigVars(
+                               'wgStructuredChangeFiltersEnableExperimentalViews',
+                               $wgStructuredChangeFiltersEnableExperimentalViews
+                       );
+                       $out->addJsConfigVars(
+                               'wgStructuredChangeFiltersEnableLiveUpdate',
+                               $wgStructuredChangeFiltersEnableLiveUpdate
+                       );
+                       $out->addJsConfigVars(
+                               'wgRCFiltersChangeTags',
+                               $this->buildChangeTagList()
+                       );
                }
        }
 
+       /**
+        * Fetch the change tags list for the front end
+        *
+        * @return Array Tag data
+        */
+       protected function buildChangeTagList() {
+               function stripAllHtml( $input ) {
+                       return trim( html_entity_decode( strip_tags( $input ) ) );
+               }
+
+               $explicitlyDefinedTags = array_fill_keys( ChangeTags::listExplicitlyDefinedTags(), 0 );
+               $softwareActivatedTags = array_fill_keys( ChangeTags::listSoftwareActivatedTags(), 0 );
+               $tagStats = ChangeTags::tagUsageStatistics();
+
+               $tagHitCounts = array_merge( $explicitlyDefinedTags, $softwareActivatedTags, $tagStats );
+
+               // Sort by hits
+               arsort( $tagHitCounts );
+
+               // Build the list and data
+               $result = [];
+               foreach ( $tagHitCounts as $tagName => $hits ) {
+                       if (
+                               // Only get active tags
+                               isset( $explicitlyDefinedTags[ $tagName ] ) ||
+                               isset( $softwareActivatedTags[ $tagName ] )
+                       ) {
+                               // Parse description
+                               $desc = ChangeTags::tagLongDescriptionMessage( $tagName, $this->getContext() );
+
+                               $result[] = [
+                                       'name' => $tagName,
+                                       'label' => stripAllHtml( ChangeTags::tagDescription( $tagName, $this->getContext() ) ),
+                                       'description' => $desc ? stripAllHtml( $desc->parse() ) : '',
+                                       'cssClass' => Sanitizer::escapeClass( 'mw-tag-' . $tagName ),
+                                       'hits' => $hits,
+                               ];
+                       }
+               }
+
+               return $result;
+       }
+
        /**
         * @inheritdoc
         */
@@ -315,8 +372,8 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
         * @inheritdoc
         */
        protected function buildQuery( &$tables, &$fields, &$conds,
-               &$query_options, &$join_conds, FormOptions $opts ) {
-
+               &$query_options, &$join_conds, FormOptions $opts
+       ) {
                $dbr = $this->getDB();
                parent::buildQuery( $tables, $fields, $conds,
                        $query_options, $join_conds, $opts );
@@ -340,8 +397,8 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
         * @inheritdoc
         */
        protected function doMainQuery( $tables, $fields, $conds, $query_options,
-               $join_conds, FormOptions $opts ) {
-
+               $join_conds, FormOptions $opts
+       ) {
                $dbr = $this->getDB();
                $user = $this->getUser();
 
@@ -365,13 +422,14 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                $fields[] = 'page_latest';
                $join_conds['page'] = [ 'LEFT JOIN', 'rc_cur_id=page_id' ];
 
+               $tagFilter = explode( '|', $opts['tagfilter'] );
                ChangeTags::modifyDisplayQuery(
                        $tables,
                        $fields,
                        $conds,
                        $join_conds,
                        $query_options,
-                       $opts['tagfilter']
+                       $tagFilter
                );
 
                if ( !$this->runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds,
@@ -384,13 +442,24 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                        return false;
                }
 
+               $orderByAndLimit = [
+                       'ORDER BY' => 'rc_timestamp DESC',
+                       'LIMIT' => $opts['limit']
+               ];
+               if ( in_array( 'DISTINCT', $query_options ) ) {
+                       // ChangeTags::modifyDisplayQuery() adds DISTINCT when filtering on multiple tags.
+                       // In order to prevent DISTINCT from causing query performance problems,
+                       // we have to GROUP BY the primary key. This in turn requires us to add
+                       // the primary key to the end of the ORDER BY, and the old ORDER BY to the
+                       // start of the GROUP BY
+                       $orderByAndLimit['ORDER BY'] = 'rc_timestamp DESC, rc_id DESC';
+                       $orderByAndLimit['GROUP BY'] = 'rc_timestamp, rc_id';
+               }
                // array_merge() is used intentionally here so that hooks can, should
                // they so desire, override the ORDER BY / LIMIT condition(s); prior to
                // MediaWiki 1.26 this used to use the plus operator instead, which meant
                // that extensions weren't able to change these conditions
-               $query_options = array_merge( [
-                       'ORDER BY' => 'rc_timestamp DESC',
-                       'LIMIT' => $opts['limit'] ], $query_options );
+               $query_options = array_merge( $orderByAndLimit, $query_options );
                $rows = $dbr->select(
                        $tables,
                        $fields,
@@ -469,6 +538,10 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
 
                $userShowHiddenCats = $this->getUser()->getBoolOption( 'showhiddencats' );
                $rclistOutput = $list->beginRecentChangesList();
+               if ( $this->isStructuredFilterUiEnabled() ) {
+                       $rclistOutput .= $this->makeLegend();
+               }
+
                foreach ( $rows as $obj ) {
                        if ( $limit == 0 ) {
                                break;
@@ -536,7 +609,9 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                $nondefaults = $opts->getChangedValues();
 
                $panel = [];
-               $panel[] = $this->makeLegend();
+               if ( !$this->isStructuredFilterUiEnabled() ) {
+                       $panel[] = $this->makeLegend();
+               }
                $panel[] = $this->optionsPanel( $defaults, $nondefaults, $numRows );
                $panel[] = '<hr />';