Issues (4122)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/installer/MysqlUpdater.php (26 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * MySQL-specific updater.
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
/**
25
 * Mysql update list and mysql-specific update functions.
26
 *
27
 * @ingroup Deployment
28
 * @since 1.17
29
 */
30
class MysqlUpdater extends DatabaseUpdater {
31
	protected function getCoreUpdateList() {
32
		return [
33
			[ 'disableContentHandlerUseDB' ],
34
35
			// 1.2
36
			[ 'addField', 'ipblocks', 'ipb_id', 'patch-ipblocks.sql' ],
37
			[ 'addField', 'ipblocks', 'ipb_expiry', 'patch-ipb_expiry.sql' ],
38
			[ 'doInterwikiUpdate' ],
39
			[ 'doIndexUpdate' ],
40
			[ 'addField', 'recentchanges', 'rc_type', 'patch-rc_type.sql' ],
41
			[ 'addIndex', 'recentchanges', 'new_name_timestamp', 'patch-rc-newindex.sql' ],
42
43
			// 1.3
44
			[ 'addField', 'user', 'user_real_name', 'patch-user-realname.sql' ],
45
			[ 'addTable', 'querycache', 'patch-querycache.sql' ],
46
			[ 'addTable', 'objectcache', 'patch-objectcache.sql' ],
47
			[ 'addTable', 'categorylinks', 'patch-categorylinks.sql' ],
48
			[ 'doOldLinksUpdate' ],
49
			[ 'doFixAncientImagelinks' ],
50
			[ 'addField', 'recentchanges', 'rc_ip', 'patch-rc_ip.sql' ],
51
52
			// 1.4
53
			[ 'addIndex', 'image', 'PRIMARY', 'patch-image_name_primary.sql' ],
54
			[ 'addField', 'recentchanges', 'rc_id', 'patch-rc_id.sql' ],
55
			[ 'addField', 'recentchanges', 'rc_patrolled', 'patch-rc-patrol.sql' ],
56
			[ 'addTable', 'logging', 'patch-logging.sql' ],
57
			[ 'addField', 'user', 'user_token', 'patch-user_token.sql' ],
58
			[ 'addField', 'watchlist', 'wl_notificationtimestamp', 'patch-email-notification.sql' ],
59
			[ 'doWatchlistUpdate' ],
60
			[ 'dropField', 'user', 'user_emailauthenticationtimestamp',
61
				'patch-email-authentication.sql' ],
62
63
			// 1.5
64
			[ 'doSchemaRestructuring' ],
65
			[ 'addField', 'logging', 'log_params', 'patch-log_params.sql' ],
66
			[ 'checkBin', 'logging', 'log_title', 'patch-logging-title.sql', ],
67
			[ 'addField', 'archive', 'ar_rev_id', 'patch-archive-rev_id.sql' ],
68
			[ 'addField', 'page', 'page_len', 'patch-page_len.sql' ],
69
			[ 'dropField', 'revision', 'inverse_timestamp', 'patch-inverse_timestamp.sql' ],
70
			[ 'addField', 'revision', 'rev_text_id', 'patch-rev_text_id.sql' ],
71
			[ 'addField', 'revision', 'rev_deleted', 'patch-rev_deleted.sql' ],
72
			[ 'addField', 'image', 'img_width', 'patch-img_width.sql' ],
73
			[ 'addField', 'image', 'img_metadata', 'patch-img_metadata.sql' ],
74
			[ 'addField', 'user', 'user_email_token', 'patch-user_email_token.sql' ],
75
			[ 'addField', 'archive', 'ar_text_id', 'patch-archive-text_id.sql' ],
76
			[ 'doNamespaceSize' ],
77
			[ 'addField', 'image', 'img_media_type', 'patch-img_media_type.sql' ],
78
			[ 'doPagelinksUpdate' ],
79
			[ 'dropField', 'image', 'img_type', 'patch-drop_img_type.sql' ],
80
			[ 'doUserUniqueUpdate' ],
81
			[ 'doUserGroupsUpdate' ],
82
			[ 'addField', 'site_stats', 'ss_total_pages', 'patch-ss_total_articles.sql' ],
83
			[ 'addTable', 'user_newtalk', 'patch-usernewtalk2.sql' ],
84
			[ 'addTable', 'transcache', 'patch-transcache.sql' ],
85
			[ 'addField', 'interwiki', 'iw_trans', 'patch-interwiki-trans.sql' ],
86
87
			// 1.6
88
			[ 'doWatchlistNull' ],
89
			[ 'addIndex', 'logging', 'times', 'patch-logging-times-index.sql' ],
90
			[ 'addField', 'ipblocks', 'ipb_range_start', 'patch-ipb_range_start.sql' ],
91
			[ 'doPageRandomUpdate' ],
92
			[ 'addField', 'user', 'user_registration', 'patch-user_registration.sql' ],
93
			[ 'doTemplatelinksUpdate' ],
94
			[ 'addTable', 'externallinks', 'patch-externallinks.sql' ],
95
			[ 'addTable', 'job', 'patch-job.sql' ],
96
			[ 'addField', 'site_stats', 'ss_images', 'patch-ss_images.sql' ],
97
			[ 'addTable', 'langlinks', 'patch-langlinks.sql' ],
98
			[ 'addTable', 'querycache_info', 'patch-querycacheinfo.sql' ],
99
			[ 'addTable', 'filearchive', 'patch-filearchive.sql' ],
100
			[ 'addField', 'ipblocks', 'ipb_anon_only', 'patch-ipb_anon_only.sql' ],
101
			[ 'addIndex', 'recentchanges', 'rc_ns_usertext', 'patch-recentchanges-utindex.sql' ],
102
			[ 'addIndex', 'recentchanges', 'rc_user_text', 'patch-rc_user_text-index.sql' ],
103
104
			// 1.9
105
			[ 'addField', 'user', 'user_newpass_time', 'patch-user_newpass_time.sql' ],
106
			[ 'addTable', 'redirect', 'patch-redirect.sql' ],
107
			[ 'addTable', 'querycachetwo', 'patch-querycachetwo.sql' ],
108
			[ 'addField', 'ipblocks', 'ipb_enable_autoblock', 'patch-ipb_optional_autoblock.sql' ],
109
			[ 'doBacklinkingIndicesUpdate' ],
110
			[ 'addField', 'recentchanges', 'rc_old_len', 'patch-rc_len.sql' ],
111
			[ 'addField', 'user', 'user_editcount', 'patch-user_editcount.sql' ],
112
113
			// 1.10
114
			[ 'doRestrictionsUpdate' ],
115
			[ 'addField', 'logging', 'log_id', 'patch-log_id.sql' ],
116
			[ 'addField', 'revision', 'rev_parent_id', 'patch-rev_parent_id.sql' ],
117
			[ 'addField', 'page_restrictions', 'pr_id', 'patch-page_restrictions_sortkey.sql' ],
118
			[ 'addField', 'revision', 'rev_len', 'patch-rev_len.sql' ],
119
			[ 'addField', 'recentchanges', 'rc_deleted', 'patch-rc_deleted.sql' ],
120
			[ 'addField', 'logging', 'log_deleted', 'patch-log_deleted.sql' ],
121
			[ 'addField', 'archive', 'ar_deleted', 'patch-ar_deleted.sql' ],
122
			[ 'addField', 'ipblocks', 'ipb_deleted', 'patch-ipb_deleted.sql' ],
123
			[ 'addField', 'filearchive', 'fa_deleted', 'patch-fa_deleted.sql' ],
124
			[ 'addField', 'archive', 'ar_len', 'patch-ar_len.sql' ],
125
126
			// 1.11
127
			[ 'addField', 'ipblocks', 'ipb_block_email', 'patch-ipb_emailban.sql' ],
128
			[ 'doCategorylinksIndicesUpdate' ],
129
			[ 'addField', 'oldimage', 'oi_metadata', 'patch-oi_metadata.sql' ],
130
			[ 'addIndex', 'archive', 'usertext_timestamp', 'patch-archive-user-index.sql' ],
131
			[ 'addIndex', 'image', 'img_usertext_timestamp', 'patch-image-user-index.sql' ],
132
			[ 'addIndex', 'oldimage', 'oi_usertext_timestamp', 'patch-oldimage-user-index.sql' ],
133
			[ 'addField', 'archive', 'ar_page_id', 'patch-archive-page_id.sql' ],
134
			[ 'addField', 'image', 'img_sha1', 'patch-img_sha1.sql' ],
135
136
			// 1.12
137
			[ 'addTable', 'protected_titles', 'patch-protected_titles.sql' ],
138
139
			// 1.13
140
			[ 'addField', 'ipblocks', 'ipb_by_text', 'patch-ipb_by_text.sql' ],
141
			[ 'addTable', 'page_props', 'patch-page_props.sql' ],
142
			[ 'addTable', 'updatelog', 'patch-updatelog.sql' ],
143
			[ 'addTable', 'category', 'patch-category.sql' ],
144
			[ 'doCategoryPopulation' ],
145
			[ 'addField', 'archive', 'ar_parent_id', 'patch-ar_parent_id.sql' ],
146
			[ 'addField', 'user_newtalk', 'user_last_timestamp', 'patch-user_last_timestamp.sql' ],
147
			[ 'doPopulateParentId' ],
148
			[ 'checkBin', 'protected_titles', 'pt_title', 'patch-pt_title-encoding.sql', ],
149
			[ 'doMaybeProfilingMemoryUpdate' ],
150
			[ 'doFilearchiveIndicesUpdate' ],
151
152
			// 1.14
153
			[ 'addField', 'site_stats', 'ss_active_users', 'patch-ss_active_users.sql' ],
154
			[ 'doActiveUsersInit' ],
155
			[ 'addField', 'ipblocks', 'ipb_allow_usertalk', 'patch-ipb_allow_usertalk.sql' ],
156
157
			// 1.15
158
			[ 'addTable', 'change_tag', 'patch-change_tag.sql' ],
159
			[ 'addTable', 'tag_summary', 'patch-tag_summary.sql' ],
160
			[ 'addTable', 'valid_tag', 'patch-valid_tag.sql' ],
161
162
			// 1.16
163
			[ 'addTable', 'user_properties', 'patch-user_properties.sql' ],
164
			[ 'addTable', 'log_search', 'patch-log_search.sql' ],
165
			[ 'addField', 'logging', 'log_user_text', 'patch-log_user_text.sql' ],
166
			# listed separately from the previous update because 1.16 was released without this update
167
			[ 'doLogUsertextPopulation' ],
168
			[ 'doLogSearchPopulation' ],
169
			[ 'addTable', 'l10n_cache', 'patch-l10n_cache.sql' ],
170
			[ 'addIndex', 'log_search', 'ls_field_val', 'patch-log_search-rename-index.sql' ],
171
			[ 'addIndex', 'change_tag', 'change_tag_rc_tag', 'patch-change_tag-indexes.sql' ],
172
			[ 'addField', 'redirect', 'rd_interwiki', 'patch-rd_interwiki.sql' ],
173
			[ 'doUpdateTranscacheField' ],
174
			[ 'doUpdateMimeMinorField' ],
175
176
			// 1.17
177
			[ 'addTable', 'iwlinks', 'patch-iwlinks.sql' ],
178
			[ 'addIndex', 'iwlinks', 'iwl_prefix_title_from', 'patch-rename-iwl_prefix.sql' ],
179
			[ 'addField', 'updatelog', 'ul_value', 'patch-ul_value.sql' ],
180
			[ 'addField', 'interwiki', 'iw_api', 'patch-iw_api_and_wikiid.sql' ],
181
			[ 'dropIndex', 'iwlinks', 'iwl_prefix', 'patch-kill-iwl_prefix.sql' ],
182
			[ 'addField', 'categorylinks', 'cl_collation', 'patch-categorylinks-better-collation.sql' ],
183
			[ 'doClFieldsUpdate' ],
184
			[ 'addTable', 'module_deps', 'patch-module_deps.sql' ],
185
			[ 'dropIndex', 'archive', 'ar_page_revid', 'patch-archive_kill_ar_page_revid.sql' ],
186
			[ 'addIndex', 'archive', 'ar_revid', 'patch-archive_ar_revid.sql' ],
187
			[ 'doLangLinksLengthUpdate' ],
188
189
			// 1.18
190
			[ 'doUserNewTalkTimestampNotNull' ],
191
			[ 'addIndex', 'user', 'user_email', 'patch-user_email_index.sql' ],
192
			[ 'modifyField', 'user_properties', 'up_property', 'patch-up_property.sql' ],
193
			[ 'addTable', 'uploadstash', 'patch-uploadstash.sql' ],
194
			[ 'addTable', 'user_former_groups', 'patch-user_former_groups.sql' ],
195
196
			// 1.19
197
			[ 'addIndex', 'logging', 'type_action', 'patch-logging-type-action-index.sql' ],
198
			[ 'addField', 'revision', 'rev_sha1', 'patch-rev_sha1.sql' ],
199
			[ 'doMigrateUserOptions' ],
200
			[ 'dropField', 'user', 'user_options', 'patch-drop-user_options.sql' ],
201
			[ 'addField', 'archive', 'ar_sha1', 'patch-ar_sha1.sql' ],
202
			[ 'addIndex', 'page', 'page_redirect_namespace_len',
203
				'patch-page_redirect_namespace_len.sql' ],
204
			[ 'addField', 'uploadstash', 'us_chunk_inx', 'patch-uploadstash_chunk.sql' ],
205
			[ 'addfield', 'job', 'job_timestamp', 'patch-jobs-add-timestamp.sql' ],
206
207
			// 1.20
208
			[ 'addIndex', 'revision', 'page_user_timestamp', 'patch-revision-user-page-index.sql' ],
209
			[ 'addField', 'ipblocks', 'ipb_parent_block_id', 'patch-ipb-parent-block-id.sql' ],
210
			[ 'addIndex', 'ipblocks', 'ipb_parent_block_id', 'patch-ipb-parent-block-id-index.sql' ],
211
			[ 'dropField', 'category', 'cat_hidden', 'patch-cat_hidden.sql' ],
212
213
			// 1.21
214
			[ 'addField', 'revision', 'rev_content_format', 'patch-revision-rev_content_format.sql' ],
215
			[ 'addField', 'revision', 'rev_content_model', 'patch-revision-rev_content_model.sql' ],
216
			[ 'addField', 'archive', 'ar_content_format', 'patch-archive-ar_content_format.sql' ],
217
			[ 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ],
218
			[ 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ],
219
			[ 'enableContentHandlerUseDB' ],
220
			[ 'dropField', 'site_stats', 'ss_admins', 'patch-drop-ss_admins.sql' ],
221
			[ 'dropField', 'recentchanges', 'rc_moved_to_title', 'patch-rc_moved.sql' ],
222
			[ 'addTable', 'sites', 'patch-sites.sql' ],
223
			[ 'addField', 'filearchive', 'fa_sha1', 'patch-fa_sha1.sql' ],
224
			[ 'addField', 'job', 'job_token', 'patch-job_token.sql' ],
225
			[ 'addField', 'job', 'job_attempts', 'patch-job_attempts.sql' ],
226
			[ 'doEnableProfiling' ],
227
			[ 'addField', 'uploadstash', 'us_props', 'patch-uploadstash-us_props.sql' ],
228
			[ 'modifyField', 'user_groups', 'ug_group', 'patch-ug_group-length-increase-255.sql' ],
229
			[ 'modifyField', 'user_former_groups', 'ufg_group',
230
				'patch-ufg_group-length-increase-255.sql' ],
231
			[ 'addIndex', 'page_props', 'pp_propname_page',
232
				'patch-page_props-propname-page-index.sql' ],
233
			[ 'addIndex', 'image', 'img_media_mime', 'patch-img_media_mime-index.sql' ],
234
235
			// 1.22
236
			[ 'doIwlinksIndexNonUnique' ],
237
			[ 'addIndex', 'iwlinks', 'iwl_prefix_from_title',
238
				'patch-iwlinks-from-title-index.sql' ],
239
			[ 'addField', 'archive', 'ar_id', 'patch-archive-ar_id.sql' ],
240
			[ 'addField', 'externallinks', 'el_id', 'patch-externallinks-el_id.sql' ],
241
242
			// 1.23
243
			[ 'addField', 'recentchanges', 'rc_source', 'patch-rc_source.sql' ],
244
			[ 'addIndex', 'logging', 'log_user_text_type_time',
245
				'patch-logging_user_text_type_time_index.sql' ],
246
			[ 'addIndex', 'logging', 'log_user_text_time', 'patch-logging_user_text_time_index.sql' ],
247
			[ 'addField', 'page', 'page_links_updated', 'patch-page_links_updated.sql' ],
248
			[ 'addField', 'user', 'user_password_expires', 'patch-user_password_expire.sql' ],
249
250
			// 1.24
251
			[ 'addField', 'page_props', 'pp_sortkey', 'patch-pp_sortkey.sql' ],
252
			[ 'dropField', 'recentchanges', 'rc_cur_time', 'patch-drop-rc_cur_time.sql' ],
253
			[ 'addIndex', 'watchlist', 'wl_user_notificationtimestamp',
254
				'patch-watchlist-user-notificationtimestamp-index.sql' ],
255
			[ 'addField', 'page', 'page_lang', 'patch-page_lang.sql' ],
256
			[ 'addField', 'pagelinks', 'pl_from_namespace', 'patch-pl_from_namespace.sql' ],
257
			[ 'addField', 'templatelinks', 'tl_from_namespace', 'patch-tl_from_namespace.sql' ],
258
			[ 'addField', 'imagelinks', 'il_from_namespace', 'patch-il_from_namespace.sql' ],
259
			[ 'modifyField', 'image', 'img_major_mime',
260
				'patch-img_major_mime-chemical.sql' ],
261
			[ 'modifyField', 'oldimage', 'oi_major_mime',
262
				'patch-oi_major_mime-chemical.sql' ],
263
			[ 'modifyField', 'filearchive', 'fa_major_mime',
264
				'patch-fa_major_mime-chemical.sql' ],
265
266
			// 1.25
267
			[ 'doUserNewTalkUseridUnsigned' ],
268
			// note this patch covers other _comment and _description fields too
269
			[ 'modifyField', 'recentchanges', 'rc_comment', 'patch-editsummary-length.sql' ],
270
271
			// 1.26
272
			[ 'dropTable', 'hitcounter' ],
273
			[ 'dropField', 'site_stats', 'ss_total_views', 'patch-drop-ss_total_views.sql' ],
274
			[ 'dropField', 'page', 'page_counter', 'patch-drop-page_counter.sql' ],
275
276
			// 1.27
277
			[ 'dropTable', 'msg_resource_links' ],
278
			[ 'dropTable', 'msg_resource' ],
279
			[ 'addTable', 'bot_passwords', 'patch-bot_passwords.sql' ],
280
			[ 'addField', 'watchlist', 'wl_id', 'patch-watchlist-wl_id.sql' ],
281
			[ 'dropIndex', 'categorylinks', 'cl_collation', 'patch-kill-cl_collation_index.sql' ],
282
			[ 'addIndex', 'categorylinks', 'cl_collation_ext',
283
				'patch-add-cl_collation_ext_index.sql' ],
284
			[ 'doCollationUpdate' ],
285
286
			// 1.28
287
			[ 'addIndex', 'recentchanges', 'rc_name_type_patrolled_timestamp',
288
				'patch-add-rc_name_type_patrolled_timestamp_index.sql' ],
289
			[ 'doRevisionPageRevIndexNonUnique' ],
290
			[ 'doNonUniquePlTlIl' ],
291
			[ 'addField', 'change_tag', 'ct_id', 'patch-change_tag-ct_id.sql' ],
292
			[ 'addField', 'tag_summary', 'ts_id', 'patch-tag_summary-ts_id.sql' ],
293
			[ 'modifyField', 'recentchanges', 'rc_ip', 'patch-rc_ip_modify.sql' ],
294
		];
295
	}
296
297
	/**
298
	 * 1.4 betas were missing the 'binary' marker from logging.log_title,
299
	 * which causes a collation mismatch error on joins in MySQL 4.1.
300
	 *
301
	 * @param string $table Table name
302
	 * @param string $field Field name to check
303
	 * @param string $patchFile Path to the patch to correct the field
304
	 * @return bool
305
	 */
306
	protected function checkBin( $table, $field, $patchFile ) {
307
		if ( !$this->doTable( $table ) ) {
308
			return true;
309
		}
310
311
		/** @var MySQLField $fieldInfo */
312
		$fieldInfo = $this->db->fieldInfo( $table, $field );
313
		if ( $fieldInfo->isBinary() ) {
314
			$this->output( "...$table table has correct $field encoding.\n" );
315
		} else {
316
			$this->applyPatch( $patchFile, false, "Fixing $field encoding on $table table" );
317
		}
318
	}
319
320
	/**
321
	 * Check whether an index contain a field
322
	 *
323
	 * @param string $table Table name
324
	 * @param string $index Index name to check
325
	 * @param string $field Field that should be in the index
326
	 * @return bool
327
	 */
328
	protected function indexHasField( $table, $index, $field ) {
329
		if ( !$this->doTable( $table ) ) {
330
			return true;
331
		}
332
333
		$info = $this->db->indexInfo( $table, $index, __METHOD__ );
334
		if ( $info ) {
335
			foreach ( $info as $row ) {
336
				if ( $row->Column_name == $field ) {
337
					$this->output( "...index $index on table $table includes field $field.\n" );
338
339
					return true;
340
				}
341
			}
342
		}
343
		$this->output( "...index $index on table $table has no field $field; added.\n" );
344
345
		return false;
346
	}
347
348
	/**
349
	 * Check that interwiki table exists; if it doesn't source it
350
	 */
351
	protected function doInterwikiUpdate() {
352
		global $IP;
353
354
		if ( !$this->doTable( 'interwiki' ) ) {
355
			return true;
356
		}
357
358
		if ( $this->db->tableExists( "interwiki", __METHOD__ ) ) {
359
			$this->output( "...already have interwiki table\n" );
360
361
			return;
362
		}
363
364
		$this->applyPatch( 'patch-interwiki.sql', false, 'Creating interwiki table' );
365
		$this->applyPatch(
366
			"$IP/maintenance/interwiki.sql",
367
			true,
368
			'Adding default interwiki definitions'
369
		);
370
	}
371
372
	/**
373
	 * Check that proper indexes are in place
374
	 */
375
	protected function doIndexUpdate() {
376
		$meta = $this->db->fieldInfo( 'recentchanges', 'rc_timestamp' );
377
		if ( $meta === false ) {
378
			throw new MWException( 'Missing rc_timestamp field of recentchanges table. Should not happen.' );
379
		}
380
		if ( $meta->isMultipleKey() ) {
381
			$this->output( "...indexes seem up to 20031107 standards.\n" );
382
383
			return;
384
		}
385
386
		$this->applyPatch( 'patch-indexes.sql', true, "Updating indexes to 20031107" );
387
	}
388
389
	protected function doOldLinksUpdate() {
390
		$cl = $this->maintenance->runChild( 'ConvertLinks' );
0 ignored issues
show
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...
391
		$cl->execute();
392
	}
393
394
	protected function doFixAncientImagelinks() {
395
		$info = $this->db->fieldInfo( 'imagelinks', 'il_from' );
396
		if ( !$info || $info->type() !== 'string' ) {
397
			$this->output( "...il_from OK\n" );
398
399
			return;
400
		}
401
402
		$applied = $this->applyPatch(
403
			'patch-fix-il_from.sql',
404
			false,
405
			'Fixing ancient broken imagelinks table.'
406
		);
407
408
		if ( $applied ) {
409
			$this->output( "NOTE: you will have to run maintenance/refreshLinks.php after this." );
410
		}
411
	}
412
413
	/**
414
	 * Check if we need to add talk page rows to the watchlist
415
	 */
416
	function doWatchlistUpdate() {
417
		$talk = $this->db->selectField( 'watchlist', 'count(*)', 'wl_namespace & 1', __METHOD__ );
418
		$nontalk = $this->db->selectField(
419
			'watchlist',
420
			'count(*)',
421
			'NOT (wl_namespace & 1)',
422
			__METHOD__
423
		);
424
		if ( $talk == $nontalk ) {
425
			$this->output( "...watchlist talk page rows already present.\n" );
426
427
			return;
428
		}
429
430
		$this->output( "Adding missing watchlist talk page rows... " );
431
		$this->db->insertSelect( 'watchlist', 'watchlist',
432
			[
433
				'wl_user' => 'wl_user',
434
				'wl_namespace' => 'wl_namespace | 1',
435
				'wl_title' => 'wl_title',
436
				'wl_notificationtimestamp' => 'wl_notificationtimestamp'
437
			], [ 'NOT (wl_namespace & 1)' ], __METHOD__, 'IGNORE' );
438
		$this->output( "done.\n" );
439
440
		$this->output( "Adding missing watchlist subject page rows... " );
441
		$this->db->insertSelect( 'watchlist', 'watchlist',
442
			[
443
				'wl_user' => 'wl_user',
444
				'wl_namespace' => 'wl_namespace & ~1',
445
				'wl_title' => 'wl_title',
446
				'wl_notificationtimestamp' => 'wl_notificationtimestamp'
447
			], [ 'wl_namespace & 1' ], __METHOD__, 'IGNORE' );
448
		$this->output( "done.\n" );
449
	}
450
451
	function doSchemaRestructuring() {
452
		if ( $this->db->tableExists( 'page', __METHOD__ ) ) {
453
			$this->output( "...page table already exists.\n" );
454
455
			return;
456
		}
457
458
		$this->output( "...converting from cur/old to page/revision/text DB structure.\n" );
459
		$this->output( wfTimestamp( TS_DB ) );
0 ignored issues
show
It seems like wfTimestamp(TS_DB) targeting wfTimestamp() can also be of type false; however, DatabaseUpdater::output() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
460
		$this->output( "......checking for duplicate entries.\n" );
461
462
		list( $cur, $old, $page, $revision, $text ) = $this->db->tableNamesN(
463
			'cur',
464
			'old',
465
			'page',
466
			'revision',
467
			'text'
468
		);
469
470
		$rows = $this->db->query( "
471
			SELECT cur_title, cur_namespace, COUNT(cur_namespace) AS c
472
			FROM $cur
473
			GROUP BY cur_title, cur_namespace
474
			HAVING c>1",
475
			__METHOD__
476
		);
477
478
		if ( $rows->numRows() > 0 ) {
479
			$this->output( wfTimestamp( TS_DB ) );
0 ignored issues
show
It seems like wfTimestamp(TS_DB) targeting wfTimestamp() can also be of type false; however, DatabaseUpdater::output() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
480
			$this->output( "......<b>Found duplicate entries</b>\n" );
481
			$this->output( sprintf( "<b>      %-60s %3s %5s</b>\n", 'Title', 'NS', 'Count' ) );
482
			$duplicate = [];
483
			foreach ( $rows as $row ) {
0 ignored issues
show
The expression $rows of type object<ResultWrapper>|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
484
				if ( !isset( $duplicate[$row->cur_namespace] ) ) {
485
					$duplicate[$row->cur_namespace] = [];
486
				}
487
488
				$duplicate[$row->cur_namespace][] = $row->cur_title;
489
				$this->output( sprintf(
490
					"      %-60s %3s %5s\n",
491
					$row->cur_title, $row->cur_namespace,
492
					$row->c
493
				) );
494
			}
495
			$sql = "SELECT cur_title, cur_namespace, cur_id, cur_timestamp FROM $cur WHERE ";
496
			$firstCond = true;
497
			foreach ( $duplicate as $ns => $titles ) {
498
				if ( $firstCond ) {
499
					$firstCond = false;
500
				} else {
501
					$sql .= ' OR ';
502
				}
503
				$sql .= "( cur_namespace = {$ns} AND cur_title in (";
504
				$first = true;
505
				foreach ( $titles as $t ) {
506
					if ( $first ) {
507
						$sql .= $this->db->addQuotes( $t );
508
						$first = false;
509
					} else {
510
						$sql .= ', ' . $this->db->addQuotes( $t );
511
					}
512
				}
513
				$sql .= ") ) \n";
514
			}
515
			# By sorting descending, the most recent entry will be the first in the list.
516
			# All following entries will be deleted by the next while-loop.
517
			$sql .= 'ORDER BY cur_namespace, cur_title, cur_timestamp DESC';
518
519
			$rows = $this->db->query( $sql, __METHOD__ );
520
521
			$prev_title = $prev_namespace = false;
522
			$deleteId = [];
523
524
			foreach ( $rows as $row ) {
0 ignored issues
show
The expression $rows of type object<ResultWrapper>|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
525
				if ( $prev_title == $row->cur_title && $prev_namespace == $row->cur_namespace ) {
526
					$deleteId[] = $row->cur_id;
527
				}
528
				$prev_title = $row->cur_title;
529
				$prev_namespace = $row->cur_namespace;
530
			}
531
			$sql = "DELETE FROM $cur WHERE cur_id IN ( " . implode( ',', $deleteId ) . ')';
532
			$this->db->query( $sql, __METHOD__ );
533
			$this->output( wfTimestamp( TS_DB ) );
0 ignored issues
show
It seems like wfTimestamp(TS_DB) targeting wfTimestamp() can also be of type false; however, DatabaseUpdater::output() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
534
			$this->output( "......<b>Deleted</b> " . $this->db->affectedRows() . " records.\n" );
535
		}
536
537
		$this->output( wfTimestamp( TS_DB ) );
0 ignored issues
show
It seems like wfTimestamp(TS_DB) targeting wfTimestamp() can also be of type false; however, DatabaseUpdater::output() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
538
		$this->output( "......Creating tables.\n" );
539
		$this->db->query( "CREATE TABLE $page (
540
			page_id int(8) unsigned NOT NULL auto_increment,
541
			page_namespace int NOT NULL,
542
			page_title varchar(255) binary NOT NULL,
543
			page_restrictions tinyblob NOT NULL,
544
			page_is_redirect tinyint(1) unsigned NOT NULL default '0',
545
			page_is_new tinyint(1) unsigned NOT NULL default '0',
546
			page_random real unsigned NOT NULL,
547
			page_touched char(14) binary NOT NULL default '',
548
			page_latest int(8) unsigned NOT NULL,
549
			page_len int(8) unsigned NOT NULL,
550
551
			PRIMARY KEY page_id (page_id),
552
			UNIQUE INDEX name_title (page_namespace,page_title),
553
			INDEX (page_random),
554
			INDEX (page_len)
555
			) ENGINE=InnoDB", __METHOD__ );
556
		$this->db->query( "CREATE TABLE $revision (
557
			rev_id int(8) unsigned NOT NULL auto_increment,
558
			rev_page int(8) unsigned NOT NULL,
559
			rev_comment tinyblob NOT NULL,
560
			rev_user int(5) unsigned NOT NULL default '0',
561
			rev_user_text varchar(255) binary NOT NULL default '',
562
			rev_timestamp char(14) binary NOT NULL default '',
563
			rev_minor_edit tinyint(1) unsigned NOT NULL default '0',
564
			rev_deleted tinyint(1) unsigned NOT NULL default '0',
565
			rev_len int(8) unsigned,
566
			rev_parent_id int(8) unsigned default NULL,
567
			PRIMARY KEY rev_page_id (rev_page, rev_id),
568
			UNIQUE INDEX rev_id (rev_id),
569
			INDEX rev_timestamp (rev_timestamp),
570
			INDEX page_timestamp (rev_page,rev_timestamp),
571
			INDEX user_timestamp (rev_user,rev_timestamp),
572
			INDEX usertext_timestamp (rev_user_text,rev_timestamp)
573
			) ENGINE=InnoDB", __METHOD__ );
574
575
		$this->output( wfTimestamp( TS_DB ) );
0 ignored issues
show
It seems like wfTimestamp(TS_DB) targeting wfTimestamp() can also be of type false; however, DatabaseUpdater::output() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
576
		$this->output( "......Locking tables.\n" );
577
		$this->db->query(
578
			"LOCK TABLES $page WRITE, $revision WRITE, $old WRITE, $cur WRITE",
579
			__METHOD__
580
		);
581
582
		$maxold = intval( $this->db->selectField( 'old', 'max(old_id)', '', __METHOD__ ) );
583
		$this->output( wfTimestamp( TS_DB ) );
0 ignored issues
show
It seems like wfTimestamp(TS_DB) targeting wfTimestamp() can also be of type false; however, DatabaseUpdater::output() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
584
		$this->output( "......maxold is {$maxold}\n" );
585
586
		$this->output( wfTimestamp( TS_DB ) );
0 ignored issues
show
It seems like wfTimestamp(TS_DB) targeting wfTimestamp() can also be of type false; however, DatabaseUpdater::output() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
587
		global $wgLegacySchemaConversion;
588
		if ( $wgLegacySchemaConversion ) {
589
			// Create HistoryBlobCurStub entries.
590
			// Text will be pulled from the leftover 'cur' table at runtime.
591
			$this->output( "......Moving metadata from cur; using blob references to text in cur table.\n" );
592
			$cur_text = "concat('O:18:\"historyblobcurstub\":1:{s:6:\"mCurId\";i:',cur_id,';}')";
593
			$cur_flags = "'object'";
594
		} else {
595
			// Copy all cur text in immediately: this may take longer but avoids
596
			// having to keep an extra table around.
597
			$this->output( "......Moving text from cur.\n" );
598
			$cur_text = 'cur_text';
599
			$cur_flags = "''";
600
		}
601
		$this->db->query(
602
			"INSERT INTO $old (old_namespace, old_title, old_text, old_comment, old_user,
603
				old_user_text, old_timestamp, old_minor_edit, old_flags)
604
			SELECT cur_namespace, cur_title, $cur_text, cur_comment, cur_user, cur_user_text,
605
				cur_timestamp, cur_minor_edit, $cur_flags
606
			FROM $cur",
607
			__METHOD__
608
		);
609
610
		$this->output( wfTimestamp( TS_DB ) );
0 ignored issues
show
It seems like wfTimestamp(TS_DB) targeting wfTimestamp() can also be of type false; however, DatabaseUpdater::output() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
611
		$this->output( "......Setting up revision table.\n" );
612
		$this->db->query(
613
			"INSERT INTO $revision (rev_id, rev_page, rev_comment, rev_user,
614
				rev_user_text, rev_timestamp, rev_minor_edit)
615
			SELECT old_id, cur_id, old_comment, old_user, old_user_text,
616
				old_timestamp, old_minor_edit
617
			FROM $old,$cur WHERE old_namespace=cur_namespace AND old_title=cur_title",
618
			__METHOD__
619
		);
620
621
		$this->output( wfTimestamp( TS_DB ) );
0 ignored issues
show
It seems like wfTimestamp(TS_DB) targeting wfTimestamp() can also be of type false; however, DatabaseUpdater::output() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
622
		$this->output( "......Setting up page table.\n" );
623
		$this->db->query(
624
			"INSERT INTO $page (page_id, page_namespace, page_title,
625
				page_restrictions, page_is_redirect, page_is_new, page_random,
626
				page_touched, page_latest, page_len)
627
			SELECT cur_id, cur_namespace, cur_title, cur_restrictions,
628
				cur_is_redirect, cur_is_new, cur_random, cur_touched, rev_id, LENGTH(cur_text)
629
			FROM $cur,$revision
630
			WHERE cur_id=rev_page AND rev_timestamp=cur_timestamp AND rev_id > {$maxold}",
631
			__METHOD__
632
		);
