Merge "Allow API action=logout with BotPasswords"
[lhc/web/wiklou.git] / maintenance / Maintenance.php
index 185a1f4..777eddd 100644 (file)
@@ -63,19 +63,19 @@ abstract class Maintenance {
        const STDIN_ALL = 'all';
 
        // This is the desired params
-       protected $mParams = array();
+       protected $mParams = [];
 
        // Array of mapping short parameters to long ones
-       protected $mShortParamsMap = array();
+       protected $mShortParamsMap = [];
 
        // Array of desired args
-       protected $mArgList = array();
+       protected $mArgList = [];
 
        // This is the list of options that were actually passed
-       protected $mOptions = array();
+       protected $mOptions = [];
 
        // This is the list of arguments that were actually passed
-       protected $mArgs = array();
+       protected $mArgs = [];
 
        // Name of the script currently running
        protected $mSelf;
@@ -84,7 +84,7 @@ abstract class Maintenance {
        protected $mQuiet = false;
        protected $mDbUser, $mDbPass;
 
-       // A description of the script, children should change this
+       // A description of the script, children should change this via addDescription()
        protected $mDescription = '';
 
        // Have we already loaded our user input?
@@ -99,16 +99,19 @@ abstract class Maintenance {
        protected $mBatchSize = null;
 
        // Generic options added by addDefaultParams()
-       private $mGenericParameters = array();
+       private $mGenericParameters = [];
        // Generic options which might or not be supported by the script
-       private $mDependantParameters = array();
+       private $mDependantParameters = [];
 
        /**
         * Used by getDB() / setDB()
-        * @var DatabaseBase
+        * @var IDatabase
         */
        private $mDb = null;
 
+       /** @var float UNIX timestamp */
+       private $lastSlaveWait = 0.0;
+
        /**
         * Used when creating separate schema files.
         * @var resource
@@ -123,16 +126,17 @@ abstract class Maintenance {
        private $config;
 
        /**
-        * Used to read the options in the order
-        * they were passed. Useful for option
-        * chaining. (Ex. dumpBackup.php)
+        * Used to read the options in the order they were passed.
+        * Useful for option chaining (Ex. dumpBackup.php). It will
+        * be an empty array if the options are passed in through
+        * loadParamsAndArgs( $self, $opts, $args ).
         *
         * This is an array of arrays where
-        * 0 => the option and 1 => parameter value
+        * 0 => the option and 1 => parameter value.
         *
         * @var array
         */
-       public $orderedOptions = array();
+       public $orderedOptions = [];
 
        /**
         * Default constructor. Children should call this *first* if implementing
@@ -146,7 +150,7 @@ abstract class Maintenance {
                        : realpath( __DIR__ . '/..' );
 
                $this->addDefaultParams();
-               register_shutdown_function( array( $this, 'outputChanneled' ), false );
+               register_shutdown_function( [ $this, 'outputChanneled' ], false );
        }
 
        /**
@@ -172,7 +176,7 @@ abstract class Maintenance {
                if ( $bt[0]['class'] !== 'Maintenance' || $bt[0]['function'] !== 'shouldExecute' ) {
                        return false; // last call should be to this function
                }
-               $includeFuncs = array( 'require_once', 'require', 'include', 'include_once' );
+               $includeFuncs = [ 'require_once', 'require', 'include', 'include_once' ];
                for ( $i = 1; $i < $count; $i++ ) {
                        if ( !in_array( $bt[$i]['function'], $includeFuncs ) ) {
                                return false; // previous calls should all be "requires"
@@ -201,13 +205,13 @@ abstract class Maintenance {
        protected function addOption( $name, $description, $required = false,
                $withArg = false, $shortName = false, $multiOccurrence = false
        ) {
-               $this->mParams[$name] = array(
+               $this->mParams[$name] = [
                        'desc' => $description,
                        'require' => $required,
                        'withArg' => $withArg,
                        'shortName' => $shortName,
                        'multiOccurrence' => $multiOccurrence
-               );
+               ];
 
                if ( $shortName !== false ) {
                        $this->mShortParamsMap[$shortName] = $name;
@@ -251,11 +255,11 @@ abstract class Maintenance {
         * @param bool $required Is this required?
         */
        protected function addArg( $arg, $description, $required = true ) {
-               $this->mArgList[] = array(
+               $this->mArgList[] = [
                        'name' => $arg,
                        'desc' => $description,
                        'require' => $required
-               );
+               ];
        }
 
        /**
@@ -631,9 +635,9 @@ abstract class Maintenance {
                if ( is_array( $wgProfiler ) && isset( $wgProfiler['class'] ) ) {
                        $class = $wgProfiler['class'];
                        $profiler = new $class(
-                               array( 'sampling' => 1, 'output' => array( $output ) )
+                               [ 'sampling' => 1, 'output' => [ $output ] ]
                                        + $wgProfiler
-                                       + array( 'threshold' => $wgProfileLimit )
+                                       + [ 'threshold' => $wgProfileLimit ]
                        );
                        $profiler->setTemplated( true );
                        Profiler::replaceStubInstance( $profiler );
@@ -648,50 +652,22 @@ abstract class Maintenance {
         * Clear all params and arguments.
         */
        public function clearParamsAndArgs() {
-               $this->mOptions = array();
-               $this->mArgs = array();
+               $this->mOptions = [];
+               $this->mArgs = [];
                $this->mInputLoaded = false;
        }
 
        /**
-        * Process command line arguments
-        * $mOptions becomes an array with keys set to the option names
-        * $mArgs becomes a zero-based array containing the non-option arguments
+        * Load params and arguments from a given array
+        * of command-line arguments
         *
-        * @param string $self The name of the script, if any
-        * @param array $opts An array of options, in form of key=>value
-        * @param array $args An array of command line arguments
+        * @since 1.27
+        * @param array $argv
         */
-       public function loadParamsAndArgs( $self = null, $opts = null, $args = null ) {
-               # If we were given opts or args, set those and return early
-               if ( $self ) {
-                       $this->mSelf = $self;
-                       $this->mInputLoaded = true;
-               }
-               if ( $opts ) {
-                       $this->mOptions = $opts;
-                       $this->mInputLoaded = true;
-               }
-               if ( $args ) {
-                       $this->mArgs = $args;
-                       $this->mInputLoaded = true;
-               }
-
-               # If we've already loaded input (either by user values or from $argv)
-               # skip on loading it again. The array_shift() will corrupt values if
-               # it's run again and again
-               if ( $this->mInputLoaded ) {
-                       $this->loadSpecialVars();
-
-                       return;
-               }
-
-               global $argv;
-               $this->mSelf = array_shift( $argv );
-
-               $options = array();
-               $args = array();
-               $this->orderedOptions = array();
+       public function loadWithArgv( $argv ) {
+               $options = [];
+               $args = [];
+               $this->orderedOptions = [];
 
                # Parse arguments
                for ( $arg = reset( $argv ); $arg !== false; $arg = next( $argv ) ) {
@@ -772,7 +748,7 @@ abstract class Maintenance {
         * @param mixed $value
         */
        private function setParam( &$options, $option, $value ) {
-               $this->orderedOptions[] = array( $option, $value );
+               $this->orderedOptions[] = [ $option, $value ];
 
                if ( isset( $this->mParams[$option] ) ) {
                        $multi = $this->mParams[$option]['multiOccurrence'];
@@ -780,7 +756,7 @@ abstract class Maintenance {
                        if ( $multi && $exists ) {
                                $options[$option][] = $value;
                        } elseif ( $multi ) {
-                               $options[$option] = array( $value );
+                               $options[$option] = [ $value ];
                        } elseif ( !$exists ) {
                                $options[$option] = $value;
                        } else {
@@ -790,6 +766,44 @@ abstract class Maintenance {
                }
        }
 
+       /**
+        * Process command line arguments
+        * $mOptions becomes an array with keys set to the option names
+        * $mArgs becomes a zero-based array containing the non-option arguments
+        *
+        * @param string $self The name of the script, if any
+        * @param array $opts An array of options, in form of key=>value
+        * @param array $args An array of command line arguments
+        */
+       public function loadParamsAndArgs( $self = null, $opts = null, $args = null ) {
+               # If we were given opts or args, set those and return early
+               if ( $self ) {
+                       $this->mSelf = $self;
+                       $this->mInputLoaded = true;
+               }
+               if ( $opts ) {
+                       $this->mOptions = $opts;
+                       $this->mInputLoaded = true;
+               }
+               if ( $args ) {
+                       $this->mArgs = $args;
+                       $this->mInputLoaded = true;
+               }
+
+               # If we've already loaded input (either by user values or from $argv)
+               # skip on loading it again. The array_shift() will corrupt values if
+               # it's run again and again
+               if ( $this->mInputLoaded ) {
+                       $this->loadSpecialVars();
+
+                       return;
+               }
+
+               global $argv;
+               $this->mSelf = $argv[0];
+               $this->loadWithArgv( array_slice( $argv, 1 ) );
+       }
+
        /**
         * Run some validation checks on the params, etc
         */
@@ -1071,11 +1085,11 @@ abstract class Maintenance {
        public function purgeRedundantText( $delete = true ) {
                # Data should come off the master, wrapped in a transaction
                $dbw = $this->getDB( DB_MASTER );
-               $dbw->begin( __METHOD__ );
+               $this->beginTransaction( $dbw, __METHOD__ );
 
                # Get "active" text records from the revisions table
                $this->output( 'Searching for active text records in revisions table...' );
-               $res = $dbw->select( 'revision', 'rev_text_id', array(), __METHOD__, array( 'DISTINCT' ) );
+               $res = $dbw->select( 'revision', 'rev_text_id', [], __METHOD__, [ 'DISTINCT' ] );
                foreach ( $res as $row ) {
                        $cur[] = $row->rev_text_id;
                }
@@ -1083,7 +1097,7 @@ abstract class Maintenance {
 
                # Get "active" text records from the archive table
                $this->output( 'Searching for active text records in archive table...' );
-               $res = $dbw->select( 'archive', 'ar_text_id', array(), __METHOD__, array( 'DISTINCT' ) );
+               $res = $dbw->select( 'archive', 'ar_text_id', [], __METHOD__, [ 'DISTINCT' ] );
                foreach ( $res as $row ) {
                        # old pre-MW 1.5 records can have null ar_text_id's.
                        if ( $row->ar_text_id !== null ) {
@@ -1095,8 +1109,8 @@ abstract class Maintenance {
                # Get the IDs of all text records not in these sets
                $this->output( 'Searching for inactive text records...' );
                $cond = 'old_id NOT IN ( ' . $dbw->makeList( $cur ) . ' )';
-               $res = $dbw->select( 'text', 'old_id', array( $cond ), __METHOD__, array( 'DISTINCT' ) );
-               $old = array();
+               $res = $dbw->select( 'text', 'old_id', [ $cond ], __METHOD__, [ 'DISTINCT' ] );
+               $old = [];
                foreach ( $res as $row ) {
                        $old[] = $row->old_id;
                }
@@ -1109,12 +1123,12 @@ abstract class Maintenance {
                # Delete as appropriate
                if ( $delete && $count ) {
                        $this->output( 'Deleting...' );
-                       $dbw->delete( 'text', array( 'old_id' => $old ), __METHOD__ );
+                       $dbw->delete( 'text', [ 'old_id' => $old ], __METHOD__ );
                        $this->output( "done.\n" );
                }
 
                # Done
-               $dbw->commit( __METHOD__ );
+               $this->commitTransaction( $dbw, __METHOD__ );
        }
 
        /**
@@ -1130,9 +1144,12 @@ abstract class Maintenance {
         * If not set, wfGetDB() will be used.
         * This function has the same parameters as wfGetDB()
         *
-        * @return DatabaseBase
+        * @param integer $db DB index (DB_SLAVE/DB_MASTER)
+        * @param array $groups; default: empty array
+        * @param string|bool $wiki; default: current wiki
+        * @return IDatabase
         */
-       protected function getDB( $db, $groups = array(), $wiki = false ) {
+       protected function getDB( $db, $groups = [], $wiki = false ) {
                if ( is_null( $this->mDb ) ) {
                        return wfGetDB( $db, $groups, $wiki );
                } else {
@@ -1143,19 +1160,67 @@ abstract class Maintenance {
        /**
         * Sets database object to be returned by getDB().
         *
-        * @param DatabaseBase $db Database object to be used
+        * @param IDatabase $db Database object to be used
         */
-       public function setDB( $db ) {
+       public function setDB( IDatabase $db ) {
                $this->mDb = $db;
        }
 
+       /**
+        * Begin a transcation on a DB
+        *
+        * This method makes it clear that begin() is called from a maintenance script,
+        * which has outermost scope. This is safe, unlike $dbw->begin() called in other places.
+        *
+        * @param IDatabase $dbw
+        * @param string $fname Caller name
+        * @since 1.27
+        */
+       protected function beginTransaction( IDatabase $dbw, $fname ) {
+               $dbw->begin( $fname );
+       }
+
+       /**
+        * Commit the transcation on a DB handle and wait for slaves to catch up
+        *
+        * This method makes it clear that commit() is called from a maintenance script,
+        * which has outermost scope. This is safe, unlike $dbw->commit() called in other places.
+        *
+        * @param IDatabase $dbw
+        * @param string $fname Caller name
+        * @return bool Whether the slave wait succeeded
+        * @since 1.27
+        */
+       protected function commitTransaction( IDatabase $dbw, $fname ) {
+               $dbw->commit( $fname );
+
+               $ok = wfWaitForSlaves( $this->lastSlaveWait, false, '*', 30 );
+               $this->lastSlaveWait = microtime( true );
+
+               return $ok;
+       }
+
+       /**
+        * Rollback the transcation on a DB handle
+        *
+        * This method makes it clear that rollback() is called from a maintenance script,
+        * which has outermost scope. This is safe, unlike $dbw->rollback() called in other places.
+        *
+        * @param IDatabase $dbw
+        * @param string $fname Caller name
+        * @since 1.27
+        */
+       protected function rollbackTransaction( IDatabase $dbw, $fname ) {
+               $dbw->rollback( $fname );
+       }
+
        /**
         * Lock the search index
         * @param DatabaseBase &$db
         */
        private function lockSearchindex( $db ) {
-               $write = array( 'searchindex' );
-               $read = array(
+               $write = [ 'searchindex' ];
+               $read = [
                        'page',
                        'revision',
                        'text',
@@ -1163,7 +1228,7 @@ abstract class Maintenance {
                        'l10n_cache',
                        'user',
                        'page_restrictions'
-               );
+               ];
                $db->lockTables( $read, $write, __CLASS__ . '::' . __METHOD__ );
        }
 
@@ -1306,13 +1371,13 @@ abstract class Maintenance {
         * @return string
         */
        private static function readlineEmulation( $prompt ) {
-               $bash = Installer::locateExecutableInDefaultPaths( array( 'bash' ) );
+               $bash = Installer::locateExecutableInDefaultPaths( [ 'bash' ] );
                if ( !wfIsWindows() && $bash ) {
                        $retval = false;
                        $encPrompt = wfEscapeShellArg( $prompt );
                        $command = "read -er -p $encPrompt && echo \"\$REPLY\"";
                        $encCommand = wfEscapeShellArg( $command );
-                       $line = wfShellExec( "$bash -c $encCommand", $retval, array(), array( 'walltime' => 0 ) );
+                       $line = wfShellExec( "$bash -c $encCommand", $retval, [], [ 'walltime' => 0 ] );
 
                        if ( $retval == 0 ) {
                                return $line;
@@ -1363,7 +1428,7 @@ abstract class LoggedUpdateMaintenance extends Maintenance {
                $key = $this->getUpdateKey();
 
                if ( !$this->hasOption( 'force' )
-                       && $db->selectRow( 'updatelog', '1', array( 'ul_key' => $key ), __METHOD__ )
+                       && $db->selectRow( 'updatelog', '1', [ 'ul_key' => $key ], __METHOD__ )
                ) {
                        $this->output( "..." . $this->updateSkippedMessage() . "\n" );
 
@@ -1374,7 +1439,7 @@ abstract class LoggedUpdateMaintenance extends Maintenance {
                        return false;
                }
 
-               if ( $db->insert( 'updatelog', array( 'ul_key' => $key ), __METHOD__, 'IGNORE' ) ) {
+               if ( $db->insert( 'updatelog', [ 'ul_key' => $key ], __METHOD__, 'IGNORE' ) ) {
                        return true;
                } else {
                        $this->output( $this->updatelogFailedMessage() . "\n" );