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/PostgresInstaller.php (2 issues)

Labels
Severity

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
 * PostgreSQL-specific installer.
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
 * Class for setting up the MediaWiki database using Postgres.
26
 *
27
 * @ingroup Deployment
28
 * @since 1.17
29
 */
30
class PostgresInstaller extends DatabaseInstaller {
31
32
	protected $globalNames = [
33
		'wgDBserver',
34
		'wgDBport',
35
		'wgDBname',
36
		'wgDBuser',
37
		'wgDBpassword',
38
		'wgDBmwschema',
39
	];
40
41
	protected $internalDefaults = [
42
		'_InstallUser' => 'postgres',
43
	];
44
45
	public $minimumVersion = '8.3';
46
	public $maxRoleSearchDepth = 5;
47
48
	protected $pgConns = [];
49
50
	function getName() {
51
		return 'postgres';
52
	}
53
54
	public function isCompiled() {
55
		return self::checkExtension( 'pgsql' );
56
	}
57
58
	function getConnectForm() {
59
		return $this->getTextBox(
60
			'wgDBserver',
61
			'config-db-host',
62
			[],
63
			$this->parent->getHelpBox( 'config-db-host-help' )
64
		) .
65
			$this->getTextBox( 'wgDBport', 'config-db-port' ) .
66
			Html::openElement( 'fieldset' ) .
67
			Html::element( 'legend', [], wfMessage( 'config-db-wiki-settings' )->text() ) .
68
			$this->getTextBox(
69
				'wgDBname',
70
				'config-db-name',
71
				[],
72
				$this->parent->getHelpBox( 'config-db-name-help' )
73
			) .
74
			$this->getTextBox(
75
				'wgDBmwschema',
76
				'config-db-schema',
77
				[],
78
				$this->parent->getHelpBox( 'config-db-schema-help' )
79
			) .
80
			Html::closeElement( 'fieldset' ) .
81
			$this->getInstallUserBox();
82
	}
83
84
	function submitConnectForm() {
85
		// Get variables from the request
86
		$newValues = $this->setVarsFromRequest( [
87
			'wgDBserver',
88
			'wgDBport',
89
			'wgDBname',
90
			'wgDBmwschema'
91
		] );
92
93
		// Validate them
94
		$status = Status::newGood();
95 View Code Duplication
		if ( !strlen( $newValues['wgDBname'] ) ) {
96
			$status->fatal( 'config-missing-db-name' );
97
		} elseif ( !preg_match( '/^[a-zA-Z0-9_]+$/', $newValues['wgDBname'] ) ) {
98
			$status->fatal( 'config-invalid-db-name', $newValues['wgDBname'] );
99
		}
100
		if ( !preg_match( '/^[a-zA-Z0-9_]*$/', $newValues['wgDBmwschema'] ) ) {
101
			$status->fatal( 'config-invalid-schema', $newValues['wgDBmwschema'] );
102
		}
103
104
		// Submit user box
105
		if ( $status->isOK() ) {
106
			$status->merge( $this->submitInstallUserBox() );
107
		}
108
		if ( !$status->isOK() ) {
109
			return $status;
110
		}
111
112
		$status = $this->getPgConnection( 'create-db' );
113
		if ( !$status->isOK() ) {
114
			return $status;
115
		}
116
		/**
117
		 * @var $conn Database
118
		 */
119
		$conn = $status->value;
120
121
		// Check version
122
		$version = $conn->getServerVersion();
123
		if ( version_compare( $version, $this->minimumVersion ) < 0 ) {
124
			return Status::newFatal( 'config-postgres-old', $this->minimumVersion, $version );
125
		}
126
127
		$this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
128
		$this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
129
130
		return Status::newGood();
131
	}
132
133
	public function getConnection() {
134
		$status = $this->getPgConnection( 'create-tables' );
135
		if ( $status->isOK() ) {
136
			$this->db = $status->value;
137
		}
138
139
		return $status;
140
	}
141
142
	public function openConnection() {
143
		return $this->openPgConnection( 'create-tables' );
144
	}
145
146
	/**
147
	 * Open a PG connection with given parameters
148
	 * @param string $user User name
149
	 * @param string $password Password
150
	 * @param string $dbName Database name
151
	 * @param string $schema Database schema
152
	 * @return Status
153
	 */
154
	protected function openConnectionWithParams( $user, $password, $dbName, $schema ) {
155
		$status = Status::newGood();
156
		try {
157
			$db = Database::factory( 'postgres', [
158
				'host' => $this->getVar( 'wgDBserver' ),
159
				'user' => $user,
160
				'password' => $password,
161
				'dbname' => $dbName,
162
				'schema' => $schema ] );
163
			$status->value = $db;
164
		} catch ( DBConnectionError $e ) {
165
			$status->fatal( 'config-connection-error', $e->getMessage() );
166
		}
167
168
		return $status;
169
	}
170
171
	/**
172
	 * Get a special type of connection
173
	 * @param string $type See openPgConnection() for details.
174
	 * @return Status
175
	 */
176
	protected function getPgConnection( $type ) {
177
		if ( isset( $this->pgConns[$type] ) ) {
178
			return Status::newGood( $this->pgConns[$type] );
179
		}
180
		$status = $this->openPgConnection( $type );
181
182
		if ( $status->isOK() ) {
183
			/**
184
			 * @var $conn Database
185
			 */
186
			$conn = $status->value;
187
			$conn->clearFlag( DBO_TRX );
188
			$conn->commit( __METHOD__ );
189
			$this->pgConns[$type] = $conn;
190
		}
191
192
		return $status;
193
	}
194
195
	/**
196
	 * Get a connection of a specific PostgreSQL-specific type. Connections
197
	 * of a given type are cached.
198
	 *
199
	 * PostgreSQL lacks cross-database operations, so after the new database is
200
	 * created, you need to make a separate connection to connect to that
201
	 * database and add tables to it.
202
	 *
203
	 * New tables are owned by the user that creates them, and MediaWiki's
204
	 * PostgreSQL support has always assumed that the table owner will be
205
	 * $wgDBuser. So before we create new tables, we either need to either
206
	 * connect as the other user or to execute a SET ROLE command. Using a
207
	 * separate connection for this allows us to avoid accidental cross-module
208
	 * dependencies.
209
	 *
210
	 * @param string $type The type of connection to get:
211
	 *    - create-db:     A connection for creating DBs, suitable for pre-
212
	 *                     installation.
213
	 *    - create-schema: A connection to the new DB, for creating schemas and
214
	 *                     other similar objects in the new DB.
215
	 *    - create-tables: A connection with a role suitable for creating tables.
216
	 *
217
	 * @throws MWException
218
	 * @return Status On success, a connection object will be in the value member.
219
	 */
220
	protected function openPgConnection( $type ) {
221
		switch ( $type ) {
222
			case 'create-db':
223
				return $this->openConnectionToAnyDB(
224
					$this->getVar( '_InstallUser' ),
225
					$this->getVar( '_InstallPassword' ) );
226
			case 'create-schema':
227
				return $this->openConnectionWithParams(
228
					$this->getVar( '_InstallUser' ),
229
					$this->getVar( '_InstallPassword' ),
230
					$this->getVar( 'wgDBname' ),
231
					$this->getVar( 'wgDBmwschema' ) );
232
			case 'create-tables':
233
				$status = $this->openPgConnection( 'create-schema' );
234
				if ( $status->isOK() ) {
235
					/**
236
					 * @var $conn Database
237
					 */
238
					$conn = $status->value;
239
					$safeRole = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) );
240
					$conn->query( "SET ROLE $safeRole" );
241
				}
242
243
				return $status;
244
			default:
245
				throw new MWException( "Invalid special connection type: \"$type\"" );
246
		}