633
634
		$this->output( wfTimestamp( TS_DB ) );
0 ignored issues
show
It seems like wfTimestamp(TS_DB) targeting wfTimestamp() can also be of type false; however, DatabaseUpdater::output() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
635
		$this->output( "......Unlocking tables.\n" );
636
		$this->db->query( "UNLOCK TABLES", __METHOD__ );
637
638
		$this->output( wfTimestamp( TS_DB ) );
0 ignored issues
show
It seems like wfTimestamp(TS_DB) targeting wfTimestamp() can also be of type false; however, DatabaseUpdater::output() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
639
		$this->output( "......Renaming old.\n" );
640
		$this->db->query( "ALTER TABLE $old RENAME TO $text", __METHOD__ );
641
642
		$this->output( wfTimestamp( TS_DB ) );
0 ignored issues
show
It seems like wfTimestamp(TS_DB) targeting wfTimestamp() can also be of type false; however, DatabaseUpdater::output() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
643
		$this->output( "...done.\n" );
644
	}
645
646
	protected function doNamespaceSize() {
647
		$tables = [
648
			'page' => 'page',
649
			'archive' => 'ar',
650
			'recentchanges' => 'rc',
651
			'watchlist' => 'wl',
652
			'querycache' => 'qc',
653
			'logging' => 'log',
654
		];
655
		foreach ( $tables as $table => $prefix ) {
656
			$field = $prefix . '_namespace';
657
658
			$tablename = $this->db->tableName( $table );
659
			$result = $this->db->query( "SHOW COLUMNS FROM $tablename LIKE '$field'", __METHOD__ );
660
			$info = $this->db->fetchObject( $result );
0 ignored issues
show
It seems like $result defined by $this->db->query("SHOW C...{$field}'", __METHOD__) on line 659 can also be of type boolean; however, IDatabase::fetchObject() does only seem to accept object<ResultWrapper>|object<stdClass>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
661
662
			if ( substr( $info->Type, 0, 3 ) == 'int' ) {
663
				$this->output( "...$field is already a full int ($info->Type).\n" );
664
			} else {
665
				$this->output( "Promoting $field from $info->Type to int... " );
666
				$this->db->query( "ALTER TABLE $tablename MODIFY $field int NOT NULL", __METHOD__ );
667
				$this->output( "done.\n" );
668
			}
669
		}
670
	}
