]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - maintenance/Maintenance.php
MediaWiki 1.17.0
[autoinstallsdev/mediawiki.git] / maintenance / Maintenance.php
1 <?php
2 /**
3  * @file
4  * @ingroup Maintenance
5  * @defgroup Maintenance Maintenance
6  */
7
8 // Define this so scripts can easily find doMaintenance.php
9 define( 'RUN_MAINTENANCE_IF_MAIN', dirname( __FILE__ ) . '/doMaintenance.php' );
10 define( 'DO_MAINTENANCE', RUN_MAINTENANCE_IF_MAIN ); // original name, harmless
11
12 $maintClass = false;
13
14 // Make sure we're on PHP5 or better
15 if ( version_compare( PHP_VERSION, '5.2.3' ) < 0 ) {
16         die ( "Sorry! This version of MediaWiki requires PHP 5.2.3; you are running " .
17                 PHP_VERSION . ".\n\n" .
18                 "If you are sure you already have PHP 5.2.3 or higher installed, it may be\n" .
19                 "installed in a different path from PHP " . PHP_VERSION . ". Check with your system\n" .
20                 "administrator.\n" );
21 }
22
23 // Wrapper for posix_isatty()
24 if ( !function_exists( 'posix_isatty' ) ) {
25         # We default as considering stdin a tty (for nice readline methods)
26         # but treating stout as not a tty to avoid color codes
27         function posix_isatty( $fd ) {
28                 return !$fd;
29         }
30 }
31
32 /**
33  * Abstract maintenance class for quickly writing and churning out
34  * maintenance scripts with minimal effort. All that _must_ be defined
35  * is the execute() method. See docs/maintenance.txt for more info
36  * and a quick demo of how to use it.
37  *
38  * This program is free software; you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation; either version 2 of the License, or
41  * (at your option) any later version.
42  *
43  * This program is distributed in the hope that it will be useful,
44  * but WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
46  * GNU General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License along
49  * with this program; if not, write to the Free Software Foundation, Inc.,
50  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
51  * http://www.gnu.org/copyleft/gpl.html
52  *
53  * @author Chad Horohoe <chad@anyonecanedit.org>
54  * @since 1.16
55  * @ingroup Maintenance
56  */
57 abstract class Maintenance {
58
59         /**
60          * Constants for DB access type
61          * @see Maintenance::getDbType()
62          */
63         const DB_NONE  = 0;
64         const DB_STD   = 1;
65         const DB_ADMIN = 2;
66
67         // Const for getStdin()
68         const STDIN_ALL = 'all';
69
70         // This is the desired params
71         protected $mParams = array();
72
73         // Array of desired args
74         protected $mArgList = array();
75
76         // This is the list of options that were actually passed
77         protected $mOptions = array();
78
79         // This is the list of arguments that were actually passed
80         protected $mArgs = array();
81
82         // Name of the script currently running
83         protected $mSelf;
84
85         // Special vars for params that are always used
86         protected $mQuiet = false;
87         protected $mDbUser, $mDbPass;
88
89         // A description of the script, children should change this
90         protected $mDescription = '';
91
92         // Have we already loaded our user input?
93         protected $mInputLoaded = false;
94
95         // Batch size. If a script supports this, they should set
96         // a default with setBatchSize()
97         protected $mBatchSize = null;
98
99         /**
100          * List of all the core maintenance scripts. This is added
101          * to scripts added by extensions in $wgMaintenanceScripts
102          * and returned by getMaintenanceScripts()
103          */
104         protected static $mCoreScripts = null;
105
106         /**
107          * Default constructor. Children should call this *first* if implementing
108          * their own constructors
109          */
110         public function __construct() {
111                 // Setup $IP, using MW_INSTALL_PATH if it exists
112                 global $IP;
113                 $IP = strval( getenv( 'MW_INSTALL_PATH' ) ) !== ''
114                         ? getenv( 'MW_INSTALL_PATH' )
115                         : realpath( dirname( __FILE__ ) . '/..' );
116
117                 $this->addDefaultParams();
118                 register_shutdown_function( array( $this, 'outputChanneled' ), false );
119         }
120
121         /**
122          * Should we execute the maintenance script, or just allow it to be included
123          * as a standalone class? It checks that the call stack only includes this
124          * function and a require (meaning was called from the file scope)
125          *
126          * @return Boolean
127          */
128         public static function shouldExecute() {
129                 $bt = debug_backtrace();
130                 if( count( $bt ) !== 2 ) {
131                         return false;
132                 }
133                 return $bt[1]['function'] == 'require_once' &&
134                         $bt[0]['class'] == 'Maintenance' &&
135                         $bt[0]['function'] == 'shouldExecute';
136         }
137
138         /**
139          * Do the actual work. All child classes will need to implement this
140          */
141         abstract public function execute();
142
143         /**
144          * Add a parameter to the script. Will be displayed on --help
145          * with the associated description
146          *
147          * @param $name String: the name of the param (help, version, etc)
148          * @param $description String: the description of the param to show on --help
149          * @param $required Boolean: is the param required?
150          * @param $withArg Boolean: is an argument required with this option?
151          */
152         protected function addOption( $name, $description, $required = false, $withArg = false ) {
153                 $this->mParams[$name] = array( 'desc' => $description, 'require' => $required, 'withArg' => $withArg );
154         }
155
156         /**
157          * Checks to see if a particular param exists.
158          * @param $name String: the name of the param
159          * @return Boolean
160          */
161         protected function hasOption( $name ) {
162                 return isset( $this->mOptions[$name] );
163         }
164
165         /**
166          * Get an option, or return the default
167          * @param $name String: the name of the param
168          * @param $default Mixed: anything you want, default null
169          * @return Mixed
170          */
171         protected function getOption( $name, $default = null ) {
172                 if ( $this->hasOption( $name ) ) {
173                         return $this->mOptions[$name];
174                 } else {
175                         // Set it so we don't have to provide the default again
176                         $this->mOptions[$name] = $default;
177                         return $this->mOptions[$name];
178                 }
179         }
180
181         /**
182          * Add some args that are needed
183          * @param $arg String: name of the arg, like 'start'
184          * @param $description String: short description of the arg
185          * @param $required Boolean: is this required?
186          */
187         protected function addArg( $arg, $description, $required = true ) {
188                 $this->mArgList[] = array(
189                         'name' => $arg,
190                         'desc' => $description,
191                         'require' => $required
192                 );
193         }
194
195         /**
196          * Remove an option.  Useful for removing options that won't be used in your script.
197          * @param $name String: the option to remove.
198          */
199         protected function deleteOption( $name ) {
200                 unset( $this->mParams[$name] );
201         }
202
203         /**
204          * Set the description text.
205          * @param $text String: the text of the description
206          */
207         protected function addDescription( $text ) {
208                 $this->mDescription = $text;
209         }
210
211         /**
212          * Does a given argument exist?
213          * @param $argId Integer: the integer value (from zero) for the arg
214          * @return Boolean
215          */
216         protected function hasArg( $argId = 0 ) {
217                 return isset( $this->mArgs[$argId] );
218         }
219
220         /**
221          * Get an argument.
222          * @param $argId Integer: the integer value (from zero) for the arg
223          * @param $default Mixed: the default if it doesn't exist
224          * @return mixed
225          */
226         protected function getArg( $argId = 0, $default = null ) {
227                 return $this->hasArg( $argId ) ? $this->mArgs[$argId] : $default;
228         }
229
230         /**
231          * Set the batch size.
232          * @param $s Integer: the number of operations to do in a batch
233          */
234         protected function setBatchSize( $s = 0 ) {
235                 $this->mBatchSize = $s;
236         }
237
238         /**
239          * Get the script's name
240          * @return String
241          */
242         public function getName() {
243                 return $this->mSelf;
244         }
245
246         /**
247          * Return input from stdin.
248          * @param $len Integer: the number of bytes to read. If null,
249          *        just return the handle. Maintenance::STDIN_ALL returns
250          *        the full length
251          * @return Mixed
252          */
253         protected function getStdin( $len = null ) {
254                 if ( $len == Maintenance::STDIN_ALL ) {
255                         return file_get_contents( 'php://stdin' );
256                 }
257                 $f = fopen( 'php://stdin', 'rt' );
258                 if ( !$len ) {
259                         return $f;
260                 }
261                 $input = fgets( $f, $len );
262                 fclose( $f );
263                 return rtrim( $input );
264         }
265
266         public function isQuiet() {
267                 return $this->mQuiet;
268         }
269
270         /**
271          * Throw some output to the user. Scripts can call this with no fears,
272          * as we handle all --quiet stuff here
273          * @param $out String: the text to show to the user
274          * @param $channel Mixed: unique identifier for the channel. See
275          *     function outputChanneled.
276          */
277         protected function output( $out, $channel = null ) {
278                 if ( $this->mQuiet ) {
279                         return;
280                 }
281                 if ( $channel === null ) {
282                         $this->cleanupChanneled();
283
284                         $f = fopen( 'php://stdout', 'w' );
285                         fwrite( $f, $out );
286                         fclose( $f );
287                 }
288                 else {
289                         $out = preg_replace( '/\n\z/', '', $out );
290                         $this->outputChanneled( $out, $channel );
291                 }
292         }
293
294         /**
295          * Throw an error to the user. Doesn't respect --quiet, so don't use
296          * this for non-error output
297          * @param $err String: the error to display
298          * @param $die Boolean: If true, go ahead and die out.
299          */
300         protected function error( $err, $die = false ) {
301                 $this->outputChanneled( false );
302                 if ( php_sapi_name() == 'cli' ) {
303                         fwrite( STDERR, $err . "\n" );
304                 } else {
305                         $f = fopen( 'php://stderr', 'w' );
306                         fwrite( $f, $err . "\n" );
307                         fclose( $f );
308                 }
309                 if ( $die ) {
310                         die();
311                 }
312         }
313
314         private $atLineStart = true;
315         private $lastChannel = null;
316
317         /**
318          * Clean up channeled output.  Output a newline if necessary.
319          */
320         public function cleanupChanneled() {
321                 if ( !$this->atLineStart ) {
322                         $handle = fopen( 'php://stdout', 'w' );
323                         fwrite( $handle, "\n" );
324                         fclose( $handle );
325                         $this->atLineStart = true;
326                 }
327         }
328
329         /**
330          * Message outputter with channeled message support. Messages on the
331          * same channel are concatenated, but any intervening messages in another
332          * channel start a new line.
333          * @param $msg String: the message without trailing newline
334          * @param $channel Channel identifier or null for no
335          *     channel. Channel comparison uses ===.
336          */
337         public function outputChanneled( $msg, $channel = null ) {
338                 if ( $msg === false ) {
339                         $this->cleanupChanneled();
340                         return;
341                 }
342
343                 $handle = fopen( 'php://stdout', 'w' );
344
345                 // End the current line if necessary
346                 if ( !$this->atLineStart && $channel !== $this->lastChannel ) {
347                         fwrite( $handle, "\n" );
348                 }
349
350                 fwrite( $handle, $msg );
351
352                 $this->atLineStart = false;
353                 if ( $channel === null ) {
354                         // For unchanneled messages, output trailing newline immediately
355                         fwrite( $handle, "\n" );
356                         $this->atLineStart = true;
357                 }
358                 $this->lastChannel = $channel;
359
360                 // Cleanup handle
361                 fclose( $handle );
362         }
363
364         /**
365          * Does the script need different DB access? By default, we give Maintenance
366          * scripts normal rights to the DB. Sometimes, a script needs admin rights
367          * access for a reason and sometimes they want no access. Subclasses should
368          * override and return one of the following values, as needed:
369          *    Maintenance::DB_NONE  -  For no DB access at all
370          *    Maintenance::DB_STD   -  For normal DB access, default
371          *    Maintenance::DB_ADMIN -  For admin DB access
372          * @return Integer
373          */
374         public function getDbType() {
375                 return Maintenance::DB_STD;
376         }
377
378         /**
379          * Add the default parameters to the scripts
380          */
381         protected function addDefaultParams() {
382                 $this->addOption( 'help', 'Display this help message' );
383                 $this->addOption( 'quiet', 'Whether to supress non-error output' );
384                 $this->addOption( 'conf', 'Location of LocalSettings.php, if not default', false, true );
385                 $this->addOption( 'wiki', 'For specifying the wiki ID', false, true );
386                 $this->addOption( 'globals', 'Output globals at the end of processing for debugging' );
387                 $this->addOption( 'memory-limit', 'Set a specific memory limit for the script, "max" for no limit or "default" to avoid changing it' );
388                 $this->addOption( 'server', "The protocol and server name to use in URLs, e.g. " .
389                                 "http://en.wikipedia.org. This is sometimes necessary because " .
390                                 "server name detection may fail in command line scripts.", false, true );
391                 // If we support a DB, show the options
392                 if ( $this->getDbType() > 0 ) {
393                         $this->addOption( 'dbuser', 'The DB user to use for this script', false, true );
394                         $this->addOption( 'dbpass', 'The password to use for this script', false, true );
395                 }
396                 // If we support $mBatchSize, show the option
397                 if ( $this->mBatchSize ) {
398                         $this->addOption( 'batch-size', 'Run this many operations ' .
399                                 'per batch, default: ' . $this->mBatchSize, false, true );
400                 }
401         }
402
403         /**
404          * Run a child maintenance script. Pass all of the current arguments
405          * to it.
406          * @param $maintClass String: a name of a child maintenance class
407          * @param $classFile String: full path of where the child is
408          * @return Maintenance child
409          */
410         public function runChild( $maintClass, $classFile = null ) {
411                 // Make sure the class is loaded first
412                 if ( !class_exists( $maintClass ) ) {
413                         if ( $classFile ) {
414                                 require_once( $classFile );
415                         }
416                         if ( !class_exists( $maintClass ) ) {
417                                 $this->error( "Cannot spawn child: $maintClass" );
418                         }
419                 }
420
421                 $child = new $maintClass();
422                 $child->loadParamsAndArgs( $this->mSelf, $this->mOptions, $this->mArgs );
423                 return $child;
424         }
425
426         /**
427          * Do some sanity checking and basic setup
428          */
429         public function setup() {
430                 global $wgCommandLineMode, $wgRequestTime;
431
432                 # Abort if called from a web server
433                 if ( isset( $_SERVER ) && isset( $_SERVER['REQUEST_METHOD'] ) ) {
434                         $this->error( 'This script must be run from the command line', true );
435                 }
436
437                 # Make sure we can handle script parameters
438                 if ( !ini_get( 'register_argc_argv' ) ) {
439                         $this->error( 'Cannot get command line arguments, register_argc_argv is set to false', true );
440                 }
441
442                 if ( version_compare( phpversion(), '5.2.4' ) >= 0 ) {
443                         // Send PHP warnings and errors to stderr instead of stdout.
444                         // This aids in diagnosing problems, while keeping messages
445                         // out of redirected output.
446                         if ( ini_get( 'display_errors' ) ) {
447                                 ini_set( 'display_errors', 'stderr' );
448                         }
449
450                         // Don't touch the setting on earlier versions of PHP,
451                         // as setting it would disable output if you'd wanted it.
452
453                         // Note that exceptions are also sent to stderr when
454                         // command-line mode is on, regardless of PHP version.
455                 }
456
457                 $this->loadParamsAndArgs();
458                 $this->maybeHelp();
459
460                 # Set the memory limit
461                 # Note we need to set it again later in cache LocalSettings changed it
462                 $this->adjustMemoryLimit();
463
464                 # Set max execution time to 0 (no limit). PHP.net says that
465                 # "When running PHP from the command line the default setting is 0."
466                 # But sometimes this doesn't seem to be the case.
467                 ini_set( 'max_execution_time', 0 );
468
469                 $wgRequestTime = microtime( true );
470
471                 # Define us as being in MediaWiki
472                 define( 'MEDIAWIKI', true );
473
474                 $wgCommandLineMode = true;
475                 # Turn off output buffering if it's on
476                 @ob_end_flush();
477
478                 $this->validateParamsAndArgs();
479         }
480
481         /**
482          * Normally we disable the memory_limit when running admin scripts.
483          * Some scripts may wish to actually set a limit, however, to avoid
484          * blowing up unexpectedly. We also support a --memory-limit option,
485          * to allow sysadmins to explicitly set one if they'd prefer to override
486          * defaults (or for people using Suhosin which yells at you for trying
487          * to disable the limits)
488          */
489         public function memoryLimit() {
490                 $limit = $this->getOption( 'memory-limit', 'max' );
491                 $limit = trim( $limit, "\" '" ); // trim quotes in case someone misunderstood
492                 return $limit;
493         }
494
495         /**
496          * Adjusts PHP's memory limit to better suit our needs, if needed.
497          */
498         protected function adjustMemoryLimit() {
499                 $limit = $this->memoryLimit();
500                 if ( $limit == 'max' ) {
501                         $limit = -1; // no memory limit
502                 }
503                 if ( $limit != 'default' ) {
504                         ini_set( 'memory_limit', $limit );
505                 }
506         }
507
508         /**
509          * Clear all params and arguments.
510          */
511         public function clearParamsAndArgs() {
512                 $this->mOptions = array();
513                 $this->mArgs = array();
514                 $this->mInputLoaded = false;
515         }
516
517         /**
518          * Process command line arguments
519          * $mOptions becomes an array with keys set to the option names
520          * $mArgs becomes a zero-based array containing the non-option arguments
521          *
522          * @param $self String The name of the script, if any
523          * @param $opts Array An array of options, in form of key=>value
524          * @param $args Array An array of command line arguments
525          */
526         public function loadParamsAndArgs( $self = null, $opts = null, $args = null ) {
527                 # If we were given opts or args, set those and return early
528                 if ( $self ) {
529                         $this->mSelf = $self;
530                         $this->mInputLoaded = true;
531                 }
532                 if ( $opts ) {
533                         $this->mOptions = $opts;
534                         $this->mInputLoaded = true;
535                 }
536                 if ( $args ) {
537                         $this->mArgs = $args;
538                         $this->mInputLoaded = true;
539                 }
540
541                 # If we've already loaded input (either by user values or from $argv)
542                 # skip on loading it again. The array_shift() will corrupt values if
543                 # it's run again and again
544                 if ( $this->mInputLoaded ) {
545                         $this->loadSpecialVars();
546                         return;
547                 }
548
549                 global $argv;
550                 $this->mSelf = array_shift( $argv );
551
552                 $options = array();
553                 $args = array();
554
555                 # Parse arguments
556                 for ( $arg = reset( $argv ); $arg !== false; $arg = next( $argv ) ) {
557                         if ( $arg == '--' ) {
558                                 # End of options, remainder should be considered arguments
559                                 $arg = next( $argv );
560                                 while ( $arg !== false ) {
561                                         $args[] = $arg;
562                                         $arg = next( $argv );
563                                 }
564                                 break;
565                         } elseif ( substr( $arg, 0, 2 ) == '--' ) {
566                                 # Long options
567                                 $option = substr( $arg, 2 );
568                                 if ( isset( $this->mParams[$option] ) && $this->mParams[$option]['withArg'] ) {
569                                         $param = next( $argv );
570                                         if ( $param === false ) {
571                                                 $this->error( "\nERROR: $option needs a value after it\n" );
572                                                 $this->maybeHelp( true );
573                                         }
574                                         $options[$option] = $param;
575                                 } else {
576                                         $bits = explode( '=', $option, 2 );
577                                         if ( count( $bits ) > 1 ) {
578                                                 $option = $bits[0];
579                                                 $param = $bits[1];
580                                         } else {
581                                                 $param = 1;
582                                         }
583                                         $options[$option] = $param;
584                                 }
585                         } elseif ( substr( $arg, 0, 1 ) == '-' ) {
586                                 # Short options
587                                 for ( $p = 1; $p < strlen( $arg ); $p++ ) {
588                                         $option = $arg { $p } ;
589                                         if ( isset( $this->mParams[$option]['withArg'] ) && $this->mParams[$option]['withArg'] ) {
590                                                 $param = next( $argv );
591                                                 if ( $param === false ) {
592                                                         $this->error( "\nERROR: $option needs a value after it\n" );
593                                                         $this->maybeHelp( true );
594                                                 }
595                                                 $options[$option] = $param;
596                                         } else {
597                                                 $options[$option] = 1;
598                                         }
599                                 }
600                         } else {
601                                 $args[] = $arg;
602                         }
603                 }
604
605                 $this->mOptions = $options;
606                 $this->mArgs = $args;
607                 $this->loadSpecialVars();
608                 $this->mInputLoaded = true;
609         }
610
611         /**
612          * Run some validation checks on the params, etc
613          */
614         protected function validateParamsAndArgs() {
615                 $die = false;
616                 # Check to make sure we've got all the required options
617                 foreach ( $this->mParams as $opt => $info ) {
618                         if ( $info['require'] && !$this->hasOption( $opt ) ) {
619                                 $this->error( "Param $opt required!" );
620                                 $die = true;
621                         }
622                 }
623                 # Check arg list too
624                 foreach ( $this->mArgList as $k => $info ) {
625                         if ( $info['require'] && !$this->hasArg( $k ) ) {
626                                 $this->error( 'Argument <' . $info['name'] . '> required!' );
627                                 $die = true;
628                         }
629                 }
630
631                 if ( $die ) {
632                         $this->maybeHelp( true );
633                 }
634         }
635
636         /**
637          * Handle the special variables that are global to all scripts
638          */
639         protected function loadSpecialVars() {
640                 if ( $this->hasOption( 'dbuser' ) ) {
641                         $this->mDbUser = $this->getOption( 'dbuser' );
642                 }
643                 if ( $this->hasOption( 'dbpass' ) ) {
644                         $this->mDbPass = $this->getOption( 'dbpass' );
645                 }
646                 if ( $this->hasOption( 'quiet' ) ) {
647                         $this->mQuiet = true;
648                 }
649                 if ( $this->hasOption( 'batch-size' ) ) {
650                         $this->mBatchSize = $this->getOption( 'batch-size' );
651                 }
652         }
653
654         /**
655          * Maybe show the help.
656          * @param $force boolean Whether to force the help to show, default false
657          */
658         protected function maybeHelp( $force = false ) {
659                 if( !$force && !$this->hasOption( 'help' ) ) {
660                         return;
661                 }
662
663                 $screenWidth = 80; // TODO: Caculate this!
664                 $tab = "    ";
665                 $descWidth = $screenWidth - ( 2 * strlen( $tab ) );
666
667                 ksort( $this->mParams );
668                 $this->mQuiet = false;
669
670                 // Description ...
671                 if ( $this->mDescription ) {
672                         $this->output( "\n" . $this->mDescription . "\n" );
673                 }
674                 $output = "\nUsage: php " . basename( $this->mSelf );
675
676                 // ... append parameters ...
677                 if ( $this->mParams ) {
678                         $output .= " [--" . implode( array_keys( $this->mParams ), "|--" ) . "]";
679                 }
680
681                 // ... and append arguments.
682                 if ( $this->mArgList ) {
683                         $output .= ' ';
684                         foreach ( $this->mArgList as $k => $arg ) {
685                                 if ( $arg['require'] ) {
686                                         $output .= '<' . $arg['name'] . '>';
687                                 } else {
688                                         $output .= '[' . $arg['name'] . ']';
689                                 }
690                                 if ( $k < count( $this->mArgList ) - 1 )
691                                         $output .= ' ';
692                         }
693                 }
694                 $this->output( "$output\n\n" );
695
696                 // Parameters description
697                 foreach ( $this->mParams as $par => $info ) {
698                         $this->output(
699                                 wordwrap( "$tab--$par: " . $info['desc'], $descWidth,
700                                                 "\n$tab$tab" ) . "\n"
701                         );
702                 }
703
704                 // Arguments description
705                 foreach ( $this->mArgList as $info ) {
706                         $openChar = $info['require'] ? '<' : '[';
707                         $closeChar = $info['require'] ? '>' : ']';
708                         $this->output(
709                                 wordwrap( "$tab$openChar" . $info['name'] . "$closeChar: " .
710                                         $info['desc'], $descWidth, "\n$tab$tab" ) . "\n"
711                         );
712                 }
713
714                 die( 1 );
715         }
716
717         /**
718          * Handle some last-minute setup here.
719          */
720         public function finalSetup() {
721                 global $wgCommandLineMode, $wgShowSQLErrors, $wgServer;
722                 global $wgProfiling, $wgDBadminuser, $wgDBadminpassword;
723                 global $wgDBuser, $wgDBpassword, $wgDBservers, $wgLBFactoryConf;
724
725                 # Turn off output buffering again, it might have been turned on in the settings files
726                 if ( ob_get_level() ) {
727                         ob_end_flush();
728                 }
729                 # Same with these
730                 $wgCommandLineMode = true;
731
732                 # Override $wgServer
733                 if( $this->hasOption( 'server') ) {
734                         $wgServer = $this->getOption( 'server', $wgServer );
735                 }
736
737                 # If these were passed, use them
738                 if ( $this->mDbUser ) {
739                         $wgDBadminuser = $this->mDbUser;
740                 }
741                 if ( $this->mDbPass ) {
742                         $wgDBadminpassword = $this->mDbPass;
743                 }
744
745                 if ( $this->getDbType() == self::DB_ADMIN && isset( $wgDBadminuser ) ) {
746                         $wgDBuser = $wgDBadminuser;
747                         $wgDBpassword = $wgDBadminpassword;
748
749                         if ( $wgDBservers ) {
750                                 foreach ( $wgDBservers as $i => $server ) {
751                                         $wgDBservers[$i]['user'] = $wgDBuser;
752                                         $wgDBservers[$i]['password'] = $wgDBpassword;
753                                 }
754                         }
755                         if ( isset( $wgLBFactoryConf['serverTemplate'] ) ) {
756                                 $wgLBFactoryConf['serverTemplate']['user'] = $wgDBuser;
757                                 $wgLBFactoryConf['serverTemplate']['password'] = $wgDBpassword;
758                         }
759                         LBFactory::destroyInstance();
760                 }
761
762                 $this->afterFinalSetup();
763
764                 $wgShowSQLErrors = true;
765                 @set_time_limit( 0 );
766                 $this->adjustMemoryLimit();
767
768                 $wgProfiling = false; // only for Profiler.php mode; avoids OOM errors
769         }
770
771         /**
772          * Execute a callback function at the end of initialisation
773          */
774         protected function afterFinalSetup() {
775                 if ( defined( 'MW_CMDLINE_CALLBACK' ) ) {
776                         call_user_func( MW_CMDLINE_CALLBACK );
777                 }
778         }
779
780         /**
781          * Potentially debug globals. Originally a feature only
782          * for refreshLinks
783          */
784         public function globals() {
785                 if ( $this->hasOption( 'globals' ) ) {
786                         print_r( $GLOBALS );
787                 }
788         }
789
790         /**
791          * Do setup specific to WMF
792          */
793         public function loadWikimediaSettings() {
794                 global $IP, $wgNoDBParam, $wgUseNormalUser, $wgConf, $site, $lang;
795
796                 if ( empty( $wgNoDBParam ) ) {
797                         # Check if we were passed a db name
798                         if ( isset( $this->mOptions['wiki'] ) ) {
799                                 $db = $this->mOptions['wiki'];
800                         } else {
801                                 $db = array_shift( $this->mArgs );
802                         }
803                         list( $site, $lang ) = $wgConf->siteFromDB( $db );
804
805                         # If not, work out the language and site the old way
806                         if ( is_null( $site ) || is_null( $lang ) ) {
807                                 if ( !$db ) {
808                                         $lang = 'aa';
809                                 } else {
810                                         $lang = $db;
811                                 }
812                                 if ( isset( $this->mArgs[0] ) ) {
813                                         $site = array_shift( $this->mArgs );
814                                 } else {
815                                         $site = 'wikipedia';
816                                 }
817                         }
818                 } else {
819                         $lang = 'aa';
820                         $site = 'wikipedia';
821                 }
822
823                 # This is for the IRC scripts, which now run as the apache user
824                 # The apache user doesn't have access to the wikiadmin_pass command
825                 if ( $_ENV['USER'] == 'apache' ) {
826                 # if ( posix_geteuid() == 48 ) {
827                         $wgUseNormalUser = true;
828                 }
829
830                 putenv( 'wikilang=' . $lang );
831
832                 ini_set( 'include_path', ".:$IP:$IP/includes:$IP/languages:$IP/maintenance" );
833
834                 if ( $lang == 'test' && $site == 'wikipedia' ) {
835                         define( 'TESTWIKI', 1 );
836                 }
837         }
838
839         /**
840          * Generic setup for most installs. Returns the location of LocalSettings
841          * @return String
842          */
843         public function loadSettings() {
844                 global $wgWikiFarm, $wgCommandLineMode, $IP;
845
846                 $wgWikiFarm = false;
847                 if ( isset( $this->mOptions['conf'] ) ) {
848                         $settingsFile = $this->mOptions['conf'];
849                 } else if ( defined("MW_CONFIG_FILE") ) {
850                         $settingsFile = MW_CONFIG_FILE;
851                 } else {
852                         $settingsFile = "$IP/LocalSettings.php";
853                 }
854                 if ( isset( $this->mOptions['wiki'] ) ) {
855                         $bits = explode( '-', $this->mOptions['wiki'] );
856                         if ( count( $bits ) == 1 ) {
857                                 $bits[] = '';
858                         }
859                         define( 'MW_DB', $bits[0] );
860                         define( 'MW_PREFIX', $bits[1] );
861                 }
862
863                 if ( !is_readable( $settingsFile ) ) {
864                         $this->error( "A copy of your installation's LocalSettings.php\n" .
865                                                 "must exist and be readable in the source directory.\n" .
866                                                 "Use --conf to specify it." , true );
867                 }
868                 $wgCommandLineMode = true;
869                 return $settingsFile;
870         }
871
872         /**
873          * Support function for cleaning up redundant text records
874          * @param $delete Boolean: whether or not to actually delete the records
875          * @author Rob Church <robchur@gmail.com>
876          */
877         public function purgeRedundantText( $delete = true ) {
878                 # Data should come off the master, wrapped in a transaction
879                 $dbw = wfGetDB( DB_MASTER );
880                 $dbw->begin();
881
882                 $tbl_arc = $dbw->tableName( 'archive' );
883                 $tbl_rev = $dbw->tableName( 'revision' );
884                 $tbl_txt = $dbw->tableName( 'text' );
885
886                 # Get "active" text records from the revisions table
887                 $this->output( 'Searching for active text records in revisions table...' );
888                 $res = $dbw->query( "SELECT DISTINCT rev_text_id FROM $tbl_rev" );
889                 foreach ( $res as $row ) {
890                         $cur[] = $row->rev_text_id;
891                 }
892                 $this->output( "done.\n" );
893
894                 # Get "active" text records from the archive table
895                 $this->output( 'Searching for active text records in archive table...' );
896                 $res = $dbw->query( "SELECT DISTINCT ar_text_id FROM $tbl_arc" );
897                 foreach ( $res as $row ) {
898                         $cur[] = $row->ar_text_id;
899                 }
900                 $this->output( "done.\n" );
901
902                 # Get the IDs of all text records not in these sets
903                 $this->output( 'Searching for inactive text records...' );
904                 $set = implode( ', ', $cur );
905                 $res = $dbw->query( "SELECT old_id FROM $tbl_txt WHERE old_id NOT IN ( $set )" );
906                 $old = array();
907                 foreach ( $res as $row ) {
908                         $old[] = $row->old_id;
909                 }
910                 $this->output( "done.\n" );
911
912                 # Inform the user of what we're going to do
913                 $count = count( $old );
914                 $this->output( "$count inactive items found.\n" );
915
916                 # Delete as appropriate
917                 if ( $delete && $count ) {
918                         $this->output( 'Deleting...' );
919                         $set = implode( ', ', $old );
920                         $dbw->query( "DELETE FROM $tbl_txt WHERE old_id IN ( $set )" );
921                         $this->output( "done.\n" );
922                 }
923
924                 # Done
925                 $dbw->commit();
926         }
927
928         /**
929          * Get the maintenance directory.
930          */
931         protected function getDir() {
932                 return dirname( __FILE__ );
933         }
934
935         /**
936          * Get the list of available maintenance scripts. Note
937          * that if you call this _before_ calling doMaintenance
938          * you won't have any extensions in it yet
939          * @return Array
940          */
941         public static function getMaintenanceScripts() {
942                 global $wgMaintenanceScripts;
943                 return $wgMaintenanceScripts + self::getCoreScripts();
944         }
945
946         /**
947          * Return all of the core maintenance scripts
948          * @return array
949          */
950         protected static function getCoreScripts() {
951                 if ( !self::$mCoreScripts ) {
952                         $paths = array(
953                                 dirname( __FILE__ ),
954                                 dirname( __FILE__ ) . '/gearman',
955                                 dirname( __FILE__ ) . '/language',
956                                 dirname( __FILE__ ) . '/storage',
957                         );
958                         self::$mCoreScripts = array();
959                         foreach ( $paths as $p ) {
960                                 $handle = opendir( $p );
961                                 while ( ( $file = readdir( $handle ) ) !== false ) {
962                                         if ( $file == 'Maintenance.php' ) {
963                                                 continue;
964                                         }
965                                         $file = $p . '/' . $file;
966                                         if ( is_dir( $file ) || !strpos( $file, '.php' ) ||
967                                                 ( strpos( file_get_contents( $file ), '$maintClass' ) === false ) ) {
968                                                 continue;
969                                         }
970                                         require( $file );
971                                         $vars = get_defined_vars();
972                                         if ( array_key_exists( 'maintClass', $vars ) ) {
973                                                 self::$mCoreScripts[$vars['maintClass']] = $file;
974                                         }
975                                 }
976                                 closedir( $handle );
977                         }
978                 }
979                 return self::$mCoreScripts;
980         }
981
982         /**
983          * Lock the search index
984          * @param &$db Database object
985          */
986         private function lockSearchindex( &$db ) {
987                 $write = array( 'searchindex' );
988                 $read = array( 'page', 'revision', 'text', 'interwiki', 'l10n_cache' );
989                 $db->lockTables( $read, $write, __CLASS__ . '::' . __METHOD__ );
990         }
991
992         /**
993          * Unlock the tables
994          * @param &$db Database object
995          */
996         private function unlockSearchindex( &$db ) {
997                 $db->unlockTables(  __CLASS__ . '::' . __METHOD__ );
998         }
999
1000         /**
1001          * Unlock and lock again
1002          * Since the lock is low-priority, queued reads will be able to complete
1003          * @param &$db Database object
1004          */
1005         private function relockSearchindex( &$db ) {
1006                 $this->unlockSearchindex( $db );
1007                 $this->lockSearchindex( $db );
1008         }
1009
1010         /**
1011          * Perform a search index update with locking
1012          * @param $maxLockTime Integer: the maximum time to keep the search index locked.
1013          * @param $callback callback String: the function that will update the function.
1014          * @param $dbw Database object
1015          * @param $results
1016          */
1017         public function updateSearchIndex( $maxLockTime, $callback, $dbw, $results ) {
1018                 $lockTime = time();
1019
1020                 # Lock searchindex
1021                 if ( $maxLockTime ) {
1022                         $this->output( "   --- Waiting for lock ---" );
1023                         $this->lockSearchindex( $dbw );
1024                         $lockTime = time();
1025                         $this->output( "\n" );
1026                 }
1027
1028                 # Loop through the results and do a search update
1029                 foreach ( $results as $row ) {
1030                         # Allow reads to be processed
1031                         if ( $maxLockTime && time() > $lockTime + $maxLockTime ) {
1032                                 $this->output( "    --- Relocking ---" );
1033                                 $this->relockSearchindex( $dbw );
1034                                 $lockTime = time();
1035                                 $this->output( "\n" );
1036                         }
1037                         call_user_func( $callback, $dbw, $row );
1038                 }
1039
1040                 # Unlock searchindex
1041                 if ( $maxLockTime ) {
1042                         $this->output( "    --- Unlocking --" );
1043                         $this->unlockSearchindex( $dbw );
1044                         $this->output( "\n" );
1045                 }
1046
1047         }
1048
1049         /**
1050          * Update the searchindex table for a given pageid
1051          * @param $dbw Database: a database write handle
1052          * @param $pageId Integer: the page ID to update.
1053          */
1054         public function updateSearchIndexForPage( $dbw, $pageId ) {
1055                 // Get current revision
1056                 $rev = Revision::loadFromPageId( $dbw, $pageId );
1057                 $title = null;
1058                 if ( $rev ) {
1059                         $titleObj = $rev->getTitle();
1060                         $title = $titleObj->getPrefixedDBkey();
1061                         $this->output( "$title..." );
1062                         # Update searchindex
1063                         $u = new SearchUpdate( $pageId, $titleObj->getText(), $rev->getText() );
1064                         $u->doUpdate();
1065                         $this->output( "\n" );
1066                 }
1067                 return $title;
1068         }
1069
1070         /**
1071          * Prompt the console for input
1072          * @param $prompt String what to begin the line with, like '> '
1073          * @return String response
1074          */
1075         public static function readconsole( $prompt = '> ' ) {
1076                 static $isatty = null;
1077                 if ( is_null( $isatty ) ) {
1078                         if ( posix_isatty( 0 /*STDIN*/ ) ) {
1079                                 $isatty = true;
1080                         } else {
1081                                 $isatty = false;
1082                         }
1083                 }
1084
1085                 if ( $isatty && function_exists( 'readline' ) ) {
1086                         return readline( $prompt );
1087                 } else {
1088                         if ( $isatty ) {
1089                                 $st = self::readlineEmulation( $prompt );
1090                         } else {
1091                                 if ( feof( STDIN ) ) {
1092                                         $st = false;
1093                                 } else {
1094                                         $st = fgets( STDIN, 1024 );
1095                                 }
1096                         }
1097                         if ( $st === false ) return false;
1098                         $resp = trim( $st );
1099                         return $resp;
1100                 }
1101         }
1102
1103         /**
1104          * Emulate readline()
1105          * @param $prompt String what to begin the line with, like '> '
1106          * @return String
1107          */
1108         private static function readlineEmulation( $prompt ) {
1109                 $bash = Installer::locateExecutableInDefaultPaths( array( 'bash' ) );
1110                 if ( !wfIsWindows() && $bash ) {
1111                         $retval = false;
1112                         $encPrompt = wfEscapeShellArg( $prompt );
1113                         $command = "read -er -p $encPrompt && echo \"\$REPLY\"";
1114                         $encCommand = wfEscapeShellArg( $command );
1115                         $line = wfShellExec( "$bash -c $encCommand", $retval );
1116
1117                         if ( $retval == 0 ) {
1118                                 return $line;
1119                         } elseif ( $retval == 127 ) {
1120                                 // Couldn't execute bash even though we thought we saw it.
1121                                 // Shell probably spit out an error message, sorry :(
1122                                 // Fall through to fgets()...
1123                         } else {
1124                                 // EOF/ctrl+D
1125                                 return false;
1126                         }
1127                 }
1128
1129                 // Fallback... we'll have no editing controls, EWWW
1130                 if ( feof( STDIN ) ) {
1131                         return false;
1132                 }
1133                 print $prompt;
1134                 return fgets( STDIN, 1024 );
1135         }
1136 }
1137
1138 class FakeMaintenance extends Maintenance {
1139         protected $mSelf = "FakeMaintenanceScript";
1140         public function execute() {
1141                 return;
1142         }
1143 }
1144