247
	}
248
249
	public function openConnectionToAnyDB( $user, $password ) {
250
		$dbs = [
251
			'template1',
252
			'postgres',
253
		];
254
		if ( !in_array( $this->getVar( 'wgDBname' ), $dbs ) ) {
255
			array_unshift( $dbs, $this->getVar( 'wgDBname' ) );
256
		}
257
		$conn = false;
258
		$status = Status::newGood();
259
		foreach ( $dbs as $db ) {
260
			try {
261
				$p = [
262
					'host' => $this->getVar( 'wgDBserver' ),
263
					'user' => $user,
264
					'password' => $password,
265
					'dbname' => $db
266
				];
267
				$conn = Database::factory( 'postgres', $p );
268
			} catch ( DBConnectionError $error ) {
269
				$conn = false;
270
				$status->fatal( 'config-pg-test-error', $db,
271
					$error->getMessage() );
272
			}
273
			if ( $conn !== false ) {
274
				break;
275
			}
276
		}
277
		if ( $conn !== false ) {
278
			return Status::newGood( $conn );
279
		} else {
280
			return $status;
281
		}
282
	}
283
284
	protected function getInstallUserPermissions() {
285
		$status = $this->getPgConnection( 'create-db' );
286
		if ( !$status->isOK() ) {
287
			return false;
288
		}
289
		/**
290
		 * @var $conn Database
291
		 */
292
		$conn = $status->value;
293
		$superuser = $this->getVar( '_InstallUser' );
294
295
		$row = $conn->selectRow( '"pg_catalog"."pg_roles"', '*',
296
			[ 'rolname' => $superuser ], __METHOD__ );
297
298
		return $row;
299
	}