671
672
	protected function doPagelinksUpdate() {
673
		if ( $this->db->tableExists( 'pagelinks', __METHOD__ ) ) {
674
			$this->output( "...already have pagelinks table.\n" );
675
676
			return;
677
		}
678
679
		$this->applyPatch(
680
			'patch-pagelinks.sql',
681
			false,
682
			'Converting links and brokenlinks tables to pagelinks'
683
		);
684
685
		global $wgContLang;
686
		foreach ( $wgContLang->getNamespaces() as $ns => $name ) {
687
			if ( $ns == 0 ) {
688
				continue;
689
			}
690
691
			$this->output( "Cleaning up broken links for namespace $ns... " );
692
			$this->db->update( 'pagelinks',
693
				[
694
					'pl_namespace' => $ns,
695
					"pl_title = TRIM(LEADING {$this->db->addQuotes( "$name:" )} FROM pl_title)",
696
				],
697
				[
698
					'pl_namespace' => 0,
699
					'pl_title' . $this->db->buildLike( "$name:", $this->db->anyString() ),
700
				],
701
				__METHOD__
702
			);
703
			$this->output( "done.\n" );
704
		}
705
	}
706
707
	protected function doUserUniqueUpdate() {
708
		if ( !$this->doTable( 'user' ) ) {
709
			return true;
710
		}
711
712
		$duper = new UserDupes( $this->db, [ $this, 'output' ] );
713
		if ( $duper->hasUniqueIndex() ) {
714
			$this->output( "...already have unique user_name index.\n" );
715
716
			return;
717
		}
718
719
		if ( !$duper->clearDupes() ) {
720
			$this->output( "WARNING: This next step will probably fail due to unfixed duplicates...\n" );
721
		}
722
		$this->applyPatch( 'patch-user_nameindex.sql', false, "Adding unique index on user_name" );
723
	}
