Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 76
0.00% covered (danger)
0.00%
0 / 22
CRAP
0.00% covered (danger)
0.00%
0 / 1
DatabaseInstaller
0.00% covered (danger)
0.00%
0 / 76
0.00% covered (danger)
0.00%
0 / 22
1980
0.00% covered (danger)
0.00%
0 / 1
 meetsMinimumRequirement
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 getName
n/a
0 / 0
n/a
0 / 0
0
 isCompiled
n/a
0 / 0
n/a
0 / 0
0
 checkPrerequisites
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 openConnection
n/a
0 / 0
n/a
0 / 0
0
 getConnection
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
72
 definitelyGetConnection
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 changeConnType
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
56
 changeConnTypeFromSchemaToTables
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDbType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getConfigVar
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOption
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 provide
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getProvision
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getLocalSettings
n/a
0 / 0
n/a
0 / 0
0
 getSchemaVars
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 preUpgrade
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getGlobalNames
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 checkExtension
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getReadableName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getGlobalDefaults
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 getInternalDefaults
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getVar
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 setVar
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getConnectForm
n/a
0 / 0
n/a
0 / 0
0
 getSettingsForm
n/a
0 / 0
n/a
0 / 0
0
 needsUpgrade
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3/**
4 * DBMS-specific installation helper.
5 *
6 * @license GPL-2.0-or-later
7 * @file
8 * @ingroup Installer
9 */
10
11namespace MediaWiki\Installer;
12
13use MediaWiki\Installer\Task\ITaskContext;
14use MediaWiki\Status\Status;
15use RuntimeException;
16use Wikimedia\Rdbms\DatabaseDomain;
17use Wikimedia\Rdbms\IDatabase;
18use Wikimedia\Rdbms\IMaintainableDatabase;
19
20/**
21 * Base class for DBMS-specific installation helper classes.
22 *
23 * @ingroup Installer
24 * @since 1.17
25 */
26abstract class DatabaseInstaller implements ITaskContext {
27
28    /**
29     * The Installer object.
30     *
31     * @var Installer
32     */
33    public $parent;
34
35    /**
36     * @var string Set by subclasses
37     */
38    public static $minimumVersion;
39
40    /**
41     * @var string Set by subclasses
42     */
43    protected static $notMinimumVersionMessage;
44
45    /**
46     * @deprecated since 1.43 -- use definitelyGetConnection()
47     * @var IMaintainableDatabase
48     */
49    public $db = null;
50
51    /** @var IMaintainableDatabase|null */
52    private $cachedConn;
53    /** @var string|null */
54    private $cachedConnType;
55
56    /**
57     * Internal variables for installation.
58     *
59     * @var array
60     */
61    protected $internalDefaults = [];
62
63    /**
64     * Array of MW configuration globals this class uses.
65     *
66     * @var array
67     */
68    protected $globalNames = [];
69
70    /** @var array */
71    private $provisions = [];
72
73    /**
74     * Whether the provided version meets the necessary requirements for this type
75     *
76     * @param IDatabase $conn
77     * @return Status
78     * @since 1.30
79     */
80    public static function meetsMinimumRequirement( IDatabase $conn ) {
81        $serverVersion = $conn->getServerVersion();
82        if ( version_compare( $serverVersion, static::$minimumVersion ) < 0 ) {
83            return Status::newFatal(
84                static::$notMinimumVersionMessage, static::$minimumVersion, $serverVersion
85            );
86        }
87
88        return Status::newGood();
89    }
90
91    /**
92     * Return the internal name, e.g. 'mysql', or 'sqlite'.
93     */
94    abstract public function getName();
95
96    /**
97     * @return bool Returns true if the client library is compiled in.
98     */
99    abstract public function isCompiled();
100
101    /**
102     * Checks for installation prerequisites other than those checked by isCompiled()
103     * @since 1.19
104     * @return Status
105     */
106    public function checkPrerequisites() {
107        return Status::newGood();
108    }
109
110    /**
111     * Open a connection to the database using the administrative user/password
112     * currently defined in the session, without any caching. Returns a status
113     * object. On success, the status object will contain a Database object in
114     * its value member.
115     *
116     * The database should not be implicitly created.
117     *
118     * @param string $type One of the self::CONN_* constants, except CONN_DONT_KNOW
119     * @return ConnectionStatus
120     */
121    abstract protected function openConnection( string $type );
122
123    /**
124     * Connect to the database using the administrative user/password currently
125     * defined in the session. Returns a status object. On success, the status
126     * object will contain a Database object in its value member.
127     *
128     * This will return a cached connection if one is available.
129     *
130     * @param string $type One of the self::CONN_* constants. Using CONN_DONT_KNOW
131     *   is deprecated and will cause an exception to be thrown in a future release.
132     * @return ConnectionStatus
133     */
134    public function getConnection( $type = self::CONN_DONT_KNOW ): ConnectionStatus {
135        if ( $type === self::CONN_DONT_KNOW ) {
136            if ( $this->cachedConnType ) {
137                $type = $this->cachedConnType;
138            } else {
139                $type = self::CONN_CREATE_DATABASE;
140            }
141        }
142        if ( $this->cachedConn ) {
143            if ( $this->cachedConnType === $type ) {
144                return new ConnectionStatus( $this->cachedConn );
145            } else {
146                return $this->changeConnType( $this->cachedConn, $this->cachedConnType, $type );
147            }
148        }
149        $status = $this->openConnection( $type );
150        if ( $status->isOK() ) {
151            $this->cachedConn = $status->getDB();
152            $this->cachedConnType = $type;
153            // Assign to $this->db for b/c
154            $this->db = $this->cachedConn;
155
156            if ( $type === self::CONN_CREATE_SCHEMA || $type === self::CONN_CREATE_TABLES ) {
157                $this->cachedConn->setSchemaVars( $this->getSchemaVars() );
158            }
159        }
160
161        return $status;
162    }
163
164    /**
165     * Get a connection and unwrap it from its Status object, throwing an
166     * exception on failure.
167     *
168     * @param string $type
169     * @return IMaintainableDatabase
170     */
171    public function definitelyGetConnection( string $type ): IMaintainableDatabase {
172        $status = $this->getConnection( $type );
173        if ( !$status->isOK() ) {
174            throw new RuntimeException( __METHOD__ . ': unexpected DB connection error' );
175        }
176        return $status->getDB();
177    }
178
179    /**
180     * Change the type of a connection.
181     *
182     * CONN_CREATE_DATABASE means the domain is indeterminate and irrelevant,
183     * so converting from this type can be done by selecting the domain, and
184     * converting to it is a no-op.
185     *
186     * CONN_CREATE_SCHEMA means the domain is correct but tables created by
187     * PostgreSQL will have the incorrect role. So to convert from this to
188     * CONN_CREATE_TABLES, we set the role.
189     *
190     * CONN_CREATE_TABLES means a fully-configured connection, suitable for
191     * most tasks, so converting from it is a no-op.
192     *
193     * @param IMaintainableDatabase $conn
194     * @param string &$storedType One of the self::CONN_* constants. An in/out
195     *   parameter, set to the new type on success. It is set to the "real" new
196     *   type, reflecting the highest configuration level reached, to avoid
197     *   unnecessary selectDomain() calls when we need to temporarily give an
198     *   unconfigured connection.
199     * @param string $newType One of the self::CONN_* constants
200     * @return ConnectionStatus
201     */
202    protected function changeConnType( IMaintainableDatabase $conn, &$storedType, $newType ) {
203        // Change type from database to schema, if requested
204        if ( $storedType === self::CONN_CREATE_DATABASE ) {
205            if ( $newType === self::CONN_CREATE_SCHEMA || $newType === self::CONN_CREATE_TABLES ) {
206                // TODO: catch exceptions from selectDomain and report as a Status
207                $conn->selectDomain( new DatabaseDomain(
208                    $this->getVar( 'wgDBname' ),
209                    $this->getVar( 'wgDBmwschema' ),
210                    $this->getVar( 'wgDBprefix' ) ?? ''
211                ) );
212                $conn->setSchemaVars( $this->getSchemaVars() );
213                $storedType = self::CONN_CREATE_SCHEMA;
214            }
215        }
216        // Change type from schema to tables, if requested
217        if ( $newType === self::CONN_CREATE_TABLES && $storedType === self::CONN_CREATE_SCHEMA ) {
218            $status = $this->changeConnTypeFromSchemaToTables( $conn );
219            if ( $status->isOK() ) {
220                $storedType = self::CONN_CREATE_TABLES;
221            }
222            return $status;
223        }
224        return new ConnectionStatus( $conn );
225    }
226
227    /**
228     * Change the type of a connection from CONN_CREATE_SCHEMA to CONN_CREATE_TABLES.
229     * Postgres overrides this.
230     *
231     * @param IMaintainableDatabase $conn
232     * @return ConnectionStatus
233     */
234    protected function changeConnTypeFromSchemaToTables( IMaintainableDatabase $conn ) {
235        return new ConnectionStatus( $conn );
236    }
237
238    public function getDbType(): string {
239        return $this->getName();
240    }
241
242    /** @inheritDoc */
243    public function getConfigVar( string $name ) {
244        return $this->getVar( "wg$name" );
245    }
246
247    /** @inheritDoc */
248    public function getOption( string $name ) {
249        return $this->getVar( "_$name" );
250    }
251
252    /** @inheritDoc */
253    public function provide( string $name, $value ) {
254        $this->provisions[$name] = $value;
255    }
256
257    /** @inheritDoc */
258    public function getProvision( string $name ) {
259        if ( isset( $this->provisions[$name] ) ) {
260            return $this->provisions[$name];
261        } else {
262            throw new \RuntimeException( "Can't find provided data \"$name\"" );
263        }
264    }
265
266    /**
267     * Get the DBMS-specific options for LocalSettings.php generation.
268     *
269     * @return string
270     */
271    abstract public function getLocalSettings();
272
273    /**
274     * Override this to provide DBMS-specific schema variables, to be
275     * substituted into the schema files.
276     * @return array
277     */
278    public function getSchemaVars() {
279        return [];
280    }
281
282    /**
283     * Allow DB installers a chance to make checks before upgrade.
284     */
285    public function preUpgrade() {
286    }
287
288    /**
289     * Get an array of MW configuration globals that will be configured by this class.
290     * @return array
291     */
292    public function getGlobalNames() {
293        return $this->globalNames;
294    }
295
296    /**
297     * Construct and initialise parent.
298     * This is typically only called from Installer::getDBInstaller()
299     * @param Installer $parent
300     */
301    public function __construct( $parent ) {
302        $this->parent = $parent;
303    }
304
305    /**
306     * Convenience function.
307     * Check if a named extension is present.
308     *
309     * @param string $name
310     * @return bool
311     */
312    protected static function checkExtension( $name ) {
313        return extension_loaded( $name );
314    }
315
316    /**
317     * Get the internationalised name for this DBMS.
318     * @return string
319     */
320    public function getReadableName() {
321        // Messages: config-type-mysql, config-type-postgres, config-type-sqlite
322        return wfMessage( 'config-type-' . $this->getName() )->text();
323    }
324
325    /**
326     * Get a name=>value map of MW configuration globals for the default values.
327     * @return array
328     * @return-taint none
329     */
330    public function getGlobalDefaults() {
331        $defaults = [];
332        foreach ( $this->getGlobalNames() as $var ) {
333            if ( isset( $GLOBALS[$var] ) ) {
334                $defaults[$var] = $GLOBALS[$var];
335            }
336        }
337        return $defaults;
338    }
339
340    /**
341     * Get a name=>value map of internal variables used during installation.
342     * @return array
343     */
344    public function getInternalDefaults() {
345        return $this->internalDefaults;
346    }
347
348    /**
349     * Get a variable, taking local defaults into account.
350     * @param string $var
351     * @param mixed|null $default
352     * @return mixed
353     */
354    public function getVar( $var, $default = null ) {
355        $defaults = $this->getGlobalDefaults();
356        $internal = $this->getInternalDefaults();
357        if ( isset( $defaults[$var] ) ) {
358            $default = $defaults[$var];
359        } elseif ( isset( $internal[$var] ) ) {
360            $default = $internal[$var];
361        }
362
363        return $this->parent->getVar( $var, $default );
364    }
365
366    /**
367     * Convenience alias for $this->parent->setVar()
368     * @param string $name
369     * @param mixed $value
370     */
371    public function setVar( $name, $value ) {
372        $this->parent->setVar( $name, $value );
373    }
374
375    abstract public function getConnectForm( WebInstaller $webInstaller ): DatabaseConnectForm;
376
377    abstract public function getSettingsForm( WebInstaller $webInstaller ): DatabaseSettingsForm;
378
379    /**
380     * Determine whether an existing installation of MediaWiki is present in
381     * the configured administrative connection. Returns true if there is
382     * such a wiki, false if the database doesn't exist.
383     *
384     * Traditionally, this is done by testing for the existence of either
385     * the revision table or the cur table.
386     *
387     * @return bool
388     */
389    public function needsUpgrade() {
390        $status = $this->getConnection( self::CONN_CREATE_SCHEMA );
391        if ( !$status->isOK() ) {
392            return false;
393        }
394        $db = $status->getDB();
395        return $db->tableExists( 'cur', __METHOD__ ) ||
396            $db->tableExists( 'revision', __METHOD__ );
397    }
398
399}