Merge "mw.rcfilters.ui.SaveFiltersPopupButtonWidget: Remove pointless option"
[lhc/web/wiklou.git] / includes / specialpage / ChangesListSpecialPage.php
index 67f68ea..303184d 100644 (file)
@@ -21,6 +21,7 @@
  * @ingroup SpecialPage
  */
 use MediaWiki\Logger\LoggerFactory;
+use Wikimedia\Rdbms\DBQueryTimeoutError;
 use Wikimedia\Rdbms\ResultWrapper;
 use Wikimedia\Rdbms\FakeResultWrapper;
 use Wikimedia\Rdbms\IDatabase;
@@ -38,6 +39,18 @@ abstract class ChangesListSpecialPage extends SpecialPage {
         */
        protected static $savedQueriesPreferenceName;
 
+       /**
+        * Preference name for 'days'. Subclasses should override this.
+        * @var string
+        */
+       protected static $daysPreferenceName;
+
+       /**
+        * Preference name for 'limit'. Subclasses should override this.
+        * @var string
+        */
+       protected static $limitPreferenceName;
+
        /** @var string */
        protected $rcSubpage;
 
@@ -540,47 +553,66 @@ abstract class ChangesListSpecialPage extends SpecialPage {
        public function execute( $subpage ) {
                $this->rcSubpage = $subpage;
 
-               $this->considerActionsForDefaultSavedQuery();
+               $this->considerActionsForDefaultSavedQuery( $subpage );
 
-               $rows = $this->getRows();
                $opts = $this->getOptions();
-               if ( $rows === false ) {
-                       $rows = new FakeResultWrapper( [] );
-               }
+               try {
+                       $rows = $this->getRows();
+                       if ( $rows === false ) {
+                               $rows = new FakeResultWrapper( [] );
+                       }
 
-               // Used by Structured UI app to get results without MW chrome
-               if ( $this->getRequest()->getVal( 'action' ) === 'render' ) {
-                       $this->getOutput()->setArticleBodyOnly( true );
-               }
+                       // Used by Structured UI app to get results without MW chrome
+                       if ( $this->getRequest()->getVal( 'action' ) === 'render' ) {
+                               $this->getOutput()->setArticleBodyOnly( true );
+                       }
 
-               // Used by "live update" and "view newest" to check
-               // if there's new changes with minimal data transfer
-               if ( $this->getRequest()->getBool( 'peek' ) ) {
-                       $code = $rows->numRows() > 0 ? 200 : 204;
-                       $this->getOutput()->setStatusCode( $code );
-                       return;
-               }
+                       // Used by "live update" and "view newest" to check
+                       // if there's new changes with minimal data transfer
+                       if ( $this->getRequest()->getBool( 'peek' ) ) {
+                               $code = $rows->numRows() > 0 ? 200 : 204;
+                               $this->getOutput()->setStatusCode( $code );
 
-               $batch = new LinkBatch;
-               foreach ( $rows as $row ) {
-                       $batch->add( NS_USER, $row->rc_user_text );
-                       $batch->add( NS_USER_TALK, $row->rc_user_text );
-                       $batch->add( $row->rc_namespace, $row->rc_title );
-                       if ( $row->rc_source === RecentChange::SRC_LOG ) {
-                               $formatter = LogFormatter::newFromRow( $row );
-                               foreach ( $formatter->getPreloadTitles() as $title ) {
-                                       $batch->addObj( $title );
+                               if ( $this->getUser()->isAnon() !==
+                                       $this->getRequest()->getFuzzyBool( 'isAnon' )
+                               ) {
+                                       $this->getOutput()->setStatusCode( 205 );
                                }
+
+                               return;
                        }
-               }
-               $batch->execute();
 
-               $this->setHeaders();
-               $this->outputHeader();
-               $this->addModules();
-               $this->webOutput( $rows, $opts );
+                       $batch = new LinkBatch;
+                       foreach ( $rows as $row ) {
+                               $batch->add( NS_USER, $row->rc_user_text );
+                               $batch->add( NS_USER_TALK, $row->rc_user_text );
+                               $batch->add( $row->rc_namespace, $row->rc_title );
+                               if ( $row->rc_source === RecentChange::SRC_LOG ) {
+                                       $formatter = LogFormatter::newFromRow( $row );
+                                       foreach ( $formatter->getPreloadTitles() as $title ) {
+                                               $batch->addObj( $title );
+                                       }
+                               }
+                       }
+                       $batch->execute();
+
+                       $this->setHeaders();
+                       $this->outputHeader();
+                       $this->addModules();
+                       $this->webOutput( $rows, $opts );
 
-               $rows->free();
+                       $rows->free();
+               } catch ( DBQueryTimeoutError $timeoutException ) {
+                       MWExceptionHandler::logException( $timeoutException );
+
+                       $this->setHeaders();
+                       $this->outputHeader();
+                       $this->addModules();
+
+                       $this->getOutput()->setStatusCode( 500 );
+                       $this->webOutputHeader( 0, $opts );
+                       $this->outputTimeout();
+               }
 
                if ( $this->getConfig()->get( 'EnableWANCacheReaper' ) ) {
                        // Clean up any bad page entries for titles showing up in RC
@@ -597,9 +629,11 @@ abstract class ChangesListSpecialPage extends SpecialPage {
         * Check whether or not the page should load defaults, and if so, whether
         * a default saved query is relevant to be redirected to. If it is relevant,
         * redirect properly with all necessary query parameters.
+        *
+        * @param string $subpage
         */
-       protected function considerActionsForDefaultSavedQuery() {
-               if ( !$this->isStructuredFilterUiEnabled() ) {
+       protected function considerActionsForDefaultSavedQuery( $subpage ) {
+               if ( !$this->isStructuredFilterUiEnabled() || $this->including() ) {
                        return;
                }
 
@@ -645,7 +679,7 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                                        // but are still valid and requested in the URL
                                        $query = array_merge( $this->getRequest()->getValues(), $query );
                                        unset( $query[ 'title' ] );
-                                       $this->getOutput()->redirect( $this->getPageTitle()->getCanonicalURL( $query ) );
+                                       $this->getOutput()->redirect( $this->getPageTitle( $subpage )->getCanonicalURL( $query ) );
                                } else {
                                        // There's a default, but the version is not 2, and the server can't
                                        // actually recognize the query itself. This happens if it is before
@@ -672,7 +706,7 @@ abstract class ChangesListSpecialPage extends SpecialPage {
         */
        protected function includeRcFiltersApp() {
                $out = $this->getOutput();
-               if ( $this->isStructuredFilterUiEnabled() ) {
+               if ( $this->isStructuredFilterUiEnabled() && !$this->including() ) {
                        $jsData = $this->getStructuredFilterJsData();
 
                        $messages = [];
@@ -709,6 +743,14 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                                'wgStructuredChangeFiltersSavedQueriesPreferenceName',
                                static::$savedQueriesPreferenceName
                        );
+                       $out->addJsConfigVars(
+                               'wgStructuredChangeFiltersLimitPreferenceName',
+                               static::$limitPreferenceName
+                       );
+                       $out->addJsConfigVars(
+                               'wgStructuredChangeFiltersDaysPreferenceName',
+                               static::$daysPreferenceName
+                       );
 
                        $out->addJsConfigVars(
                                'StructuredChangeFiltersLiveUpdatePollingRate',
@@ -791,6 +833,17 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                );
        }
 
+       /**
+        * Add the "timeout" message to the output
+        */
+       protected function outputTimeout() {
+               $this->getOutput()->addHTML(
+                       '<div class="mw-changeslist-timeout">' .
+                       $this->msg( 'recentchanges-timeout' )->parse() .
+                       '</div>'
+               );
+       }
+
        /**
         * Get the database result for this special page instance. Used by ApiFeedRecentChanges.
         *
@@ -1388,8 +1441,10 @@ abstract class ChangesListSpecialPage extends SpecialPage {
        protected function doMainQuery( $tables, $fields, $conds,
                $query_options, $join_conds, FormOptions $opts
        ) {
-               $tables[] = 'recentchanges';
-               $fields = array_merge( RecentChange::selectFields(), $fields );
+               $rcQuery = RecentChange::getQueryInfo();
+               $tables = array_merge( $tables, $rcQuery['tables'] );
+               $fields = array_merge( $rcQuery['fields'], $fields );
+               $join_conds = array_merge( $join_conds, $rcQuery['joins'] );
 
                ChangeTags::modifyDisplayQuery(
                        $tables,
@@ -1437,16 +1492,26 @@ abstract class ChangesListSpecialPage extends SpecialPage {
        }
 
        /**
-        * Send output to the OutputPage object, only called if not used feeds
+        * Send header output to the OutputPage object, only called if not using feeds
         *
-        * @param ResultWrapper $rows Database rows
+        * @param int $rowCount Number of database rows
         * @param FormOptions $opts
         */
-       public function webOutput( $rows, $opts ) {
+       private function webOutputHeader( $rowCount, $opts ) {
                if ( !$this->including() ) {
                        $this->outputFeedLinks();
-                       $this->doHeader( $opts, $rows->numRows() );
+                       $this->doHeader( $opts, $rowCount );
                }
+       }
+
+       /**
+        * Send output to the OutputPage object, only called if not used feeds
+        *
+        * @param ResultWrapper $rows Database rows
+        * @param FormOptions $opts
+        */
+       public function webOutput( $rows, $opts ) {
+               $this->webOutputHeader( $rows->numRows(), $opts );
 
                $this->outputChangesList( $rows, $opts );
        }
@@ -1560,8 +1625,13 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                # Collapsible
                $collapsedState = $this->getRequest()->getCookie( 'changeslist-state' );
                $collapsedClass = $collapsedState === 'collapsed' ? ' mw-collapsed' : '';
+               # Enhanced mode
+               $enhancedMode = $this->getRequest()->getBool( 'enhanced', $user->getOption( 'usenewrc' ) );
+               $enhancedClass = $enhancedMode ? ' mw-enhanced' : '';
+
+               $legendClasses = $collapsedClass . $enhancedClass;
                $legend =
-                       '<div class="mw-changeslist-legend mw-collapsible' . $collapsedClass . '">' .
+                       '<div class="mw-changeslist-legend mw-collapsible' . $legendClasses . '">' .
                                $legendHeading .
                                '<div class="mw-collapsible-content">' . $legend . '</div>' .
                        '</div>';
@@ -1581,7 +1651,7 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                ] );
                $out->addModules( 'mediawiki.special.changeslist.legend.js' );
 
-               if ( $this->isStructuredFilterUiEnabled() ) {
+               if ( $this->isStructuredFilterUiEnabled() && !$this->including() ) {
                        $out->addModules( 'mediawiki.rcfilters.filters.ui' );
                        $out->addModuleStyles( 'mediawiki.rcfilters.filters.base.styles' );
                }
@@ -1717,11 +1787,10 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                        return true;
                }
 
-               if ( $this->getConfig()->get( 'StructuredChangeFiltersShowPreference' ) ) {
-                       return !$this->getUser()->getOption( 'rcenhancedfilters-disable' );
-               } else {
-                       return $this->getUser()->getOption( 'rcenhancedfilters' );
-               }
+               return static::checkStructuredFilterUiEnabled(
+                       $this->getConfig(),
+                       $this->getUser()
+               );
        }
 
        /**
@@ -1738,14 +1807,42 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                }
        }
 
-       abstract function getDefaultLimit();
+       /**
+        * Static method to check whether StructuredFilter UI is enabled for the given user
+        *
+        * @since 1.31
+        * @param Config $config
+        * @param User $user User object
+        * @return bool
+        */
+       public static function checkStructuredFilterUiEnabled( Config $config, User $user ) {
+               if ( $config->get( 'StructuredChangeFiltersShowPreference' ) ) {
+                       return !$user->getOption( 'rcenhancedfilters-disable' );
+               } else {
+                       return $user->getOption( 'rcenhancedfilters' );
+               }
+       }
+
+       /**
+        * Get the default value of the number of changes to display when loading
+        * the result set.
+        *
+        * @since 1.30
+        * @return int
+        */
+       public function getDefaultLimit() {
+               return $this->getUser()->getIntOption( static::$limitPreferenceName );
+       }
 
        /**
         * Get the default value of the number of days to display when loading
         * the result set.
         * Supports fractional values, and should be cast to a float.
         *
+        * @since 1.30
         * @return float
         */
-       abstract function getDefaultDays();
+       public function getDefaultDays() {
+               return floatval( $this->getUser()->getOption( static::$daysPreferenceName ) );
+       }
 }