724
725
	protected function doUserGroupsUpdate() {
726
		if ( !$this->doTable( 'user_groups' ) ) {
727
			return true;
728
		}
729
730
		if ( $this->db->tableExists( 'user_groups', __METHOD__ ) ) {
731
			$info = $this->db->fieldInfo( 'user_groups', 'ug_group' );
732
			if ( $info->type() == 'int' ) {
733
				$oldug = $this->db->tableName( 'user_groups' );
734
				$newug = $this->db->tableName( 'user_groups_bogus' );
735
				$this->output( "user_groups table exists but is in bogus intermediate " .
736
					"format. Renaming to $newug... " );
737
				$this->db->query( "ALTER TABLE $oldug RENAME TO $newug", __METHOD__ );
738
				$this->output( "done.\n" );
739
740
				$this->applyPatch( 'patch-user_groups.sql', false, "Re-adding fresh user_groups table" );
741
742
				$this->output( "***\n" );
743
				$this->output( "*** WARNING: You will need to manually fix up user " .
744
					"permissions in the user_groups\n" );
745
				$this->output( "*** table. Old 1.5 alpha versions did some pretty funky stuff...\n" );
746
				$this->output( "***\n" );
747
			} else {
748
				$this->output( "...user_groups table exists and is in current format.\n" );
749
			}
750
751
			return;
752
		}
753
754
		$this->applyPatch( 'patch-user_groups.sql', false, "Adding user_groups table" );
755
756
		if ( !$this->db->tableExists( 'user_rights', __METHOD__ ) ) {
757
			if ( $this->db->fieldExists( 'user', 'user_rights', __METHOD__ ) ) {
758
				$this->applyPatch(
759
					'patch-user_rights.sql',
760
					false,
761
					'Upgrading from a 1.3 or older database? Breaking out user_rights for conversion'
762
				);
763
			} else {
764
				$this->output( "*** WARNING: couldn't locate user_rights table or field for upgrade.\n" );
765
				$this->output( "*** You may need to manually configure some sysops by manipulating\n" );
766
				$this->output( "*** the user_groups table.\n" );
767
768
				return;
769
			}
770
		}
771
772
		$this->output( "Converting user_rights table to user_groups... " );
773
		$result = $this->db->select( 'user_rights',
774
			[ 'ur_user', 'ur_rights' ],
775
			[ "ur_rights != ''" ],
776
			__METHOD__ );
777
778
		foreach ( $result as $row ) {
0 ignored issues
show
The expression $result of type boolean|object<ResultWrapper> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
779
			$groups = array_unique(
780
				array_map( 'trim',
781
					explode( ',', $row->ur_rights ) ) );
782
783
			foreach ( $groups as $group ) {
784
				$this->db->insert( 'user_groups',
785
					[
786
						'ug_user' => $row->ur_user,
787
						'ug_group' => $group ],
788
					__METHOD__ );
789
			}
790
		}
791
		$this->output( "done.\n" );
792
	}
