[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/installation/src/Model/ -> DatabaseModel.php (source)

   1  <?php
   2  
   3  /**
   4   * @package     Joomla.Installation
   5   * @subpackage  Model
   6   *
   7   * @copyright   (C) 2009 Open Source Matters, Inc. <https://www.joomla.org>
   8   * @license     GNU General Public License version 2 or later; see LICENSE.txt
   9   */
  10  
  11  namespace Joomla\CMS\Installation\Model;
  12  
  13  use Joomla\CMS\Factory;
  14  use Joomla\CMS\Installation\Helper\DatabaseHelper;
  15  use Joomla\CMS\Language\LanguageHelper;
  16  use Joomla\CMS\Language\Text;
  17  use Joomla\CMS\Object\CMSObject;
  18  use Joomla\Database\DatabaseDriver;
  19  use Joomla\Database\DatabaseInterface;
  20  use Joomla\Utilities\ArrayHelper;
  21  
  22  // phpcs:disable PSR1.Files.SideEffects
  23  \defined('_JEXEC') or die;
  24  // phpcs:enable PSR1.Files.SideEffects
  25  
  26  /**
  27   * Database configuration model for the Joomla Core Installer.
  28   *
  29   * @since  3.1
  30   */
  31  class DatabaseModel extends BaseInstallationModel
  32  {
  33      /**
  34       * Get the current setup options from the session.
  35       *
  36       * @return  array  An array of options from the session.
  37       *
  38       * @since   4.0.0
  39       */
  40      public function getOptions()
  41      {
  42          return Factory::getSession()->get('setup.options', array());
  43      }
  44  
  45      /**
  46       * Method to initialise the database.
  47       *
  48       * @param   boolean  $select  Select the database when creating the connections.
  49       *
  50       * @return  DatabaseInterface|boolean  Database object on success, boolean false on failure
  51       *
  52       * @since   3.1
  53       */
  54      public function initialise($select = true)
  55      {
  56          $options = $this->getOptions();
  57  
  58          // Get the options as an object for easier handling.
  59          $options = ArrayHelper::toObject($options);
  60  
  61          // Load the backend language files so that the DB error messages work.
  62          $lang = Factory::getLanguage();
  63          $currentLang = $lang->getTag();
  64  
  65          // Load the selected language
  66          if (LanguageHelper::exists($currentLang, JPATH_ADMINISTRATOR)) {
  67              $lang->load('joomla', JPATH_ADMINISTRATOR, $currentLang, true);
  68          } else {
  69              // Pre-load en-GB in case the chosen language files do not exist.
  70              $lang->load('joomla', JPATH_ADMINISTRATOR, 'en-GB', true);
  71          }
  72  
  73          // Validate and clean up connection parameters
  74          $paramsCheck = DatabaseHelper::validateConnectionParameters($options);
  75  
  76          if ($paramsCheck) {
  77              Factory::getApplication()->enqueueMessage($paramsCheck, 'warning');
  78  
  79              return false;
  80          }
  81  
  82          // Security check for remote db hosts
  83          if (!DatabaseHelper::checkRemoteDbHost($options)) {
  84              // Messages have been enqueued in the called function.
  85              return false;
  86          }
  87  
  88          // Get a database object.
  89          try {
  90              return DatabaseHelper::getDbo(
  91                  $options->db_type,
  92                  $options->db_host,
  93                  $options->db_user,
  94                  $options->db_pass_plain,
  95                  $options->db_name,
  96                  $options->db_prefix,
  97                  $select,
  98                  DatabaseHelper::getEncryptionSettings($options)
  99              );
 100          } catch (\RuntimeException $e) {
 101              Factory::getApplication()->enqueueMessage(Text::sprintf('INSTL_DATABASE_COULD_NOT_CONNECT', $e->getMessage()), 'error');
 102  
 103              return false;
 104          }
 105      }
 106  
 107      /**
 108       * Method to create a new database.
 109       *
 110       * @return  boolean
 111       *
 112       * @since   3.1
 113       * @throws  \RuntimeException
 114       */
 115      public function createDatabase()
 116      {
 117          $options = (object) $this->getOptions();
 118  
 119          $db = $this->initialise(false);
 120  
 121          if ($db === false) {
 122              // Error messages are enqueued by the initialise function, we just need to tell the controller how to redirect
 123              return false;
 124          }
 125  
 126          // Check database version.
 127          $type = $options->db_type;
 128  
 129          try {
 130              $db_version = $db->getVersion();
 131          } catch (\RuntimeException $e) {
 132              /*
 133               * We may get here if the database doesn't exist, if so then explain that to users instead of showing the database connector's error
 134               * This only supports PDO PostgreSQL and the PDO MySQL drivers presently
 135               *
 136               * Error Messages:
 137               * PDO MySQL: [1049] Unknown database 'database_name'
 138               * PDO PostgreSQL: database "database_name" does not exist
 139               */
 140              if (
 141                  $type === 'mysql' && strpos($e->getMessage(), '[1049] Unknown database') === 42
 142                  || $type === 'pgsql' && strpos($e->getMessage(), 'database "' . $options->db_name . '" does not exist')
 143              ) {
 144                  /*
 145                   * Now we're really getting insane here; we're going to try building a new JDatabaseDriver instance
 146                   * in order to trick the connection into creating the database
 147                   */
 148                  if ($type === 'mysql') {
 149                      // MySQL (PDO): Don't specify database name
 150                      $altDBoptions = array(
 151                          'driver'   => $options->db_type,
 152                          'host'     => $options->db_host,
 153                          'user'     => $options->db_user,
 154                          'password' => $options->db_pass_plain,
 155                          'prefix'   => $options->db_prefix,
 156                          'select'   => false,
 157                          DatabaseHelper::getEncryptionSettings($options),
 158                      );
 159                  } else {
 160                      // PostgreSQL (PDO): Use 'postgres'
 161                      $altDBoptions = array(
 162                          'driver'   => $options->db_type,
 163                          'host'     => $options->db_host,
 164                          'user'     => $options->db_user,
 165                          'password' => $options->db_pass_plain,
 166                          'database' => 'postgres',
 167                          'prefix'   => $options->db_prefix,
 168                          'select'   => false,
 169                          DatabaseHelper::getEncryptionSettings($options),
 170                      );
 171                  }
 172  
 173                  $altDB = DatabaseDriver::getInstance($altDBoptions);
 174  
 175                  // Check database server parameters
 176                  $dbServerCheck = DatabaseHelper::checkDbServerParameters($altDB, $options);
 177  
 178                  if ($dbServerCheck) {
 179                      // Some server parameter is not ok
 180                      throw new \RuntimeException($dbServerCheck, 500, $e);
 181                  }
 182  
 183                  // Try to create the database now using the alternate driver
 184                  try {
 185                      $this->createDb($altDB, $options, $altDB->hasUtfSupport());
 186                  } catch (\RuntimeException $e) {
 187                      // We did everything we could
 188                      throw new \RuntimeException(Text::_('INSTL_DATABASE_COULD_NOT_CREATE_DATABASE'), 500, $e);
 189                  }
 190  
 191                  // If we got here, the database should have been successfully created, now try one more time to get the version
 192                  try {
 193                      $db_version = $db->getVersion();
 194                  } catch (\RuntimeException $e) {
 195                      // We did everything we could
 196                      throw new \RuntimeException(Text::sprintf('INSTL_DATABASE_COULD_NOT_CONNECT', $e->getMessage()), 500, $e);
 197                  }
 198              } else {
 199                  // Anything getting into this part of the conditional either doesn't support manually creating the database or isn't that type of error
 200                  throw new \RuntimeException(Text::sprintf('INSTL_DATABASE_COULD_NOT_CONNECT', $e->getMessage()), 500, $e);
 201              }
 202          }
 203  
 204          // Check database server parameters
 205          $dbServerCheck = DatabaseHelper::checkDbServerParameters($db, $options);
 206  
 207          if ($dbServerCheck) {
 208              // Some server parameter is not ok
 209              throw new \RuntimeException($dbServerCheck, 500, $e);
 210          }
 211  
 212          // @internal Check for spaces in beginning or end of name.
 213          if (strlen(trim($options->db_name)) <> strlen($options->db_name)) {
 214              throw new \RuntimeException(Text::_('INSTL_DATABASE_NAME_INVALID_SPACES'));
 215          }
 216  
 217          // @internal Check for asc(00) Null in name.
 218          if (strpos($options->db_name, chr(00)) !== false) {
 219              throw new \RuntimeException(Text::_('INSTL_DATABASE_NAME_INVALID_CHAR'));
 220          }
 221  
 222          // Get database's UTF support.
 223          $utfSupport = $db->hasUtfSupport();
 224  
 225          // Try to select the database.
 226          try {
 227              $db->select($options->db_name);
 228          } catch (\RuntimeException $e) {
 229              // If the database could not be selected, attempt to create it and then select it.
 230              if (!$this->createDb($db, $options, $utfSupport)) {
 231                  throw new \RuntimeException(Text::sprintf('INSTL_DATABASE_ERROR_CREATE', $options->db_name), 500, $e);
 232              }
 233  
 234              $db->select($options->db_name);
 235          }
 236  
 237          // Set the character set to UTF-8 for pre-existing databases.
 238          try {
 239              $db->alterDbCharacterSet($options->db_name);
 240          } catch (\RuntimeException $e) {
 241              // Continue Anyhow
 242          }
 243  
 244          $options = (array) $options;
 245  
 246          // Remove *_errors value.
 247          foreach ($options as $i => $option) {
 248              if (isset($i['1']) && $i['1'] == '*') {
 249                  unset($options[$i]);
 250  
 251                  break;
 252              }
 253          }
 254  
 255          $options = array_merge(['db_created' => 1], $options);
 256  
 257          Factory::getSession()->set('setup.options', $options);
 258  
 259          return true;
 260      }
 261  
 262      /**
 263       * Method to process the old database.
 264       *
 265       * @return  boolean  True on success.
 266       *
 267       * @since   3.1
 268       */
 269      public function handleOldDatabase()
 270      {
 271          $options = $this->getOptions();
 272  
 273          if (!isset($options['db_created']) || !$options['db_created']) {
 274              return $this->createDatabase($options);
 275          }
 276  
 277          // Get the options as an object for easier handling.
 278          $options = ArrayHelper::toObject($options);
 279  
 280          if (!$db = $this->initialise()) {
 281              return false;
 282          }
 283  
 284          // Set the character set to UTF-8 for pre-existing databases.
 285          try {
 286              $db->alterDbCharacterSet($options->db_name);
 287          } catch (\RuntimeException $e) {
 288              // Continue Anyhow
 289          }
 290  
 291          // Backup any old database.
 292          if (!$this->backupDatabase($db, $options->db_prefix)) {
 293              return false;
 294          }
 295  
 296          return true;
 297      }
 298  
 299      /**
 300       * Method to create the database tables.
 301       *
 302       * @param   string  $schema  The SQL schema file to apply.
 303       *
 304       * @return  boolean  True on success.
 305       *
 306       * @since   3.1
 307       */
 308      public function createTables($schema)
 309      {
 310          if (!$db = $this->initialise()) {
 311              return false;
 312          }
 313  
 314          $serverType = $db->getServerType();
 315  
 316          // Set the appropriate schema script based on UTF-8 support.
 317          $schemaFile = 'sql/' . $serverType . '/' . $schema . '.sql';
 318  
 319          // Check if the schema is a valid file
 320          if (!is_file($schemaFile)) {
 321              Factory::getApplication()->enqueueMessage(Text::sprintf('INSTL_ERROR_DB', Text::_('INSTL_DATABASE_NO_SCHEMA')), 'error');
 322  
 323              return false;
 324          }
 325  
 326          // Attempt to import the database schema.
 327          if (!$this->populateDatabase($db, $schemaFile)) {
 328              return false;
 329          }
 330  
 331          return true;
 332      }
 333  
 334      /**
 335       * Method to backup all tables in a database with a given prefix.
 336       *
 337       * @param   DatabaseDriver  $db      JDatabaseDriver object.
 338       * @param   string          $prefix  Database table prefix.
 339       *
 340       * @return  boolean  True on success.
 341       *
 342       * @since    3.1
 343       */
 344      public function backupDatabase($db, $prefix)
 345      {
 346          $return = true;
 347          $backup = 'bak_' . $prefix;
 348  
 349          // Get the tables in the database.
 350          $tables = $db->getTableList();
 351  
 352          if ($tables) {
 353              foreach ($tables as $table) {
 354                  // If the table uses the given prefix, back it up.
 355                  if (strpos($table, $prefix) === 0) {
 356                      // Backup table name.
 357                      $backupTable = str_replace($prefix, $backup, $table);
 358  
 359                      // Drop the backup table.
 360                      try {
 361                          $db->dropTable($backupTable, true);
 362                      } catch (\RuntimeException $e) {
 363                          Factory::getApplication()->enqueueMessage(Text::sprintf('INSTL_DATABASE_ERROR_BACKINGUP', $e->getMessage()), 'error');
 364  
 365                          $return = false;
 366                      }
 367  
 368                      // Rename the current table to the backup table.
 369                      try {
 370                          $db->renameTable($table, $backupTable, $backup, $prefix);
 371                      } catch (\RuntimeException $e) {
 372                          Factory::getApplication()->enqueueMessage(Text::sprintf('INSTL_DATABASE_ERROR_BACKINGUP', $e->getMessage()), 'error');
 373  
 374                          $return = false;
 375                      }
 376                  }
 377              }
 378          }
 379  
 380          return $return;
 381      }
 382  
 383      /**
 384       * Method to create a new database.
 385       *
 386       * @param   DatabaseDriver  $db       Database object.
 387       * @param   CMSObject       $options  CMSObject coming from "initialise" function to pass user
 388       *                                    and database name to database driver.
 389       * @param   boolean         $utf      True if the database supports the UTF-8 character set.
 390       *
 391       * @return  boolean  True on success.
 392       *
 393       * @since   3.1
 394       */
 395      public function createDb($db, $options, $utf)
 396      {
 397          // Build the create database query.
 398          try {
 399              // Run the create database query.
 400              $db->createDatabase($options, $utf);
 401          } catch (\RuntimeException $e) {
 402              // If an error occurred return false.
 403              return false;
 404          }
 405  
 406          return true;
 407      }
 408  
 409      /**
 410       * Method to import a database schema from a file.
 411       *
 412       * @param   \Joomla\Database\DatabaseInterface  $db      JDatabase object.
 413       * @param   string                              $schema  Path to the schema file.
 414       *
 415       * @return  boolean  True on success.
 416       *
 417       * @since   3.1
 418       */
 419      public function populateDatabase($db, $schema)
 420      {
 421          $return = true;
 422  
 423          // Get the contents of the schema file.
 424          if (!($buffer = file_get_contents($schema))) {
 425              Factory::getApplication()->enqueueMessage(Text::_('INSTL_SAMPLE_DATA_NOT_FOUND'), 'error');
 426  
 427              return false;
 428          }
 429  
 430          // Get an array of queries from the schema and process them.
 431          $queries = $this->splitQueries($buffer);
 432  
 433          foreach ($queries as $query) {
 434              // Trim any whitespace.
 435              $query = trim($query);
 436  
 437              // If the query isn't empty and is not a MySQL or PostgreSQL comment, execute it.
 438              if (!empty($query) && ($query[0] != '#') && ($query[0] != '-')) {
 439                  // Execute the query.
 440                  $db->setQuery($query);
 441  
 442                  try {
 443                      $db->execute();
 444                  } catch (\RuntimeException $e) {
 445                      Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
 446  
 447                      $return = false;
 448                  }
 449              }
 450          }
 451  
 452          return $return;
 453      }
 454  
 455      /**
 456       * Method to split up queries from a schema file into an array.
 457       *
 458       * @param   string  $query  SQL schema.
 459       *
 460       * @return  array  Queries to perform.
 461       *
 462       * @since   3.1
 463       */
 464      protected function splitQueries($query)
 465      {
 466          $buffer    = array();
 467          $queries   = array();
 468          $in_string = false;
 469  
 470          // Trim any whitespace.
 471          $query = trim($query);
 472  
 473          // Remove comment lines.
 474          $query = preg_replace("/\n\#[^\n]*/", '', "\n" . $query);
 475  
 476          // Remove PostgreSQL comment lines.
 477          $query = preg_replace("/\n\--[^\n]*/", '', "\n" . $query);
 478  
 479          // Find function.
 480          $funct = explode('CREATE OR REPLACE FUNCTION', $query);
 481  
 482          // Save sql before function and parse it.
 483          $query = $funct[0];
 484  
 485          // Parse the schema file to break up queries.
 486          for ($i = 0; $i < strlen($query) - 1; $i++) {
 487              if ($query[$i] == ';' && !$in_string) {
 488                  $queries[] = substr($query, 0, $i);
 489                  $query     = substr($query, $i + 1);
 490                  $i         = 0;
 491              }
 492  
 493              if ($in_string && ($query[$i] == $in_string) && $buffer[1] != "\\") {
 494                  $in_string = false;
 495              } elseif (!$in_string && ($query[$i] == '"' || $query[$i] == "'") && (!isset($buffer[0]) || $buffer[0] != "\\")) {
 496                  $in_string = $query[$i];
 497              }
 498  
 499              if (isset($buffer[1])) {
 500                  $buffer[0] = $buffer[1];
 501              }
 502  
 503              $buffer[1] = $query[$i];
 504          }
 505  
 506          // If the is anything left over, add it to the queries.
 507          if (!empty($query)) {
 508              $queries[] = $query;
 509          }
 510  
 511          // Add function part as is.
 512          for ($f = 1, $fMax = count($funct); $f < $fMax; $f++) {
 513              $queries[] = 'CREATE OR REPLACE FUNCTION ' . $funct[$f];
 514          }
 515  
 516          return $queries;
 517      }
 518  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer