Completed
Branch master (9259dd)
by
unknown
27:26
created

DatabaseUpdater   F

Complexity

Total Complexity 136

Size/Duplication

Total Lines 1108
Duplicated Lines 11.55 %

Coupling/Cohesion

Components 2
Dependencies 15

Importance

Changes 0
Metric Value
dl 128
loc 1108
rs 0.6314
c 0
b 0
f 0
wmc 136
lcom 2
cbo 15

53 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 2
A initOldGlobals() 0 13 1
C loadExtensions() 3 26 7
A newForDB() 0 10 2
A getDB() 0 3 1
A output() 0 11 3
A addExtensionUpdate() 0 3 1
A addExtensionTable() 0 3 1
A addExtensionIndex() 0 3 1
A addExtensionField() 0 3 1
A dropExtensionField() 0 3 1
A dropExtensionIndex() 0 3 1
A dropExtensionTable() 0 3 1
A renameExtensionIndex() 0 13 1
A modifyExtensionField() 0 3 1
A tableExists() 0 3 1
A addPostDatabaseUpdateMaintenance() 0 3 1
A getExtensionUpdates() 0 3 1
A getPostDatabaseUpdateMaintenance() 0 3 1
A writeSchemaUpdateFile() 0 13 2
B doUpdates() 0 28 6
B runUpdates() 0 23 6
A setAppliedUpdates() 0 12 2
A updateRowExists() 0 11 1
A insertUpdateRow() 0 9 3
A canUseNewUpdatelog() 0 4 2
A doTable() 0 16 4
B getOldGlobalUpdates() 11 35 5
getCoreUpdateList() 0 1 ?
A copyFile() 0 5 1
A appendLine() 0 8 2
B applyPatch() 0 24 5
A addTable() 13 13 3
A addField() 15 15 4
A addIndex() 15 15 4
A dropField() 13 13 3
A dropIndex() 13 13 3
C renameIndex() 12 42 7
A dropTable() 0 21 4
B modifyField() 0 21 5
A setFileAccess() 0 17 3
A purgeCache() 0 14 2
A checkStats() 0 14 4
A doActiveUsersInit() 0 14 2
A doLogUsertextPopulation() 13 13 2
A doLogSearchPopulation() 12 12 2
A doUpdateTranscacheField() 0 10 2
A doCollationUpdate() 0 21 3
A doMigrateUserOptions() 0 7 2
B doEnableProfiling() 8 21 8
A rebuildLocalisationCache() 0 10 1
A disableContentHandlerUseDB() 0 9 2
A enableContentHandlerUseDB() 0 8 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like DatabaseUpdater often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DatabaseUpdater, and based on these observations, apply Extract Interface, too.

1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 33 and the first side effect is on line 24.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/**
3
 * DBMS-specific updater helper.
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License along
16
 * with this program; if not, write to the Free Software Foundation, Inc.,
17
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
 * http://www.gnu.org/copyleft/gpl.html
19
 *
20
 * @file
21
 * @ingroup Deployment
22
 */
23
24
require_once __DIR__ . '/../../maintenance/Maintenance.php';
25
26
/**
27
 * Class for handling database updates. Roughly based off of updaters.inc, with
28
 * a few improvements :)
29
 *
30
 * @ingroup Deployment
31
 * @since 1.17
32
 */
33
abstract class DatabaseUpdater {
34
	protected static $updateCounter = 0;
35
36
	/**
37
	 * Array of updates to perform on the database
38
	 *
39
	 * @var array
40
	 */
41
	protected $updates = [];
42
43
	/**
44
	 * Array of updates that were skipped
45
	 *
46
	 * @var array
47
	 */
48
	protected $updatesSkipped = [];
49
50
	/**
51
	 * List of extension-provided database updates
52
	 * @var array
53
	 */
54
	protected $extensionUpdates = [];
55
56
	/**
57
	 * Handle to the database subclass
58
	 *
59
	 * @var DatabaseBase
60
	 */
61
	protected $db;
62
63
	protected $shared = false;
64
65
	/**
66
	 * @var string[] Scripts to run after database update
67
	 * Should be a subclass of LoggedUpdateMaintenance
68
	 */
69
	protected $postDatabaseUpdateMaintenance = [
70
		DeleteDefaultMessages::class,
71
		PopulateRevisionLength::class,
72
		PopulateRevisionSha1::class,
73
		PopulateImageSha1::class,
74
		FixExtLinksProtocolRelative::class,
75
		PopulateFilearchiveSha1::class,
76
		PopulateBacklinkNamespace::class,
77
		FixDefaultJsonContentPages::class,
78
	];
79
80
	/**
81
	 * File handle for SQL output.
82
	 *
83
	 * @var resource
84
	 */
85
	protected $fileHandle = null;
86
87
	/**
88
	 * Flag specifying whether or not to skip schema (e.g. SQL-only) updates.
89
	 *
90
	 * @var bool
91
	 */
92
	protected $skipSchema = false;
93
94
	/**
95
	 * Hold the value of $wgContentHandlerUseDB during the upgrade.
96
	 */
97
	protected $holdContentHandlerUseDB = true;
98
99
	/**
100
	 * Constructor
101
	 *
102
	 * @param DatabaseBase $db To perform updates on
103
	 * @param bool $shared Whether to perform updates on shared tables
104
	 * @param Maintenance $maintenance Maintenance object which created us
105
	 */
106
	protected function __construct( DatabaseBase &$db, $shared, Maintenance $maintenance = null ) {
107
		$this->db = $db;
108
		$this->db->setFlag( DBO_DDLMODE ); // For Oracle's handling of schema files
109
		$this->shared = $shared;
110
		if ( $maintenance ) {
111
			$this->maintenance = $maintenance;
0 ignored issues
show
Bug introduced by
The property maintenance does not seem to exist. Did you mean postDatabaseUpdateMaintenance?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
112
			$this->fileHandle = $maintenance->fileHandle;
113
		} else {
114
			$this->maintenance = new FakeMaintenance;
0 ignored issues
show
Bug introduced by
The property maintenance does not seem to exist. Did you mean postDatabaseUpdateMaintenance?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
115
		}
116
		$this->maintenance->setDB( $db );
0 ignored issues
show
Bug introduced by
The property maintenance does not seem to exist. Did you mean postDatabaseUpdateMaintenance?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
117
		$this->initOldGlobals();
118
		$this->loadExtensions();
119
		Hooks::run( 'LoadExtensionSchemaUpdates', [ $this ] );
120
	}
121
122
	/**
123
	 * Initialize all of the old globals. One day this should all become
124
	 * something much nicer
125
	 */
126
	private function initOldGlobals() {
127
		global $wgExtNewTables, $wgExtNewFields, $wgExtPGNewFields,
128
			$wgExtPGAlteredFields, $wgExtNewIndexes, $wgExtModifiedFields;
129
130
		# For extensions only, should be populated via hooks
131
		# $wgDBtype should be checked to specifiy the proper file
132
		$wgExtNewTables = []; // table, dir
133
		$wgExtNewFields = []; // table, column, dir
134
		$wgExtPGNewFields = []; // table, column, column attributes; for PostgreSQL
135
		$wgExtPGAlteredFields = []; // table, column, new type, conversion method; for PostgreSQL
136
		$wgExtNewIndexes = []; // table, index, dir
137
		$wgExtModifiedFields = []; // table, index, dir
138
	}
139
140
	/**
141
	 * Loads LocalSettings.php, if needed, and initialises everything needed for
142
	 * LoadExtensionSchemaUpdates hook.
143
	 */
144
	private function loadExtensions() {
145
		if ( !defined( 'MEDIAWIKI_INSTALL' ) ) {
146
			return; // already loaded
147
		}
148
		$vars = Installer::getExistingLocalSettings();
149
150
		$registry = ExtensionRegistry::getInstance();
151
		$queue = $registry->getQueue();
152
		// Don't accidentally load extensions in the future
153
		$registry->clearQueue();
154
155
		// This will automatically add "AutoloadClasses" to $wgAutoloadClasses
156
		$data = $registry->readFromQueue( $queue );
157
		$hooks = [ 'wgHooks' => [ 'LoadExtensionSchemaUpdates' => [] ] ];
158 View Code Duplication
		if ( isset( $data['globals']['wgHooks']['LoadExtensionSchemaUpdates'] ) ) {
159
			$hooks = $data['globals']['wgHooks']['LoadExtensionSchemaUpdates'];
160
		}
161
		if ( $vars && isset( $vars['wgHooks']['LoadExtensionSchemaUpdates'] ) ) {
162
			$hooks = array_merge_recursive( $hooks, $vars['wgHooks']['LoadExtensionSchemaUpdates'] );
163
		}
164
		global $wgHooks, $wgAutoloadClasses;
165
		$wgHooks['LoadExtensionSchemaUpdates'] = $hooks;
166
		if ( $vars && isset( $vars['wgAutoloadClasses'] ) ) {
167
			$wgAutoloadClasses += $vars['wgAutoloadClasses'];
168
		}
169
	}
170
171
	/**
172
	 * @param DatabaseBase $db
173
	 * @param bool $shared
174
	 * @param Maintenance $maintenance
175
	 *
176
	 * @throws MWException
177
	 * @return DatabaseUpdater
178
	 */
179
	public static function newForDB( &$db, $shared = false, $maintenance = null ) {
180
		$type = $db->getType();
181
		if ( in_array( $type, Installer::getDBTypes() ) ) {
182
			$class = ucfirst( $type ) . 'Updater';
183
184
			return new $class( $db, $shared, $maintenance );
185
		} else {
186
			throw new MWException( __METHOD__ . ' called for unsupported $wgDBtype' );
187
		}
188
	}
189
190
	/**
191
	 * Get a database connection to run updates
192
	 *
193
	 * @return DatabaseBase
194
	 */
195
	public function getDB() {
196
		return $this->db;
197
	}
198
199
	/**
200
	 * Output some text. If we're running from web, escape the text first.
201
	 *
202
	 * @param string $str Text to output
203
	 */
204
	public function output( $str ) {
205
		if ( $this->maintenance->isQuiet() ) {
0 ignored issues
show
Bug introduced by
The property maintenance does not seem to exist. Did you mean postDatabaseUpdateMaintenance?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
206
			return;
207
		}
208
		global $wgCommandLineMode;
209
		if ( !$wgCommandLineMode ) {
210
			$str = htmlspecialchars( $str );
211
		}
212
		echo $str;
213
		flush();
214
	}
215
216
	/**
217
	 * Add a new update coming from an extension. This should be called by
218
	 * extensions while executing the LoadExtensionSchemaUpdates hook.
219
	 *
220
	 * @since 1.17
221
	 *
222
	 * @param array $update The update to run. Format is the following:
223
	 *                first item is the callback function, it also can be a
224
	 *                simple string with the name of a function in this class,
225
	 *                following elements are parameters to the function.
226
	 *                Note that callback functions will receive this object as
227
	 *                first parameter.
228
	 */
229
	public function addExtensionUpdate( array $update ) {
230
		$this->extensionUpdates[] = $update;
231
	}
232
233
	/**
234
	 * Convenience wrapper for addExtensionUpdate() when adding a new table (which
235
	 * is the most common usage of updaters in an extension)
236
	 *
237
	 * @since 1.18
238
	 *
239
	 * @param string $tableName Name of table to create
240
	 * @param string $sqlPath Full path to the schema file
241
	 */
242
	public function addExtensionTable( $tableName, $sqlPath ) {
243
		$this->extensionUpdates[] = [ 'addTable', $tableName, $sqlPath, true ];
244
	}
245
246
	/**
247
	 * @since 1.19
248
	 *
249
	 * @param string $tableName
250
	 * @param string $indexName
251
	 * @param string $sqlPath
252
	 */
253
	public function addExtensionIndex( $tableName, $indexName, $sqlPath ) {
254
		$this->extensionUpdates[] = [ 'addIndex', $tableName, $indexName, $sqlPath, true ];
255
	}
256
257
	/**
258
	 *
259
	 * @since 1.19
260
	 *
261
	 * @param string $tableName
262
	 * @param string $columnName
263
	 * @param string $sqlPath
264
	 */
265
	public function addExtensionField( $tableName, $columnName, $sqlPath ) {
266
		$this->extensionUpdates[] = [ 'addField', $tableName, $columnName, $sqlPath, true ];
267
	}
268
269
	/**
270
	 *
271
	 * @since 1.20
272
	 *
273
	 * @param string $tableName
274
	 * @param string $columnName
275
	 * @param string $sqlPath
276
	 */
277
	public function dropExtensionField( $tableName, $columnName, $sqlPath ) {
278
		$this->extensionUpdates[] = [ 'dropField', $tableName, $columnName, $sqlPath, true ];
279
	}
280
281
	/**
282
	 * Drop an index from an extension table
283
	 *
284
	 * @since 1.21
285
	 *
286
	 * @param string $tableName The table name
287
	 * @param string $indexName The index name
288
	 * @param string $sqlPath The path to the SQL change path
289
	 */
290
	public function dropExtensionIndex( $tableName, $indexName, $sqlPath ) {
291
		$this->extensionUpdates[] = [ 'dropIndex', $tableName, $indexName, $sqlPath, true ];
292
	}
293
294
	/**
295
	 *
296
	 * @since 1.20
297
	 *
298
	 * @param string $tableName
299
	 * @param string $sqlPath
300
	 */
301
	public function dropExtensionTable( $tableName, $sqlPath ) {
302
		$this->extensionUpdates[] = [ 'dropTable', $tableName, $sqlPath, true ];
303
	}
304
305
	/**
306
	 * Rename an index on an extension table
307
	 *
308
	 * @since 1.21
309
	 *
310
	 * @param string $tableName The table name
311
	 * @param string $oldIndexName The old index name
312
	 * @param string $newIndexName The new index name
313
	 * @param string $sqlPath The path to the SQL change path
314
	 * @param bool $skipBothIndexExistWarning Whether to warn if both the old
315
	 * and the new indexes exist. [facultative; by default, false]
316
	 */
317
	public function renameExtensionIndex( $tableName, $oldIndexName, $newIndexName,
318
		$sqlPath, $skipBothIndexExistWarning = false
319
	) {
320
		$this->extensionUpdates[] = [
321
			'renameIndex',
322
			$tableName,
323
			$oldIndexName,
324
			$newIndexName,
325
			$skipBothIndexExistWarning,
326
			$sqlPath,
327
			true
328
		];
329
	}
330
331
	/**
332
	 * @since 1.21
333
	 *
334
	 * @param string $tableName The table name
335
	 * @param string $fieldName The field to be modified
336
	 * @param string $sqlPath The path to the SQL change path
337
	 */
338
	public function modifyExtensionField( $tableName, $fieldName, $sqlPath ) {
339
		$this->extensionUpdates[] = [ 'modifyField', $tableName, $fieldName, $sqlPath, true ];
340
	}
341
342
	/**
343
	 *
344
	 * @since 1.20
345
	 *
346
	 * @param string $tableName
347
	 * @return bool
348
	 */
349
	public function tableExists( $tableName ) {
350
		return ( $this->db->tableExists( $tableName, __METHOD__ ) );
351
	}
352
353
	/**
354
	 * Add a maintenance script to be run after the database updates are complete.
355
	 *
356
	 * Script should subclass LoggedUpdateMaintenance
357
	 *
358
	 * @since 1.19
359
	 *
360
	 * @param string $class Name of a Maintenance subclass
361
	 */
362
	public function addPostDatabaseUpdateMaintenance( $class ) {
363
		$this->postDatabaseUpdateMaintenance[] = $class;
364
	}
365
366
	/**
367
	 * Get the list of extension-defined updates
368
	 *
369
	 * @return array
370
	 */
371
	protected function getExtensionUpdates() {
372
		return $this->extensionUpdates;
373
	}
374
375
	/**
376
	 * @since 1.17
377
	 *
378
	 * @return string[]
379
	 */
380
	public function getPostDatabaseUpdateMaintenance() {
381
		return $this->postDatabaseUpdateMaintenance;
382
	}
383
384
	/**
385
	 * @since 1.21
386
	 *
387
	 * Writes the schema updates desired to a file for the DB Admin to run.
388
	 * @param array $schemaUpdate
389
	 */
390
	private function writeSchemaUpdateFile( $schemaUpdate = [] ) {
391
		$updates = $this->updatesSkipped;
392
		$this->updatesSkipped = [];
393
394
		foreach ( $updates as $funcList ) {
395
			$func = $funcList[0];
396
			$arg = $funcList[1];
397
			$origParams = $funcList[2];
398
			call_user_func_array( $func, $arg );
399
			flush();
400
			$this->updatesSkipped[] = $origParams;
401
		}
402
	}
403
404
	/**
405
	 * Do all the updates
406
	 *
407
	 * @param array $what What updates to perform
408
	 */
409
	public function doUpdates( $what = [ 'core', 'extensions', 'stats' ] ) {
410
		global $wgVersion;
411
412
		$this->db->begin( __METHOD__ );
413
		$what = array_flip( $what );
414
		$this->skipSchema = isset( $what['noschema'] ) || $this->fileHandle !== null;
415
		if ( isset( $what['core'] ) ) {
416
			$this->runUpdates( $this->getCoreUpdateList(), false );
417
		}
418
		if ( isset( $what['extensions'] ) ) {
419
			$this->runUpdates( $this->getOldGlobalUpdates(), false );
420
			$this->runUpdates( $this->getExtensionUpdates(), true );
421
		}
422
423
		if ( isset( $what['stats'] ) ) {
424
			$this->checkStats();
425
		}
426
427
		$this->setAppliedUpdates( $wgVersion, $this->updates );
428
429
		if ( $this->fileHandle ) {
430
			$this->skipSchema = false;
431
			$this->writeSchemaUpdateFile();
432
			$this->setAppliedUpdates( "$wgVersion-schema", $this->updatesSkipped );
433
		}
434
435
		$this->db->commit( __METHOD__ );
436
	}
437
438
	/**
439
	 * Helper function for doUpdates()
440
	 *
441
	 * @param array $updates Array of updates to run
442
	 * @param bool $passSelf Whether to pass this object we calling external functions
443
	 */
444
	private function runUpdates( array $updates, $passSelf ) {
445
		$updatesDone = [];
446
		$updatesSkipped = [];
447
		foreach ( $updates as $params ) {
448
			$origParams = $params;
449
			$func = array_shift( $params );
450
			if ( !is_array( $func ) && method_exists( $this, $func ) ) {
451
				$func = [ $this, $func ];
452
			} elseif ( $passSelf ) {
453
				array_unshift( $params, $this );
454
			}
455
			$ret = call_user_func_array( $func, $params );
456
			flush();
457
			if ( $ret !== false ) {
458
				$updatesDone[] = $origParams;
459
				wfGetLBFactory()->waitForReplication();
0 ignored issues
show
Deprecated Code introduced by
The function wfGetLBFactory() has been deprecated with message: since 1.27, use MediaWikiServices::getDBLoadBalancerFactory() instead.

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
460
			} else {
461
				$updatesSkipped[] = [ $func, $params, $origParams ];
462
			}
463
		}
464
		$this->updatesSkipped = array_merge( $this->updatesSkipped, $updatesSkipped );
465
		$this->updates = array_merge( $this->updates, $updatesDone );
466
	}
467
468
	/**
469
	 * @param string $version
470
	 * @param array $updates
471
	 */
472
	protected function setAppliedUpdates( $version, $updates = [] ) {
473
		$this->db->clearFlag( DBO_DDLMODE );
474
		if ( !$this->canUseNewUpdatelog() ) {
475
			return;
476
		}
477
		$key = "updatelist-$version-" . time() . self::$updateCounter;
478
		self::$updateCounter++;
479
		$this->db->insert( 'updatelog',
480
			[ 'ul_key' => $key, 'ul_value' => serialize( $updates ) ],
481
			__METHOD__ );
482
		$this->db->setFlag( DBO_DDLMODE );
483
	}
484
485
	/**
486
	 * Helper function: check if the given key is present in the updatelog table.
487
	 * Obviously, only use this for updates that occur after the updatelog table was
488
	 * created!
489
	 * @param string $key Name of the key to check for
490
	 * @return bool
491
	 */
492
	public function updateRowExists( $key ) {
493
		$row = $this->db->selectRow(
494
			'updatelog',
495
			# Bug 65813
496
			'1 AS X',
497
			[ 'ul_key' => $key ],
498
			__METHOD__
499
		);
500
501
		return (bool)$row;
502
	}
503
504
	/**
505
	 * Helper function: Add a key to the updatelog table
506
	 * Obviously, only use this for updates that occur after the updatelog table was
507
	 * created!
508
	 * @param string $key Name of key to insert
509
	 * @param string $val [optional] Value to insert along with the key
510
	 */
511
	public function insertUpdateRow( $key, $val = null ) {
512
		$this->db->clearFlag( DBO_DDLMODE );
513
		$values = [ 'ul_key' => $key ];
514
		if ( $val && $this->canUseNewUpdatelog() ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $val of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
515
			$values['ul_value'] = $val;
516
		}
517
		$this->db->insert( 'updatelog', $values, __METHOD__, 'IGNORE' );
518
		$this->db->setFlag( DBO_DDLMODE );
519
	}
520
521
	/**
522
	 * Updatelog was changed in 1.17 to have a ul_value column so we can record
523
	 * more information about what kind of updates we've done (that's what this
524
	 * class does). Pre-1.17 wikis won't have this column, and really old wikis
525
	 * might not even have updatelog at all
526
	 *
527
	 * @return bool
528
	 */
529
	protected function canUseNewUpdatelog() {
530
		return $this->db->tableExists( 'updatelog', __METHOD__ ) &&
531
			$this->db->fieldExists( 'updatelog', 'ul_value', __METHOD__ );
532
	}
533
534
	/**
535
	 * Returns whether updates should be executed on the database table $name.
536
	 * Updates will be prevented if the table is a shared table and it is not
537
	 * specified to run updates on shared tables.
538
	 *
539
	 * @param string $name Table name
540
	 * @return bool
541
	 */
542
	protected function doTable( $name ) {
543
		global $wgSharedDB, $wgSharedTables;
544
545
		// Don't bother to check $wgSharedTables if there isn't a shared database
546
		// or the user actually also wants to do updates on the shared database.
547
		if ( $wgSharedDB === null || $this->shared ) {
548
			return true;
549
		}
550
551
		if ( in_array( $name, $wgSharedTables ) ) {
552
			$this->output( "...skipping update to shared table $name.\n" );
553
			return false;
554
		} else {
555
			return true;
556
		}
557
	}
558
559
	/**
560
	 * Before 1.17, we used to handle updates via stuff like
561
	 * $wgExtNewTables/Fields/Indexes. This is nasty :) We refactored a lot
562
	 * of this in 1.17 but we want to remain back-compatible for a while. So
563
	 * load up these old global-based things into our update list.
564
	 *
565
	 * @return array
566
	 */
567
	protected function getOldGlobalUpdates() {
568
		global $wgExtNewFields, $wgExtNewTables, $wgExtModifiedFields,
569
			$wgExtNewIndexes;
570
571
		$updates = [];
572
573 View Code Duplication
		foreach ( $wgExtNewTables as $tableRecord ) {
574
			$updates[] = [
575
				'addTable', $tableRecord[0], $tableRecord[1], true
576
			];
577
		}
578
579
		foreach ( $wgExtNewFields as $fieldRecord ) {
580
			$updates[] = [
581
				'addField', $fieldRecord[0], $fieldRecord[1],
582
				$fieldRecord[2], true
583
			];
584
		}
585
586 View Code Duplication
		foreach ( $wgExtNewIndexes as $fieldRecord ) {
587
			$updates[] = [
588
				'addIndex', $fieldRecord[0], $fieldRecord[1],
589
				$fieldRecord[2], true
590
			];
591
		}
592
593
		foreach ( $wgExtModifiedFields as $fieldRecord ) {
594
			$updates[] = [
595
				'modifyField', $fieldRecord[0], $fieldRecord[1],
596
				$fieldRecord[2], true
597
			];
598
		}
599
600
		return $updates;
601
	}
602
603
	/**
604
	 * Get an array of updates to perform on the database. Should return a
605
	 * multi-dimensional array. The main key is the MediaWiki version (1.12,
606
	 * 1.13...) with the values being arrays of updates, identical to how
607
	 * updaters.inc did it (for now)
608
	 *
609
	 * @return array
610
	 */
611
	abstract protected function getCoreUpdateList();
612
613
	/**
614
	 * Append an SQL fragment to the open file handle.
615
	 *
616
	 * @param string $filename File name to open
617
	 */
618
	public function copyFile( $filename ) {
619
		$this->db->sourceFile( $filename, false, false, false,
620
			[ $this, 'appendLine' ]
621
		);
622
	}
623
624
	/**
625
	 * Append a line to the open filehandle.  The line is assumed to
626
	 * be a complete SQL statement.
627
	 *
628
	 * This is used as a callback for sourceLine().
629
	 *
630
	 * @param string $line Text to append to the file
631
	 * @return bool False to skip actually executing the file
632
	 * @throws MWException
633
	 */
634
	public function appendLine( $line ) {
635
		$line = rtrim( $line ) . ";\n";
636
		if ( fwrite( $this->fileHandle, $line ) === false ) {
637
			throw new MWException( "trouble writing file" );
638
		}
639
640
		return false;
641
	}
642
643
	/**
644
	 * Applies a SQL patch
645
	 *
646
	 * @param string $path Path to the patch file
647
	 * @param bool $isFullPath Whether to treat $path as a relative or not
648
	 * @param string $msg Description of the patch
649
	 * @return bool False if patch is skipped.
650
	 */
651
	protected function applyPatch( $path, $isFullPath = false, $msg = null ) {
652
		if ( $msg === null ) {
653
			$msg = "Applying $path patch";
654
		}
655
		if ( $this->skipSchema ) {
656
			$this->output( "...skipping schema change ($msg).\n" );
657
658
			return false;
659
		}
660
661
		$this->output( "$msg ..." );
662
663
		if ( !$isFullPath ) {
664
			$path = $this->db->patchPath( $path );
665
		}
666
		if ( $this->fileHandle !== null ) {
667
			$this->copyFile( $path );
668
		} else {
669
			$this->db->sourceFile( $path );
670
		}
671
		$this->output( "done.\n" );
672
673
		return true;
674
	}
675
676
	/**
677
	 * Add a new table to the database
678
	 *
679
	 * @param string $name Name of the new table
680
	 * @param string $patch Path to the patch file
681
	 * @param bool $fullpath Whether to treat $patch path as a relative or not
682
	 * @return bool False if this was skipped because schema changes are skipped
683
	 */
684 View Code Duplication
	protected function addTable( $name, $patch, $fullpath = false ) {
685
		if ( !$this->doTable( $name ) ) {
686
			return true;
687
		}
688
689
		if ( $this->db->tableExists( $name, __METHOD__ ) ) {
690
			$this->output( "...$name table already exists.\n" );
691
		} else {
692
			return $this->applyPatch( $patch, $fullpath, "Creating $name table" );
693
		}
694
695
		return true;
696
	}
697
698
	/**
699
	 * Add a new field to an existing table
700
	 *
701
	 * @param string $table Name of the table to modify
702
	 * @param string $field Name of the new field
703
	 * @param string $patch Path to the patch file
704
	 * @param bool $fullpath Whether to treat $patch path as a relative or not
705
	 * @return bool False if this was skipped because schema changes are skipped
706
	 */
707 View Code Duplication
	protected function addField( $table, $field, $patch, $fullpath = false ) {
708
		if ( !$this->doTable( $table ) ) {
709
			return true;
710
		}
711
712
		if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
713
			$this->output( "...$table table does not exist, skipping new field patch.\n" );
714
		} elseif ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
715
			$this->output( "...have $field field in $table table.\n" );
716
		} else {
717
			return $this->applyPatch( $patch, $fullpath, "Adding $field field to table $table" );
718
		}
719
720
		return true;
721
	}
722
723
	/**
724
	 * Add a new index to an existing table
725
	 *
726
	 * @param string $table Name of the table to modify
727
	 * @param string $index Name of the new index
728
	 * @param string $patch Path to the patch file
729
	 * @param bool $fullpath Whether to treat $patch path as a relative or not
730
	 * @return bool False if this was skipped because schema changes are skipped
731
	 */
732 View Code Duplication
	protected function addIndex( $table, $index, $patch, $fullpath = false ) {
733
		if ( !$this->doTable( $table ) ) {
734
			return true;
735
		}
736
737
		if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
738
			$this->output( "...skipping: '$table' table doesn't exist yet.\n" );
739
		} elseif ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
740
			$this->output( "...index $index already set on $table table.\n" );
741
		} else {
742
			return $this->applyPatch( $patch, $fullpath, "Adding index $index to table $table" );
743
		}
744
745
		return true;
746
	}
747
748
	/**
749
	 * Drop a field from an existing table
750
	 *
751
	 * @param string $table Name of the table to modify
752
	 * @param string $field Name of the old field
753
	 * @param string $patch Path to the patch file
754
	 * @param bool $fullpath Whether to treat $patch path as a relative or not
755
	 * @return bool False if this was skipped because schema changes are skipped
756
	 */
757 View Code Duplication
	protected function dropField( $table, $field, $patch, $fullpath = false ) {
758
		if ( !$this->doTable( $table ) ) {
759
			return true;
760
		}
761
762
		if ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
763
			return $this->applyPatch( $patch, $fullpath, "Table $table contains $field field. Dropping" );
764
		} else {
765
			$this->output( "...$table table does not contain $field field.\n" );
766
		}
767
768
		return true;
769
	}
770
771
	/**
772
	 * Drop an index from an existing table
773
	 *
774
	 * @param string $table Name of the table to modify
775
	 * @param string $index Name of the index
776
	 * @param string $patch Path to the patch file
777
	 * @param bool $fullpath Whether to treat $patch path as a relative or not
778
	 * @return bool False if this was skipped because schema changes are skipped
779
	 */
780 View Code Duplication
	protected function dropIndex( $table, $index, $patch, $fullpath = false ) {
781
		if ( !$this->doTable( $table ) ) {
782
			return true;
783
		}
784
785
		if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
786
			return $this->applyPatch( $patch, $fullpath, "Dropping $index index from table $table" );
787
		} else {
788
			$this->output( "...$index key doesn't exist.\n" );
789
		}
790
791
		return true;
792
	}
793
794
	/**
795
	 * Rename an index from an existing table
796
	 *
797
	 * @param string $table Name of the table to modify
798
	 * @param string $oldIndex Old name of the index
799
	 * @param string $newIndex New name of the index
800
	 * @param bool $skipBothIndexExistWarning Whether to warn if both the
801
	 * old and the new indexes exist.
802
	 * @param string $patch Path to the patch file
803
	 * @param bool $fullpath Whether to treat $patch path as a relative or not
804
	 * @return bool False if this was skipped because schema changes are skipped
805
	 */
806
	protected function renameIndex( $table, $oldIndex, $newIndex,
807
		$skipBothIndexExistWarning, $patch, $fullpath = false
808
	) {
809
		if ( !$this->doTable( $table ) ) {
810
			return true;
811
		}
812
813
		// First requirement: the table must exist
814
		if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
815
			$this->output( "...skipping: '$table' table doesn't exist yet.\n" );
816
817
			return true;
818
		}
819
820
		// Second requirement: the new index must be missing
821 View Code Duplication
		if ( $this->db->indexExists( $table, $newIndex, __METHOD__ ) ) {
822
			$this->output( "...index $newIndex already set on $table table.\n" );
823
			if ( !$skipBothIndexExistWarning &&
824
				$this->db->indexExists( $table, $oldIndex, __METHOD__ )
825
			) {
826
				$this->output( "...WARNING: $oldIndex still exists, despite it has " .
827
					"been renamed into $newIndex (which also exists).\n" .
828
					"            $oldIndex should be manually removed if not needed anymore.\n" );
829
			}
830
831
			return true;
832
		}
833
834
		// Third requirement: the old index must exist
835
		if ( !$this->db->indexExists( $table, $oldIndex, __METHOD__ ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->db->indexExists($... $oldIndex, __METHOD__) of type null|boolean is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
836
			$this->output( "...skipping: index $oldIndex doesn't exist.\n" );
837
838
			return true;
839
		}
840
841
		// Requirements have been satisfied, patch can be applied
842
		return $this->applyPatch(
843
			$patch,
844
			$fullpath,
845
			"Renaming index $oldIndex into $newIndex to table $table"
846
		);
847
	}