793
794
	/**
795
	 * Make sure wl_notificationtimestamp can be NULL,
796
	 * and update old broken items.
797
	 */
798
	protected function doWatchlistNull() {
799
		$info = $this->db->fieldInfo( 'watchlist', 'wl_notificationtimestamp' );
800
		if ( !$info ) {
801
			return;
802
		}
803
		if ( $info->isNullable() ) {
804
			$this->output( "...wl_notificationtimestamp is already nullable.\n" );
805
806
			return;
807
		}
808
809
		$this->applyPatch(
810
			'patch-watchlist-null.sql',
811
			false,
812
			'Making wl_notificationtimestamp nullable'
813
		);
814
	}
815
816
	/**
817
	 * Set page_random field to a random value where it is equals to 0.
818
	 *
819
	 * @see bug 3946
820
	 */
821
	protected function doPageRandomUpdate() {
822
		$page = $this->db->tableName( 'page' );
823
		$this->db->query( "UPDATE $page SET page_random = RAND() WHERE page_random = 0", __METHOD__ );
824
		$rows = $this->db->affectedRows();
825
826
		if ( $rows ) {
827
			$this->output( "Set page_random to a random value on $rows rows where it was set to 0\n" );
828
		} else {
829
			$this->output( "...no page_random rows needed to be set\n" );
830
		}
831
	}
832
833
	protected function doTemplatelinksUpdate() {
834
		if ( $this->db->tableExists( 'templatelinks', __METHOD__ ) ) {
835
			$this->output( "...templatelinks table already exists\n" );
836
837
			return;
838
		}
839
840
		$this->applyPatch( 'patch-templatelinks.sql', false, "Creating templatelinks table" );
841
842
		$this->output( "Populating...\n" );
843
		if ( wfGetLB()->getServerCount() > 1 ) {
0 ignored issues
show
Deprecated Code introduced by
The function wfGetLB() has been deprecated with message: since 1.27, use MediaWikiServices::getDBLoadBalancer() or 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...
844
			// Slow, replication-friendly update
845
			$res = $this->db->select( 'pagelinks', [ 'pl_from', 'pl_namespace', 'pl_title' ],
846
				[ 'pl_namespace' => NS_TEMPLATE ], __METHOD__ );
847
			$count = 0;
848
			foreach ( $res as $row ) {
0 ignored issues
show
The expression $res of type boolean|object<ResultWrapper> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
849
				$count = ( $count + 1 ) % 100;
850
				if ( $count == 0 ) {
851
					wfGetLBFactory()->waitForReplication( [ 'wiki' => wfWikiID() ] );
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...
852
				}
853
				$this->db->insert( 'templatelinks',
854
					[
855
						'tl_from' => $row->pl_from,
856
						'tl_namespace' => $row->pl_namespace,
857
						'tl_title' => $row->pl_title,
858
					], __METHOD__
859
				);
860
			}
861
		} else {
862
			// Fast update
863
			$this->db->insertSelect( 'templatelinks', 'pagelinks',
864
				[
865
					'tl_from' => 'pl_from',
866
					'tl_namespace' => 'pl_namespace',
867
					'tl_title' => 'pl_title'
868
				], [
869
					'pl_namespace' => 10
870
				], __METHOD__
871
			);
872
		}
873
		$this->output( "Done. Please run maintenance/refreshLinks.php for a more " .
874
			"thorough templatelinks update.\n" );
875
	}
