Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
41.67% |
60 / 144 |
|
37.50% |
3 / 8 |
CRAP | |
0.00% |
0 / 1 |
UpdateMediaWiki | |
42.25% |
60 / 142 |
|
37.50% |
3 / 8 |
364.70 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
23 / 23 |
|
100.00% |
1 / 1 |
1 | |||
getDbType | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setup | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
18.92% |
14 / 74 |
|
0.00% |
0 / 1 |
279.99 | |||
afterFinalSetup | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
validateParamsAndArgs | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
42 | |||
formatWarnings | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
validateSettings | |
85.00% |
17 / 20 |
|
0.00% |
0 / 1 |
7.17 |
1 | #!/usr/bin/env php |
2 | <?php |
3 | /** |
4 | * Run all updaters. |
5 | * |
6 | * This is used when the database schema is modified and we need to apply patches. |
7 | * |
8 | * This program is free software; you can redistribute it and/or modify |
9 | * it under the terms of the GNU General Public License as published by |
10 | * the Free Software Foundation; either version 2 of the License, or |
11 | * (at your option) any later version. |
12 | * |
13 | * This program is distributed in the hope that it will be useful, |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | * GNU General Public License for more details. |
17 | * |
18 | * You should have received a copy of the GNU General Public License along |
19 | * with this program; if not, write to the Free Software Foundation, Inc., |
20 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
21 | * http://www.gnu.org/copyleft/gpl.html |
22 | * |
23 | * @file |
24 | * @todo document |
25 | * @ingroup Maintenance |
26 | */ |
27 | |
28 | // NO_AUTOLOAD -- due to hashbang above |
29 | |
30 | // @codeCoverageIgnoreStart |
31 | require_once __DIR__ . '/Maintenance.php'; |
32 | // @codeCoverageIgnoreEnd |
33 | |
34 | use MediaWiki\Context\RequestContext; |
35 | use MediaWiki\Installer\DatabaseInstaller; |
36 | use MediaWiki\Installer\DatabaseUpdater; |
37 | use MediaWiki\Installer\Installer; |
38 | use MediaWiki\Maintenance\LoggedUpdateMaintenance; |
39 | use MediaWiki\Maintenance\Maintenance; |
40 | use MediaWiki\Settings\SettingsBuilder; |
41 | use MediaWiki\WikiMap\WikiMap; |
42 | use Wikimedia\Rdbms\DatabaseSqlite; |
43 | |
44 | /** |
45 | * Maintenance script to run database schema updates. |
46 | * |
47 | * @ingroup Maintenance |
48 | */ |
49 | class UpdateMediaWiki extends Maintenance { |
50 | public function __construct() { |
51 | parent::__construct(); |
52 | $this->addDescription( 'MediaWiki database updater' ); |
53 | $this->addOption( 'quick', 'Skip 5 second countdown before starting' ); |
54 | $this->addOption( 'initial', |
55 | 'Do initial updates required after manual installation using tables-generated.sql' ); |
56 | $this->addOption( 'doshared', 'Also update shared tables' ); |
57 | $this->addOption( 'noschema', 'Only do the updates that are not done during schema updates' ); |
58 | $this->addOption( |
59 | 'schema', |
60 | 'Output SQL to do the schema updates instead of doing them. Works ' |
61 | . 'even when $wgAllowSchemaUpdates is false', |
62 | false, |
63 | true |
64 | ); |
65 | $this->addOption( 'force', 'Override when $wgAllowSchemaUpdates disables this script' ); |
66 | $this->addOption( |
67 | 'skip-external-dependencies', |
68 | 'Skips checking whether external dependencies are up to date, mostly for developers' |
69 | ); |
70 | $this->addOption( |
71 | 'skip-config-validation', |
72 | 'Skips checking whether the existing configuration is valid' |
73 | ); |
74 | } |
75 | |
76 | public function getDbType() { |
77 | return Maintenance::DB_ADMIN; |
78 | } |
79 | |
80 | public function setup() { |
81 | global $wgMessagesDirs; |
82 | // T206765: We need to load the installer i18n files as some of errors come installer/updater code |
83 | // T310378: We have to ensure we do this before execute() |
84 | $wgMessagesDirs['MediaWikiInstaller'] = dirname( __DIR__ ) . '/includes/installer/i18n'; |
85 | } |
86 | |
87 | public function execute() { |
88 | global $wgLang, $wgAllowSchemaUpdates; |
89 | |
90 | if ( !$wgAllowSchemaUpdates |
91 | && !( $this->hasOption( 'force' ) |
92 | || $this->hasOption( 'schema' ) |
93 | || $this->hasOption( 'noschema' ) ) |
94 | ) { |
95 | $this->fatalError( "Do not run update.php on this wiki. If you're seeing this you should\n" |
96 | . "probably ask for some help in performing your schema updates or use\n" |
97 | . "the --noschema and --schema options to get an SQL file for someone\n" |
98 | . "else to inspect and run.\n\n" |
99 | . "If you know what you are doing, you can continue with --force\n" ); |
100 | } |
101 | |
102 | $this->fileHandle = null; |
103 | if ( str_starts_with( $this->getOption( 'schema', '' ), '--' ) ) { |
104 | $this->fatalError( "The --schema option requires a file as an argument.\n" ); |
105 | } elseif ( $this->hasOption( 'schema' ) ) { |
106 | $file = $this->getOption( 'schema' ); |
107 | $this->fileHandle = fopen( $file, "w" ); |
108 | if ( $this->fileHandle === false ) { |
109 | $err = error_get_last(); |
110 | $this->fatalError( "Problem opening the schema file for writing: $file\n\t{$err['message']}" ); |
111 | } |
112 | } |
113 | |
114 | // Check for warnings about settings, and abort if there are any. |
115 | if ( !$this->hasOption( 'skip-config-validation' ) ) { |
116 | $this->validateSettings(); |
117 | } |
118 | |
119 | $lang = $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'en' ); |
120 | // Set global language to ensure localised errors are in English (T22633) |
121 | RequestContext::getMain()->setLanguage( $lang ); |
122 | |
123 | // BackCompat |
124 | $wgLang = $lang; |
125 | |
126 | define( 'MW_UPDATER', true ); |
127 | |
128 | $this->output( 'MediaWiki ' . MW_VERSION . " Updater\n\n" ); |
129 | |
130 | $this->waitForReplication(); |
131 | |
132 | // Check external dependencies are up to date |
133 | if ( !$this->hasOption( 'skip-external-dependencies' ) && !getenv( 'MW_SKIP_EXTERNAL_DEPENDENCIES' ) ) { |
134 | $composerLockUpToDate = $this->runChild( CheckComposerLockUpToDate::class ); |
135 | $composerLockUpToDate->execute(); |
136 | } else { |
137 | $this->output( |
138 | "Skipping checking whether external dependencies are up to date, proceed at your own risk\n" |
139 | ); |
140 | } |
141 | |
142 | # Attempt to connect to the database as a privileged user |
143 | # This will vomit up an error if there are permissions problems |
144 | $db = $this->getPrimaryDB(); |
145 | |
146 | # Check to see whether the database server meets the minimum requirements |
147 | /** @var DatabaseInstaller $dbInstallerClass */ |
148 | $dbInstallerClass = Installer::getDBInstallerClass( $db->getType() ); |
149 | $status = $dbInstallerClass::meetsMinimumRequirement( $db ); |
150 | if ( !$status->isOK() ) { |
151 | // This might output some wikitext like <strong> but it should be comprehensible |
152 | $this->fatalError( $status ); |
153 | } |
154 | |
155 | $dbDomain = WikiMap::getCurrentWikiDbDomain()->getId(); |
156 | $this->output( "Going to run database updates for $dbDomain\n" ); |
157 | if ( $db->getType() === 'sqlite' ) { |
158 | /** @var DatabaseSqlite $db */ |
159 | '@phan-var DatabaseSqlite $db'; |
160 | $this->output( "Using SQLite file: '{$db->getDbFilePath()}'\n" ); |
161 | } |
162 | $this->output( "Depending on the size of your database this may take a while!\n" ); |
163 | |
164 | if ( !$this->hasOption( 'quick' ) ) { |
165 | $this->output( "Abort with control-c in the next five seconds " |
166 | . "(skip this countdown with --quick) ..." ); |
167 | $this->countDown( 5 ); |
168 | } |
169 | |
170 | $time1 = microtime( true ); |
171 | |
172 | $shared = $this->hasOption( 'doshared' ); |
173 | |
174 | $updates = [ 'core', 'extensions' ]; |
175 | if ( !$this->hasOption( 'schema' ) ) { |
176 | if ( $this->hasOption( 'noschema' ) ) { |
177 | $updates[] = 'noschema'; |
178 | } |
179 | $updates[] = 'stats'; |
180 | } |
181 | if ( $this->hasOption( 'initial' ) ) { |
182 | $updates[] = 'initial'; |
183 | } |
184 | |
185 | $updater = DatabaseUpdater::newForDB( $db, $shared, $this ); |
186 | |
187 | // Avoid upgrading from versions older than 1.35 |
188 | // Using an implicit marker (rev_actor was introduced in 1.34) |
189 | // TODO: Use an explicit marker |
190 | // See T259771 |
191 | if ( !$updater->fieldExists( 'revision', 'rev_actor' ) ) { |
192 | $this->fatalError( |
193 | "Can not upgrade from versions older than 1.35, please upgrade to that version or later first." |
194 | ); |
195 | } |
196 | |
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 | |
217 | $updater->purgeCache(); |
218 | |
219 | $time2 = microtime( true ); |
220 | |
221 | $timeDiff = $lang->formatTimePeriod( $time2 - $time1 ); |
222 | $this->output( "\nDone in $timeDiff.\n" ); |
223 | } |
224 | |
225 | protected function afterFinalSetup() { |
226 | global $wgLocalisationCacheConf; |
227 | |
228 | # Don't try to access the database |
229 | # This needs to be disabled early since extensions will try to use the l10n |
230 | # cache from $wgExtensionFunctions (T22471) |
231 | $wgLocalisationCacheConf = [ |
232 | 'class' => LocalisationCache::class, |
233 | 'storeClass' => LCStoreNull::class, |
234 | 'storeDirectory' => false, |
235 | 'manualRecache' => false, |
236 | ]; |
237 | } |
238 | |
239 | /** |
240 | * @suppress PhanPluginDuplicateConditionalNullCoalescing |
241 | */ |
242 | public function validateParamsAndArgs() { |
243 | // Allow extensions to add additional params. |
244 | $params = []; |
245 | $this->getHookRunner()->onMaintenanceUpdateAddParams( $params ); |
246 | |
247 | // This executes before the PHP version check, so don't use null coalesce (??). |
248 | // Keeping this compatible with older PHP versions lets us reach the code that |
249 | // displays a more helpful error. |
250 | foreach ( $params as $name => $param ) { |
251 | $this->addOption( |
252 | $name, |
253 | $param['desc'], |
254 | isset( $param['require'] ) ? $param['require'] : false, |
255 | isset( $param['withArg'] ) ? $param['withArg'] : false, |
256 | isset( $param['shortName'] ) ? $param['shortName'] : false, |
257 | isset( $param['multiOccurrence'] ) ? $param['multiOccurrence'] : false |
258 | ); |
259 | } |
260 | |
261 | parent::validateParamsAndArgs(); |
262 | } |
263 | |
264 | private function formatWarnings( array $warnings ) { |
265 | $text = ''; |
266 | foreach ( $warnings as $warning ) { |
267 | $warning = wordwrap( $warning, 75, "\n " ); |
268 | $text .= "* $warning\n"; |
269 | } |
270 | return $text; |
271 | } |
272 | |
273 | private function validateSettings() { |
274 | $settings = SettingsBuilder::getInstance(); |
275 | |
276 | $warnings = []; |
277 | if ( $settings->getWarnings() ) { |
278 | $warnings = $settings->getWarnings(); |
279 | } |
280 | |
281 | $status = $settings->validate(); |
282 | if ( !$status->isOK() ) { |
283 | foreach ( $status->getMessages( 'error' ) as $msg ) { |
284 | $warnings[] = wfMessage( $msg )->text(); |
285 | } |
286 | } |
287 | |
288 | $deprecations = $settings->detectDeprecatedConfig(); |
289 | foreach ( $deprecations as $key => $msg ) { |
290 | $warnings[] = "$key is deprecated: $msg"; |
291 | } |
292 | |
293 | $obsolete = $settings->detectObsoleteConfig(); |
294 | foreach ( $obsolete as $key => $msg ) { |
295 | $warnings[] = "$key is obsolete: $msg"; |
296 | } |
297 | |
298 | if ( $warnings ) { |
299 | $this->fatalError( "Some of your configuration settings caused a warning:\n\n" |
300 | . $this->formatWarnings( $warnings ) . "\n" |
301 | . "Please correct the issue before running update.php again.\n" |
302 | . "If you know what you are doing, you can bypass this check\n" |
303 | . "using --skip-config-validation.\n" ); |
304 | } |
305 | } |
306 | } |
307 | |
308 | // @codeCoverageIgnoreStart |
309 | $maintClass = UpdateMediaWiki::class; |
310 | require_once RUN_MAINTENANCE_IF_MAIN; |
311 | // @codeCoverageIgnoreEnd |