848
849
	/**
850
	 * If the specified table exists, drop it, or execute the
851
	 * patch if one is provided.
852
	 *
853
	 * Public @since 1.20
854
	 *
855
	 * @param string $table Table to drop.
856
	 * @param string|bool $patch String of patch file that will drop the table. Default: false.
857
	 * @param bool $fullpath Whether $patch is a full path. Default: false.
858
	 * @return bool False if this was skipped because schema changes are skipped
859
	 */
860
	public function dropTable( $table, $patch = false, $fullpath = false ) {
861
		if ( !$this->doTable( $table ) ) {
862
			return true;
863
		}
864
865
		if ( $this->db->tableExists( $table, __METHOD__ ) ) {
866
			$msg = "Dropping table $table";
867
868
			if ( $patch === false ) {
869
				$this->output( "$msg ..." );
870
				$this->db->dropTable( $table, __METHOD__ );
871
				$this->output( "done.\n" );
872
			} else {
873
				return $this->applyPatch( $patch, $fullpath, $msg );
0 ignored issues
show
Bug introduced by
It seems like $patch defined by parameter $patch on line 860 can also be of type boolean; however, DatabaseUpdater::applyPatch() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
874
			}
875
		} else {
876
			$this->output( "...$table doesn't exist.\n" );
877
		}
878
879
		return true;
880
	}
