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