300
301
	protected function canCreateAccounts() {
302
		$perms = $this->getInstallUserPermissions();
303
		if ( !$perms ) {
304
			return false;
305
		}
306
307
		return $perms->rolsuper === 't' || $perms->rolcreaterole === 't';
308
	}
309
310
	protected function isSuperUser() {
311
		$perms = $this->getInstallUserPermissions();
312
		if ( !$perms ) {
313
			return false;
314
		}
315
316
		return $perms->rolsuper === 't';
317
	}
318
319
	public function getSettingsForm() {
320
		if ( $this->canCreateAccounts() ) {
321
			$noCreateMsg = false;
322
		} else {
323
			$noCreateMsg = 'config-db-web-no-create-privs';
324
		}
325
		$s = $this->getWebUserBox( $noCreateMsg );
326
327
		return $s;
328
	}
329
330
	public function submitSettingsForm() {
331
		$status = $this->submitWebUserBox();
332
		if ( !$status->isOK() ) {
333
			return $status;
334
		}
335
336
		$same = $this->getVar( 'wgDBuser' ) === $this->getVar( '_InstallUser' );
337
338
		if ( $same ) {
339
			$exists = true;
340
		} else {
341
			// Check if the web user exists
342
			// Connect to the database with the install user
343
			$status = $this->getPgConnection( 'create-db' );
344
			if ( !$status->isOK() ) {
345
				return $status;
346
			}
347
			$exists = $status->value->roleExists( $this->getVar( 'wgDBuser' ) );
348
		}
349
350
		// Validate the create checkbox
351
		if ( $this->canCreateAccounts() && !$same && !$exists ) {
352
			$create = $this->getVar( '_CreateDBAccount' );
353
		} else {
354
			$this->setVar( '_CreateDBAccount', false );
355
			$create = false;
356
		}
357
358
		if ( !$create && !$exists ) {
359
			if ( $this->canCreateAccounts() ) {
360
				$msg = 'config-install-user-missing-create';
361
			} else {
362
				$msg = 'config-install-user-missing';
363
			}
364
365
			return Status::newFatal( $msg, $this->getVar( 'wgDBuser' ) );
366
		}
367
368
		if ( !$exists ) {
369
			// No more checks to do
370
			return Status::newGood();
371
		}
372
373
		// Existing web account. Test the connection.
374
		$status = $this->openConnectionToAnyDB(
375
			$this->getVar( 'wgDBuser' ),
376
			$this->getVar( 'wgDBpassword' ) );
377
		if ( !$status->isOK() ) {
378
			return $status;
379
		}
380
381
		// The web user is conventionally the table owner in PostgreSQL
382
		// installations. Make sure the install user is able to create
383
		// objects on behalf of the web user.
384
		if ( $same || $this->canCreateObjectsForWebUser() ) {
385
			return Status::newGood();
386
		} else {
387
			return Status::newFatal( 'config-pg-not-in-role' );
388
		}
389
	}
390
391
	/**
392
	 * Returns true if the install user is able to create objects owned
393
	 * by the web user, false otherwise.
394
	 * @return bool
395
	 */
396
	protected function canCreateObjectsForWebUser() {
397
		if ( $this->isSuperUser() ) {
398
			return true;
399
		}
400
401
		$status = $this->getPgConnection( 'create-db' );
402
		if ( !$status->isOK() ) {
403
			return false;
404
		}
405
		$conn = $status->value;
406
		$installerId = $conn->selectField( '"pg_catalog"."pg_roles"', 'oid',
407
			[ 'rolname' => $this->getVar( '_InstallUser' ) ], __METHOD__ );
408
		$webId = $conn->selectField( '"pg_catalog"."pg_roles"', 'oid',
409
			[ 'rolname' => $this->getVar( 'wgDBuser' ) ], __METHOD__ );
410
411
		return $this->isRoleMember( $conn, $installerId, $webId, $this->maxRoleSearchDepth );
412
	}
413
414
	/**
415
	 * Recursive helper for canCreateObjectsForWebUser().
416
	 * @param Database $conn
417
	 * @param int $targetMember Role ID of the member to look for
418
	 * @param int $group Role ID of the group to look for
419
	 * @param int $maxDepth Maximum recursive search depth
420
	 * @return bool
421
	 */
