MediaWiki  master
update.php
Go to the documentation of this file.
1 #!/usr/bin/env php
2 <?php
28 require_once __DIR__ . '/Maintenance.php';
29 
32 
39  public function __construct() {
40  parent::__construct();
41  $this->addDescription( 'MediaWiki database updater' );
42  $this->addOption( 'skip-compat-checks', 'Skips compatibility checks, mostly for developers' );
43  $this->addOption( 'quick', 'Skip 5 second countdown before starting' );
44  $this->addOption( 'doshared', 'Also update shared tables' );
45  $this->addOption( 'nopurge', 'Do not purge the objectcache table after updates' );
46  $this->addOption( 'noschema', 'Only do the updates that are not done during schema updates' );
47  $this->addOption(
48  'schema',
49  'Output SQL to do the schema updates instead of doing them. Works '
50  . 'even when $wgAllowSchemaUpdates is false',
51  false,
52  true
53  );
54  $this->addOption( 'force', 'Override when $wgAllowSchemaUpdates disables this script' );
55  $this->addOption(
56  'skip-external-dependencies',
57  'Skips checking whether external dependencies are up to date, mostly for developers'
58  );
59  }
60 
61  public function getDbType() {
62  return Maintenance::DB_ADMIN;
63  }
64 
65  private function compatChecks() {
66  $minimumPcreVersion = Installer::MINIMUM_PCRE_VERSION;
67 
68  $pcreVersion = explode( ' ', PCRE_VERSION, 2 )[0];
69  if ( version_compare( $pcreVersion, $minimumPcreVersion, '<' ) ) {
70  $this->fatalError(
71  "PCRE $minimumPcreVersion or later is required.\n" .
72  "Your PHP binary is linked with PCRE $pcreVersion.\n\n" .
73  "More information:\n" .
74  "https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE\n\n" .
75  "ABORTING.\n" );
76  }
77  }
78 
79  public function execute() {
81 
82  if ( !$wgAllowSchemaUpdates
83  && !( $this->hasOption( 'force' )
84  || $this->hasOption( 'schema' )
85  || $this->hasOption( 'noschema' ) )
86  ) {
87  $this->fatalError( "Do not run update.php on this wiki. If you're seeing this you should\n"
88  . "probably ask for some help in performing your schema updates or use\n"
89  . "the --noschema and --schema options to get an SQL file for someone\n"
90  . "else to inspect and run.\n\n"
91  . "If you know what you are doing, you can continue with --force\n" );
92  }
93 
94  $this->fileHandle = null;
95  if ( substr( $this->getOption( 'schema' ), 0, 2 ) === "--" ) {
96  $this->fatalError( "The --schema option requires a file as an argument.\n" );
97  } elseif ( $this->hasOption( 'schema' ) ) {
98  $file = $this->getOption( 'schema' );
99  $this->fileHandle = fopen( $file, "w" );
100  if ( $this->fileHandle === false ) {
101  $err = error_get_last();
102  $this->fatalError( "Problem opening the schema file for writing: $file\n\t{$err['message']}" );
103  }
104  }
105 
106  // T206765: We need to load the installer i18n files as some of errors come installer/updater code
107  $wgMessagesDirs['MediawikiInstaller'] = dirname( __DIR__ ) . '/includes/installer/i18n';
108 
109  $lang = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( 'en' );
110  // Set global language to ensure localised errors are in English (T22633)
111  RequestContext::getMain()->setLanguage( $lang );
112  $wgLang = $lang; // BackCompat
113 
114  define( 'MW_UPDATER', true );
115 
116  $this->output( "MediaWiki {$wgVersion} Updater\n\n" );
117 
118  wfWaitForSlaves();
119 
120  if ( !$this->hasOption( 'skip-compat-checks' ) ) {
121  $this->compatChecks();
122  } else {
123  $this->output( "Skipping compatibility checks, proceed at your own risk (Ctrl+C to abort)\n" );
124  $this->countDown( 5 );
125  }
126 
127  // Check external dependencies are up to date
128  if ( !$this->hasOption( 'skip-external-dependencies' ) ) {
129  $composerLockUpToDate = $this->runChild( CheckComposerLockUpToDate::class );
130  $composerLockUpToDate->execute();
131  } else {
132  $this->output(
133  "Skipping checking whether external dependencies are up to date, proceed at your own risk\n"
134  );
135  }
136 
137  # Attempt to connect to the database as a privileged user
138  # This will vomit up an error if there are permissions problems
139  $db = $this->getDB( DB_MASTER );
140 
141  # Check to see whether the database server meets the minimum requirements
142 
143  $dbInstallerClass = Installer::getDBInstallerClass( $db->getType() );
144  $status = $dbInstallerClass::meetsMinimumRequirement( $db->getServerVersion() );
145  if ( !$status->isOK() ) {
146  // This might output some wikitext like <strong> but it should be comprehensible
147  $text = $status->getWikiText();
148  $this->fatalError( $text );
149  }
150 
151  $dbDomain = WikiMap::getCurrentWikiDbDomain()->getId();
152  $this->output( "Going to run database updates for $dbDomain\n" );
153  if ( $db->getType() === 'sqlite' ) {
155  '@phan-var DatabaseSqlite $db';
156  $this->output( "Using SQLite file: '{$db->getDbFilePath()}'\n" );
157  }
158  $this->output( "Depending on the size of your database this may take a while!\n" );
159 
160  if ( !$this->hasOption( 'quick' ) ) {
161  $this->output( "Abort with control-c in the next five seconds "
162  . "(skip this countdown with --quick) ... " );
163  $this->countDown( 5 );
164  }
165 
166  $time1 = microtime( true );
167 
168  $badPhpUnit = dirname( __DIR__ ) . '/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php';
169  if ( file_exists( $badPhpUnit ) ) {
170  // Bad versions of the file are:
171  // https://raw.githubusercontent.com/sebastianbergmann/phpunit/c820f915bfae34e5a836f94967a2a5ea5ef34f21/src/Util/PHP/eval-stdin.php
172  // https://raw.githubusercontent.com/sebastianbergmann/phpunit/3aaddb1c5bd9b9b8d070b4cf120e71c36fd08412/src/Util/PHP/eval-stdin.php
173  $md5 = md5_file( $badPhpUnit );
174  if ( $md5 === '120ac49800671dc383b6f3709c25c099'
175  || $md5 === '28af792cb38fc9a1b236b91c1aad2876'
176  ) {
177  $success = unlink( $badPhpUnit );
178  if ( $success ) {
179  $this->output( "Removed PHPUnit eval-stdin.php to protect against CVE-2017-9841\n" );
180  } else {
181  $this->error( "Unable to remove $badPhpUnit, you should manually. See CVE-2017-9841" );
182  }
183  }
184  }
185 
186  $shared = $this->hasOption( 'doshared' );
187 
188  $updates = [ 'core', 'extensions' ];
189  if ( !$this->hasOption( 'schema' ) ) {
190  if ( $this->hasOption( 'noschema' ) ) {
191  $updates[] = 'noschema';
192  }
193  $updates[] = 'stats';
194  }
195 
196  $updater = DatabaseUpdater::newForDB( $db, $shared, $this );
197  $updater->doUpdates( $updates );
198 
199  foreach ( $updater->getPostDatabaseUpdateMaintenance() as $maint ) {
200  $child = $this->runChild( $maint );
201 
202  // LoggedUpdateMaintenance is checking the updatelog itself
203  $isLoggedUpdate = $child instanceof LoggedUpdateMaintenance;
204 
205  if ( !$isLoggedUpdate && $updater->updateRowExists( $maint ) ) {
206  continue;
207  }
208 
209  $child->execute();
210  if ( !$isLoggedUpdate ) {
211  $updater->insertUpdateRow( $maint );
212  }
213  }
214 
215  $updater->setFileAccess();
216  if ( !$this->hasOption( 'nopurge' ) ) {
217  $updater->purgeCache();
218  }
219 
220  $time2 = microtime( true );
221 
222  $timeDiff = $lang->formatTimePeriod( $time2 - $time1 );
223  $this->output( "\nDone in $timeDiff.\n" );
224  }
225 
226  protected function afterFinalSetup() {
228 
229  # Don't try to access the database
230  # This needs to be disabled early since extensions will try to use the l10n
231  # cache from $wgExtensionFunctions (T22471)
232  $wgLocalisationCacheConf = [
233  'class' => LocalisationCache::class,
234  'storeClass' => LCStoreNull::class,
235  'storeDirectory' => false,
236  'manualRecache' => false,
237  ];
238  }
239 
245  public function validateParamsAndArgs() {
246  // Allow extensions to add additional params.
247  $params = [];
248  Hooks::run( 'MaintenanceUpdateAddParams', [ &$params ] );
249 
250  // This executes before the PHP version check, so don't use null coalesce (??).
251  // Keeping this compatible with older PHP versions lets us reach the code that
252  // displays a more helpful error.
253  // @phan-suppress-next-line PhanEmptyForeach False positive
254  foreach ( $params as $name => $param ) {
255  $this->addOption(
256  $name,
257  $param['desc'],
258  isset( $param['require'] ) ? $param['require'] : false,
259  isset( $param['withArg'] ) ? $param['withArg'] : false,
260  isset( $param['shortName'] ) ? $param['shortName'] : false,
261  isset( $param['multiOccurrence'] ) ? $param['multiOccurrence'] : false
262  );
263  }
264 
265  parent::validateParamsAndArgs();
266  }
267 }
268 
269 $maintClass = UpdateMediaWiki::class;
270 require_once RUN_MAINTENANCE_IF_MAIN;
static getDBInstallerClass( $type)
Get the DatabaseInstaller class name for this type.
Definition: Installer.php:558
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
const RUN_MAINTENANCE_IF_MAIN
Definition: Maintenance.php:39
$wgVersion
MediaWiki version number.
$wgMessagesDirs
Extension messages directories.
$success
error( $err, $die=0)
Throw an error to the user.
runChild( $maintClass, $classFile=null)
Run a child maintenance script.
getOption( $name, $default=null)
Get an option, or return the default.
if(!isset( $args[0])) $lang
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
Definition: Maintenance.php:86
validateParamsAndArgs()
Definition: update.php:245
hasOption( $name)
Checks to see if a particular option exists.
$maintClass
Definition: update.php:269
const DB_MASTER
Definition: defines.php:26
$wgAllowSchemaUpdates
Allow schema updates.
$wgLang
Definition: Setup.php:858
static getMain()
Get the RequestContext object associated with the main request.
$wgLocalisationCacheConf
Localisation cache configuration.
wfWaitForSlaves( $ifWritesSince=null, $wiki=false, $cluster=false, $timeout=null)
Waits for the replica DBs to catch up to the master position.
addDescription( $text)
Set the description text.
const DB_ADMIN
Definition: Maintenance.php:93
output( $out, $channel=null)
Throw some output to the user.
static getCurrentWikiDbDomain()
Definition: WikiMap.php:293
const MINIMUM_PCRE_VERSION
The oldest version of PCRE we can support.
Definition: Installer.php:54
Class for scripts that perform database maintenance and want to log the update in updatelog so we can...
static newForDB(IMaintainableDatabase $db, $shared=false, Maintenance $maintenance=null)
countDown( $seconds)
Count down from $seconds to zero on the terminal, with a one-second pause between showing each number...
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
Maintenance script to run database schema updates.
Definition: update.php:38
fatalError( $msg, $exitCode=1)
Output a message and terminate the current script.
getDB( $db, $groups=[], $dbDomain=false)
Returns a database to be used by current maintenance script.
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200