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