876
877
	protected function doBacklinkingIndicesUpdate() {
878
		if ( !$this->indexHasField( 'pagelinks', 'pl_namespace', 'pl_from' ) ||
879
			!$this->indexHasField( 'templatelinks', 'tl_namespace', 'tl_from' ) ||
880
			!$this->indexHasField( 'imagelinks', 'il_to', 'il_from' )
881
		) {
882
			$this->applyPatch( 'patch-backlinkindexes.sql', false, "Updating backlinking indices" );
883
		}
884
	}
885
886
	/**
887
	 * Adding page_restrictions table, obsoleting page.page_restrictions.
888
	 * Migrating old restrictions to new table
889
	 * -- Andrew Garrett, January 2007.
890
	 */
891
	protected function doRestrictionsUpdate() {
892
		if ( $this->db->tableExists( 'page_restrictions', __METHOD__ ) ) {
893
			$this->output( "...page_restrictions table already exists.\n" );
894
895
			return;
896
		}
897
898
		$this->applyPatch(
899
			'patch-page_restrictions.sql',
900
			false,
901
			'Creating page_restrictions table (1/2)'
902
		);
903
		$this->applyPatch(
904
			'patch-page_restrictions_sortkey.sql',
905
			false,
906
			'Creating page_restrictions table (2/2)'
907
		);
908
		$this->output( "done.\n" );
909
910
		$this->output( "Migrating old restrictions to new table...\n" );
911
		$task = $this->maintenance->runChild( 'UpdateRestrictions' );
0 ignored issues
show
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...
912
		$task->execute();
913
	}
914
915
	protected function doCategorylinksIndicesUpdate() {
916
		if ( !$this->indexHasField( 'categorylinks', 'cl_sortkey', 'cl_from' ) ) {
917
			$this->applyPatch( 'patch-categorylinksindex.sql', false, "Updating categorylinks Indices" );
918
		}
919
	}
920
921 View Code Duplication
	protected function doCategoryPopulation() {
922
		if ( $this->updateRowExists( 'populate category' ) ) {
923
			$this->output( "...category table already populated.\n" );
924
925
			return;
926
		}
927
928
		$this->output(
929
			"Populating category table, printing progress markers. " .
930
			"For large databases, you\n" .
931
			"may want to hit Ctrl-C and do this manually with maintenance/\n" .
932
			"populateCategory.php.\n"
933
		);
934
		$task = $this->maintenance->runChild( 'PopulateCategory' );
0 ignored issues
show
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...
935
		$task->execute();
936
		$this->output( "Done populating category table.\n" );
937
	}
938
939
	protected function doPopulateParentId() {
940
		if ( !$this->updateRowExists( 'populate rev_parent_id' ) ) {
941
			$this->output(
942
				"Populating rev_parent_id fields, printing progress markers. For large\n" .
943
				"databases, you may want to hit Ctrl-C and do this manually with\n" .
944
				"maintenance/populateParentId.php.\n" );
945
946
			$task = $this->maintenance->runChild( 'PopulateParentId' );
0 ignored issues
show
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...
947
			$task->execute();
948
		}
949
	}
950
951 View Code Duplication
	protected function doMaybeProfilingMemoryUpdate() {
952
		if ( !$this->doTable( 'profiling' ) ) {
953
			return true;
954
		}
955
956
		if ( !$this->db->tableExists( 'profiling', __METHOD__ ) ) {
957
			return true;
958
		} elseif ( $this->db->fieldExists( 'profiling', 'pf_memory', __METHOD__ ) ) {
959
			$this->output( "...profiling table has pf_memory field.\n" );
960
961
			return true;
962
		}
963
964
		return $this->applyPatch(
965
			'patch-profiling-memory.sql',
966
			false,
967
			'Adding pf_memory field to table profiling'
968
		);
969
	}