422
	protected function isRoleMember( $conn, $targetMember, $group, $maxDepth ) {
423
		if ( $targetMember === $group ) {
424
			// A role is always a member of itself
425
			return true;
426
		}
427
		// Get all members of the given group
428
		$res = $conn->select( '"pg_catalog"."pg_auth_members"', [ 'member' ],
429
			[ 'roleid' => $group ], __METHOD__ );
430
		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...
431
			if ( $row->member == $targetMember ) {
432
				// Found target member
433
				return true;
434
			}
435
			// Recursively search each member of the group to see if the target
436
			// is a member of it, up to the given maximum depth.
437
			if ( $maxDepth > 0 ) {
438
				if ( $this->isRoleMember( $conn, $targetMember, $row->member, $maxDepth - 1 ) ) {
439
					// Found member of member
440
					return true;
441
				}
442
			}
443
		}
444
445
		return false;
446
	}
447
448
	public function preInstall() {
449
		$createDbAccount = [
450
			'name' => 'user',
451
			'callback' => [ $this, 'setupUser' ],
452
		];
453
		$commitCB = [
454
			'name' => 'pg-commit',
455
			'callback' => [ $this, 'commitChanges' ],
456
		];
457
		$plpgCB = [
458
			'name' => 'pg-plpgsql',
459
			'callback' => [ $this, 'setupPLpgSQL' ],
460
		];
461
		$schemaCB = [
462
			'name' => 'schema',
463
			'callback' => [ $this, 'setupSchema' ]
464
		];
465
466
		if ( $this->getVar( '_CreateDBAccount' ) ) {
467
			$this->parent->addInstallStep( $createDbAccount, 'database' );
468
		}
469
		$this->parent->addInstallStep( $commitCB, 'interwiki' );
470
		$this->parent->addInstallStep( $plpgCB, 'database' );
471
		$this->parent->addInstallStep( $schemaCB, 'database' );
472
	}
473
474
	function setupDatabase() {
475
		$status = $this->getPgConnection( 'create-db' );
476
		if ( !$status->isOK() ) {
477
			return $status;
478
		}
479
		$conn = $status->value;
480
481
		$dbName = $this->getVar( 'wgDBname' );
482
483
		$exists = $conn->selectField( '"pg_catalog"."pg_database"', '1',
484
			[ 'datname' => $dbName ], __METHOD__ );
485
		if ( !$exists ) {
486
			$safedb = $conn->addIdentifierQuotes( $dbName );
487
			$conn->query( "CREATE DATABASE $safedb", __METHOD__ );
488
		}
489
490
		return Status::newGood();
491
	}
492
493
	function setupSchema() {
494
		// Get a connection to the target database
495
		$status = $this->getPgConnection( 'create-schema' );
496
		if ( !$status->isOK() ) {
497
			return $status;
498
		}
499
		$conn = $status->value;
500
501
		// Create the schema if necessary
502
		$schema = $this->getVar( 'wgDBmwschema' );
503
		$safeschema = $conn->addIdentifierQuotes( $schema );
504
		$safeuser = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) );
505
		if ( !$conn->schemaExists( $schema ) ) {
506
			try {
507
				$conn->query( "CREATE SCHEMA $safeschema AUTHORIZATION $safeuser" );
508
			} catch ( DBQueryError $e ) {
509
				return Status::newFatal( 'config-install-pg-schema-failed',
510
					$this->getVar( '_InstallUser' ), $schema );
511
			}
512
		}
513
514
		// Select the new schema in the current connection
515
		$conn->determineCoreSchema( $schema );
516
517
		return Status::newGood();
518
	}
519
520
	function commitChanges() {
521
		$this->db->commit( __METHOD__ );
522
523
		return Status::newGood();
524
	}
525
526
	function setupUser() {
527
		if ( !$this->getVar( '_CreateDBAccount' ) ) {
528
			return Status::newGood();
529
		}
530
531
		$status = $this->getPgConnection( 'create-db' );
532
		if ( !$status->isOK() ) {
533
			return $status;
534
		}
535
		$conn = $status->value;
536
537
		$safeuser = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) );
538
		$safepass = $conn->addQuotes( $this->getVar( 'wgDBpassword' ) );
539
540
		// Check if the user already exists
541
		$userExists = $conn->roleExists( $this->getVar( 'wgDBuser' ) );
542
		if ( !$userExists ) {
543
			// Create the user
544
			try {
545
				$sql = "CREATE ROLE $safeuser NOCREATEDB LOGIN PASSWORD $safepass";
546
547
				// If the install user is not a superuser, we need to make the install
548
				// user a member of the new user's group, so that the install user will
549
				// be able to create a schema and other objects on behalf of the new user.
550
				if ( !$this->isSuperUser() ) {
551
					$sql .= ' ROLE' . $conn->addIdentifierQuotes( $this->getVar( '_InstallUser' ) );
552
				}
553
554
				$conn->query( $sql, __METHOD__ );
555
			} catch ( DBQueryError $e ) {
556
				return Status::newFatal( 'config-install-user-create-failed',
557
					$this->getVar( 'wgDBuser' ), $e->getMessage() );
558
			}
559
		}