881
882
	/**
883
	 * Modify an existing field
884
	 *
885
	 * @param string $table Name of the table to which the field belongs
886
	 * @param string $field Name of the field to modify
887
	 * @param string $patch Path to the patch file
888
	 * @param bool $fullpath Whether to treat $patch path as a relative or not
889
	 * @return bool False if this was skipped because schema changes are skipped
890
	 */
891
	public function modifyField( $table, $field, $patch, $fullpath = false ) {
892
		if ( !$this->doTable( $table ) ) {
893
			return true;
894
		}
895
896
		$updateKey = "$table-$field-$patch";
897
		if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
898
			$this->output( "...$table table does not exist, skipping modify field patch.\n" );
899
		} elseif ( !$this->db->fieldExists( $table, $field, __METHOD__ ) ) {
900
			$this->output( "...$field field does not exist in $table table, " .
901
				"skipping modify field patch.\n" );
902
		} elseif ( $this->updateRowExists( $updateKey ) ) {
903
			$this->output( "...$field in table $table already modified by patch $patch.\n" );
904
		} else {
905
			$this->insertUpdateRow( $updateKey );
906
907
			return $this->applyPatch( $patch, $fullpath, "Modifying $field field of table $table" );
908
		}
909
910
		return true;
911
	}
912
913
	/**
914
	 * Set any .htaccess files or equivilent for storage repos
915
	 *
916
	 * Some zones (e.g. "temp") used to be public and may have been initialized as such
917
	 */
918
	public function setFileAccess() {
919
		$repo = RepoGroup::singleton()->getLocalRepo();
920
		$zonePath = $repo->getZonePath( 'temp' );
921
		if ( $repo->getBackend()->directoryExists( [ 'dir' => $zonePath ] ) ) {
922
			// If the directory was never made, then it will have the right ACLs when it is made
923
			$status = $repo->getBackend()->secure( [
924
				'dir' => $zonePath,
925
				'noAccess' => true,
926
				'noListing' => true
927
			] );
928
			if ( $status->isOK() ) {
929
				$this->output( "Set the local repo temp zone container to be private.\n" );
930
			} else {
931
				$this->output( "Failed to set the local repo temp zone container to be private.\n" );
932
			}
933
		}
934
	}
935
936
	/**
937
	 * Purge the objectcache table
938
	 */
939
	public function purgeCache() {
940
		global $wgLocalisationCacheConf;
941
		# We can't guarantee that the user will be able to use TRUNCATE,
942
		# but we know that DELETE is available to us
943
		$this->output( "Purging caches..." );
944
		$this->db->delete( 'objectcache', '*', __METHOD__ );
945
		if ( $wgLocalisationCacheConf['manualRecache'] ) {
946
			$this->rebuildLocalisationCache();
947
		}
948
		$blobStore = new MessageBlobStore();
949
		$blobStore->clear();
950
		$this->db->delete( 'module_deps', '*', __METHOD__ );
951
		$this->output( "done.\n" );
952
	}
953
954
	/**
955
	 * Check the site_stats table is not properly populated.
956
	 */
957
	protected function checkStats() {
958
		$this->output( "...site_stats is populated..." );
959
		$row = $this->db->selectRow( 'site_stats', '*', [ 'ss_row_id' => 1 ], __METHOD__ );
960
		if ( $row === false ) {
961
			$this->output( "data is missing! rebuilding...\n" );
962
		} elseif ( isset( $row->site_stats ) && $row->ss_total_pages == -1 ) {
963
			$this->output( "missing ss_total_pages, rebuilding...\n" );
964
		} else {
965
			$this->output( "done.\n" );
966
967
			return;
968
		}
969
		SiteStatsInit::doAllAndCommit( $this->db );
970
	}
971
972
	# Common updater functions
973
974
	/**
975
	 * Sets the number of active users in the site_stats table
976
	 */
977
	protected function doActiveUsersInit() {
978
		$activeUsers = $this->db->selectField( 'site_stats', 'ss_active_users', false, __METHOD__ );
979
		if ( $activeUsers == -1 ) {
980
			$activeUsers = $this->db->selectField( 'recentchanges',
981
				'COUNT( DISTINCT rc_user_text )',
982
				[ 'rc_user != 0', 'rc_bot' => 0, "rc_log_type != 'newusers'" ], __METHOD__
983
			);
984
			$this->db->update( 'site_stats',
985
				[ 'ss_active_users' => intval( $activeUsers ) ],
986
				[ 'ss_row_id' => 1 ], __METHOD__, [ 'LIMIT' => 1 ]
987
			);
988
		}
989
		$this->output( "...ss_active_users user count set...\n" );
990
	}
991
992
	/**
993
	 * Populates the log_user_text field in the logging table
994
	 */
995 View Code Duplication
	protected function doLogUsertextPopulation() {
996
		if ( !$this->updateRowExists( 'populate log_usertext' ) ) {
997
			$this->output(
998
				"Populating log_user_text field, printing progress markers. For large\n" .
999
				"databases, you may want to hit Ctrl-C and do this manually with\n" .
1000
				"maintenance/populateLogUsertext.php.\n"
1001
			);
1002
1003
			$task = $this->maintenance->runChild( 'PopulateLogUsertext' );
0 ignored issues
show
Bug introduced by
The property maintenance does not seem to exist. Did you mean postDatabaseUpdateMaintenance?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1004
			$task->execute();
1005
			$this->output( "done.\n" );
1006
		}
1007
	}