970
971
	protected function doFilearchiveIndicesUpdate() {
972
		$info = $this->db->indexInfo( 'filearchive', 'fa_user_timestamp', __METHOD__ );
973
		if ( !$info ) {
974
			$this->applyPatch( 'patch-filearchive-user-index.sql', false, "Updating filearchive indices" );
975
		}
976
977
		return true;
978
	}
979
980 View Code Duplication
	protected function doNonUniquePlTlIl() {
981
		$info = $this->db->indexInfo( 'pagelinks', 'pl_namespace' );
982
		if ( is_array( $info ) && $info[0]->Non_unique ) {
983
			$this->output( "...pl_namespace, tl_namespace, il_to indices are already non-UNIQUE.\n" );
984
985
			return true;
986
		}
987
		if ( $this->skipSchema ) {
988
			$this->output( "...skipping schema change (making pl_namespace, tl_namespace " .
989
				"and il_to indices non-UNIQUE).\n" );
990
991
			return false;
992
		}
993
994
		return $this->applyPatch(
995
			'patch-pl-tl-il-nonunique.sql',
996
			false,
997
			'Making pl_namespace, tl_namespace and il_to indices non-UNIQUE'
998
		);
999
	}
1000
1001
	protected function doUpdateMimeMinorField() {
1002
		if ( $this->updateRowExists( 'mime_minor_length' ) ) {
1003
			$this->output( "...*_mime_minor fields are already long enough.\n" );
1004
1005
			return;
1006
		}
1007
1008
		$this->applyPatch(
1009
			'patch-mime_minor_length.sql',
1010
			false,
1011
			'Altering all *_mime_minor fields to 100 bytes in size'
1012
		);
1013
	}
1014
1015
	protected function doClFieldsUpdate() {
1016
		if ( $this->updateRowExists( 'cl_fields_update' ) ) {
1017
			$this->output( "...categorylinks up-to-date.\n" );
1018
1019
			return;
1020
		}
1021
1022
		$this->applyPatch(
1023
			'patch-categorylinks-better-collation2.sql',
1024
			false,
1025
			'Updating categorylinks (again)'
1026
		);
1027
	}
1028
1029
	protected function doLangLinksLengthUpdate() {
1030
		$langlinks = $this->db->tableName( 'langlinks' );
1031
		$res = $this->db->query( "SHOW COLUMNS FROM $langlinks LIKE 'll_lang'" );
1032
		$row = $this->db->fetchObject( $res );
0 ignored issues
show
It seems like $res defined by $this->db->query("SHOW C...links} LIKE 'll_lang'") on line 1031 can also be of type boolean; however, IDatabase::fetchObject() does only seem to accept object<ResultWrapper>|object<stdClass>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1033
1034
		if ( $row && $row->Type == "varbinary(10)" ) {
1035
			$this->applyPatch(
1036
				'patch-langlinks-ll_lang-20.sql',
1037
				false,
1038
				'Updating length of ll_lang in langlinks'
1039
			);
1040
		} else {
1041
			$this->output( "...ll_lang is up-to-date.\n" );
1042
		}
1043
	}
1044
1045 View Code Duplication
	protected function doUserNewTalkTimestampNotNull() {
1046
		if ( !$this->doTable( 'user_newtalk' ) ) {
1047
			return true;
1048
		}
1049
1050
		$info = $this->db->fieldInfo( 'user_newtalk', 'user_last_timestamp' );
1051
		if ( $info === false ) {
1052
			return;
1053
		}
1054
		if ( $info->isNullable() ) {
1055
			$this->output( "...user_last_timestamp is already nullable.\n" );
1056
1057
			return;
1058
		}
1059
1060
		$this->applyPatch(
1061
			'patch-user-newtalk-timestamp-null.sql',
1062
			false,
1063
			'Making user_last_timestamp nullable'
1064
		);
1065
	}
1066
1067 View Code Duplication
	protected function doIwlinksIndexNonUnique() {
1068
		$info = $this->db->indexInfo( 'iwlinks', 'iwl_prefix_title_from' );
1069
		if ( is_array( $info ) && $info[0]->Non_unique ) {
1070
			$this->output( "...iwl_prefix_title_from index is already non-UNIQUE.\n" );
1071
1072
			return true;
1073
		}
1074
		if ( $this->skipSchema ) {
1075
			$this->output( "...skipping schema change (making iwl_prefix_title_from index non-UNIQUE).\n" );
1076
1077
			return false;
1078
		}
1079
1080
		return $this->applyPatch(
1081
			'patch-iwl_prefix_title_from-non-unique.sql',
1082
			false,
1083
			'Making iwl_prefix_title_from index non-UNIQUE'
1084
		);
1085
	}
1086
1087 View Code Duplication
	protected function doUserNewTalkUseridUnsigned() {
1088
		if ( !$this->doTable( 'user_newtalk' ) ) {
1089
			return true;
1090
		}
1091
1092
		$info = $this->db->fieldInfo( 'user_newtalk', 'user_id' );
1093
		if ( $info === false ) {
1094
			return true;
1095
		}
1096
		if ( $info->isUnsigned() ) {
1097
			$this->output( "...user_id is already unsigned int.\n" );
1098
1099
			return true;
1100
		}
1101
1102
		return $this->applyPatch(
1103
			'patch-user-newtalk-userid-unsigned.sql',
1104
			false,
1105
			'Making user_id unsigned int'
1106
		);
1107
	}
1108
1109 View Code Duplication
	protected function doRevisionPageRevIndexNonUnique() {
1110
		if ( !$this->doTable( 'revision' ) ) {
1111
			return true;
1112
		} elseif ( !$this->db->indexExists( 'revision', 'rev_page_id' ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->db->indexExists('revision', 'rev_page_id') 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...
1113
			$this->output( "...rev_page_id index not found on revision.\n" );
1114
			return true;
1115
		}
1116
1117
		if ( !$this->db->indexUnique( 'revision', 'rev_page_id' ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->db->indexUnique('revision', 'rev_page_id') 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...
1118
			$this->output( "...rev_page_id index already non-unique.\n" );
1119
			return true;
1120
		}
1121
1122
		return $this->applyPatch(
1123
			'patch-revision-page-rev-index-nonunique.sql',
1124
			false,
1125
			'Making rev_page_id index non-unique'
1126
		);
1127
	}
1128
1129
	public function getSchemaVars() {
1130
		global $wgDBTableOptions;
1131
1132
		$vars = [];
1133
		$vars['wgDBTableOptions'] = str_replace( 'TYPE', 'ENGINE', $wgDBTableOptions );
1134
		$vars['wgDBTableOptions'] = str_replace(
1135
			'CHARSET=mysql4',
1136
			'CHARSET=binary',
1137
			$vars['wgDBTableOptions']
1138
		);
1139
1140
		return $vars;
1141
	}
1142
}
1143