560
561
		return Status::newGood();
562
	}
563
564
	function getLocalSettings() {
565
		$port = $this->getVar( 'wgDBport' );
566
		$schema = $this->getVar( 'wgDBmwschema' );
567
568
		return "# Postgres specific settings
569
\$wgDBport = \"{$port}\";
570
\$wgDBmwschema = \"{$schema}\";";
571
	}
572
573
	public function preUpgrade() {
574
		global $wgDBuser, $wgDBpassword;
575
576
		# Normal user and password are selected after this step, so for now
577
		# just copy these two
578
		$wgDBuser = $this->getVar( '_InstallUser' );
579
		$wgDBpassword = $this->getVar( '_InstallPassword' );
580
	}
581
582
	public function createTables() {
583
		$schema = $this->getVar( 'wgDBmwschema' );
584
585
		$status = $this->getConnection();
586
		if ( !$status->isOK() ) {
587
			return $status;
588
		}
589
590
		/** @var $conn DatabasePostgres */
591
		$conn = $status->value;
592
593
		if ( $conn->tableExists( 'archive' ) ) {
594
			$status->warning( 'config-install-tables-exist' );
595
			$this->enableLB();
596
597
			return $status;
598
		}
599
600
		$conn->begin( __METHOD__ );
601
602
		if ( !$conn->schemaExists( $schema ) ) {
603
			$status->fatal( 'config-install-pg-schema-not-exist' );
604
605
			return $status;
606
		}
607
		$error = $conn->sourceFile( $this->getSchemaPath( $conn ) );
608
		if ( $error !== true ) {
609
			$conn->reportQueryError( $error, 0, '', __METHOD__ );
0 ignored issues
show
It seems like $error defined by $conn->sourceFile($this->getSchemaPath($conn)) on line 607 can also be of type boolean; however, DatabasePostgres::reportQueryError() does only seem to accept string, 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...
610
			$conn->rollback( __METHOD__ );
611
			$status->fatal( 'config-install-tables-failed', $error );
612
		} else {
613
			$conn->commit( __METHOD__ );
614
		}
615
		// Resume normal operations
616
		if ( $status->isOK() ) {
617
			$this->enableLB();
618
		}
619
620
		return $status;
621
	}
622
623
	public function getGlobalDefaults() {
624
		// The default $wgDBmwschema is null, which breaks Postgres and other DBMSes that require
625
		// the use of a schema, so we need to set it here
626
		return array_merge( parent::getGlobalDefaults(), [
627
			'wgDBmwschema' => 'mediawiki',
628
		] );
629
	}
630
631
	public function setupPLpgSQL() {
632
		// Connect as the install user, since it owns the database and so is
633
		// the user that needs to run "CREATE LANGAUGE"
634
		$status = $this->getPgConnection( 'create-schema' );
635
		if ( !$status->isOK() ) {
636
			return $status;
637
		}
638
		/**
639
		 * @var $conn Database
640
		 */
641
		$conn = $status->value;
642
643
		$exists = $conn->selectField( '"pg_catalog"."pg_language"', 1,
644
			[ 'lanname' => 'plpgsql' ], __METHOD__ );
645
		if ( $exists ) {
646
			// Already exists, nothing to do
647
			return Status::newGood();
648
		}
649
650
		// plpgsql is not installed, but if we have a pg_pltemplate table, we
651
		// should be able to create it
652
		$exists = $conn->selectField(
653
			[ '"pg_catalog"."pg_class"', '"pg_catalog"."pg_namespace"' ],
654
			1,
655
			[
656
				'pg_namespace.oid=relnamespace',
657
				'nspname' => 'pg_catalog',
658
				'relname' => 'pg_pltemplate',
659
			],
660
			__METHOD__ );
661
		if ( $exists ) {
662
			try {
663
				$conn->query( 'CREATE LANGUAGE plpgsql' );
664
			} catch ( DBQueryError $e ) {
665
				return Status::newFatal( 'config-pg-no-plpgsql', $this->getVar( 'wgDBname' ) );
666
			}
667
		} else {
668
			return Status::newFatal( 'config-pg-no-plpgsql', $this->getVar( 'wgDBname' ) );
669
		}
670
671
		return Status::newGood();
672
	}
673
}
674