1008
1009
	/**
1010
	 * Migrate log params to new table and index for searching
1011
	 */
1012 View Code Duplication
	protected function doLogSearchPopulation() {
1013
		if ( !$this->updateRowExists( 'populate log_search' ) ) {
1014
			$this->output(
1015
				"Populating log_search table, printing progress markers. For large\n" .
1016
				"databases, you may want to hit Ctrl-C and do this manually with\n" .
1017
				"maintenance/populateLogSearch.php.\n" );
1018
1019
			$task = $this->maintenance->runChild( 'PopulateLogSearch' );
0 ignored issues
show
Bug introduced by
The property maintenance does not seem to exist. Did you mean postDatabaseUpdateMaintenance?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1020
			$task->execute();
1021
			$this->output( "done.\n" );
1022
		}
1023
	}
1024
1025
	/**
1026
	 * Updates the timestamps in the transcache table
1027
	 * @return bool
1028
	 */
1029
	protected function doUpdateTranscacheField() {
1030
		if ( $this->updateRowExists( 'convert transcache field' ) ) {
1031
			$this->output( "...transcache tc_time already converted.\n" );
1032
1033
			return true;
1034
		}
1035
1036
		return $this->applyPatch( 'patch-tc-timestamp.sql', false,
1037
			"Converting tc_time from UNIX epoch to MediaWiki timestamp" );
1038
	}
1039
1040
	/**
1041
	 * Update CategoryLinks collation
1042
	 */
1043
	protected function doCollationUpdate() {
1044
		global $wgCategoryCollation;
1045
		if ( $this->db->fieldExists( 'categorylinks', 'cl_collation', __METHOD__ ) ) {
1046
			if ( $this->db->selectField(
1047
				'categorylinks',
1048
				'COUNT(*)',
1049
				'cl_collation != ' . $this->db->addQuotes( $wgCategoryCollation ),
1050
				__METHOD__
1051
				) == 0
1052
			) {
1053
				$this->output( "...collations up-to-date.\n" );
1054
1055
				return;
1056
			}
1057
1058
			$this->output( "Updating category collations..." );
1059
			$task = $this->maintenance->runChild( 'UpdateCollation' );
0 ignored issues
show
Bug introduced by
The property maintenance does not seem to exist. Did you mean postDatabaseUpdateMaintenance?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1060
			$task->execute();
1061
			$this->output( "...done.\n" );
1062
		}
1063
	}
1064
1065
	/**
1066
	 * Migrates user options from the user table blob to user_properties
1067
	 */
1068
	protected function doMigrateUserOptions() {
1069
		if ( $this->db->tableExists( 'user_properties' ) ) {
1070
			$cl = $this->maintenance->runChild( 'ConvertUserOptions', 'convertUserOptions.php' );
0 ignored issues
show
Bug introduced by
The property maintenance does not seem to exist. Did you mean postDatabaseUpdateMaintenance?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1071
			$cl->execute();
1072
			$this->output( "done.\n" );
1073
		}
1074
	}
1075
1076
	/**
1077
	 * Enable profiling table when it's turned on
1078
	 */
1079
	protected function doEnableProfiling() {
1080
		global $wgProfiler;
1081
1082
		if ( !$this->doTable( 'profiling' ) ) {
1083
			return true;
1084
		}
1085
1086
		$profileToDb = false;
1087 View Code Duplication
		if ( isset( $wgProfiler['output'] ) ) {
1088
			$out = $wgProfiler['output'];
1089
			if ( $out === 'db' ) {
1090
				$profileToDb = true;
1091
			} elseif ( is_array( $out ) && in_array( 'db', $out ) ) {
1092
				$profileToDb = true;
1093
			}
1094
		}
1095
1096
		if ( $profileToDb && !$this->db->tableExists( 'profiling', __METHOD__ ) ) {
1097
			$this->applyPatch( 'patch-profiling.sql', false, 'Add profiling table' );
1098
		}
1099
	}
1100
1101
	/**
1102
	 * Rebuilds the localisation cache
1103
	 */
1104
	protected function rebuildLocalisationCache() {
1105
		/**
1106
		 * @var $cl RebuildLocalisationCache
1107
		 */
1108
		$cl = $this->maintenance->runChild( 'RebuildLocalisationCache', 'rebuildLocalisationCache.php' );
0 ignored issues
show
Bug introduced by
The property maintenance does not seem to exist. Did you mean postDatabaseUpdateMaintenance?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1109
		$this->output( "Rebuilding localisation cache...\n" );
1110
		$cl->setForce();
1111
		$cl->execute();
1112
		$this->output( "done.\n" );
1113
	}
1114
1115
	/**
1116
	 * Turns off content handler fields during parts of the upgrade
1117
	 * where they aren't available.
1118
	 */
1119
	protected function disableContentHandlerUseDB() {
1120
		global $wgContentHandlerUseDB;
1121
1122
		if ( $wgContentHandlerUseDB ) {
1123
			$this->output( "Turning off Content Handler DB fields for this part of upgrade.\n" );
1124
			$this->holdContentHandlerUseDB = $wgContentHandlerUseDB;
1125
			$wgContentHandlerUseDB = false;
1126
		}
1127
	}
1128
1129
	/**
1130
	 * Turns content handler fields back on.
1131
	 */
1132
	protected function enableContentHandlerUseDB() {
1133
		global $wgContentHandlerUseDB;
1134
1135
		if ( $this->holdContentHandlerUseDB ) {
1136
			$this->output( "Content Handler DB fields should be usable now.\n" );
1137
			$wgContentHandlerUseDB = $this->holdContentHandlerUseDB;
1138
		}
1139
	}
1140
}
1141