[ Index ]

PHP Cross Reference of MyBB 1.8.37

title

Body

[close]

/inc/ -> AbstractPdoDbDriver.php (source)

   1  <?php
   2  /**
   3   * MyBB 1.8
   4   * Copyright 2020 MyBB Group, All Rights Reserved
   5   *
   6   * Website: http://www.mybb.com
   7   * License: http://www.mybb.com/about/license
   8   */
   9  
  10  abstract class AbstractPdoDbDriver implements DB_Base
  11  {
  12      /**
  13       * Whether error reporting is enabled.
  14       *
  15       * @var boolean
  16       */
  17      public $error_reporting = 1;
  18  
  19      /**
  20       * The read database connection resource.
  21       *
  22       * @var PDO|null
  23       */
  24      public $read_link = null;
  25  
  26      /**
  27       * The write database connection resource.
  28       *
  29       * @var PDO|null
  30       */
  31      public $write_link = null;
  32  
  33      /**
  34       * Reference to the last database connection resource used.
  35       *
  36       * @var PDO|null
  37       */
  38      public $current_link = null;
  39  
  40      /**
  41       * The database name.
  42       *
  43       * @var string
  44       */
  45      public $database;
  46  
  47      /**
  48       * The database encoding currently in use (if supported).
  49       *
  50       * @var string
  51       */
  52      public $db_encoding = "utf8";
  53  
  54      /**
  55       * The time spent performing queries.
  56       *
  57       * @var float
  58       */
  59      public $query_time = 0;
  60  
  61      /**
  62       * A count of the number of queries.
  63       *
  64       * @var int
  65       */
  66      public $query_count = 0;
  67  
  68      /**
  69       * @var array
  70       */
  71      public $connections = array();
  72  
  73      /**
  74       * @var PDOException|null
  75       */
  76      private $lastPdoException;
  77  
  78      /**
  79       * The type of the previous query.
  80       *
  81       * 1 => write; 0 => read
  82       *
  83       * @var int
  84       */
  85      protected $last_query_type = 0;
  86  
  87      /**
  88       * Used to store row offsets for queries when seeking.
  89       *
  90       * @var array
  91       */
  92      private $resultSeekPositions = array();
  93  
  94      /**
  95       * The last result, used to get the number of affected rows in {@see AbstractPdoDbDriver::affected_rows()}.
  96       *
  97       * @var PDOStatement|null
  98       */
  99      private $lastResult = null;
 100  
 101      /**
 102       * The table prefix used for simple select, update, insert and delete queries
 103       *
 104       * @var string
 105       */
 106      public $table_prefix;
 107  
 108      /**
 109       * The current version of the DBMS.
 110       *
 111       * Note that this is the version used by the {@see AbstractPdoDbDriver::$read_link}.
 112       *
 113       * @var string
 114       */
 115      public $version;
 116  
 117      /**
 118       * A list of the performed queries.
 119       *
 120       * @var array
 121       */
 122      public $querylist = array();
 123  
 124      /**
 125       * The engine used to run the SQL database.
 126       *
 127       * @var string
 128       */
 129      public $engine = "pdo";
 130  
 131      /**
 132       * Whether or not this engine can use the search functionality.
 133       *
 134       * @var boolean
 135       */
 136      public $can_search = true;
 137  
 138      /**
 139       * Build a DSN string using the given configuration.
 140       *
 141       * @param string $hostname The hostname of the database serer to connect to.
 142       * @param string $db The name of the database to connect to.
 143       * @param int|null The optional port to use to connect to the database server.
 144       * @param string|null The character encoding to use for the connection.
 145       *
 146       * @return string The DSN string, including the driver prefix.
 147       */
 148      protected abstract function getDsn($hostname, $db, $port, $encoding);
 149  
 150      /**
 151       * Connect to the database server.
 152       *
 153       * @param array $config Array of DBMS connection details.
 154       *
 155       * @return bool Whether opening the connection was successful.
 156       */
 157  	public function connect($config)
 158      {
 159          $connections = array(
 160              'read' => array(),
 161              'write' => array(),
 162          );
 163  
 164          if (isset($config['hostname'])) {
 165              // simple connection, with single DB server
 166              $connections['read'][] = $config;
 167          } else {
 168              if (!isset($config['read'])) {
 169                  // multiple servers, but no specific read/write servers
 170                  foreach ($config as $key => $settings) {
 171                      if (is_int($key)) {
 172                          $connections['read'][] = $settings;
 173                      }
 174                  }
 175              } else {
 176                  // both read and write servers
 177                  $connections = $config;
 178              }
 179          }
 180  
 181          if (isset($config['encoding'])) {
 182              $this->db_encoding = $config['encoding'];
 183          }
 184  
 185          // Actually connect to the specified servers
 186          foreach (array('read', 'write') as $type) {
 187              if (!isset($connections[$type]) || !is_array($connections[$type])){
 188                  break;
 189              }
 190  
 191              if (isset($connections[$type]['hostname'])) {
 192                  $details = $connections[$type];
 193                  unset($connections[$type]);
 194                  $connections[$type][] = $details;
 195              }
 196  
 197              // shuffle the connections
 198              shuffle($connections[$type]);
 199  
 200              // loop through the connections
 201              foreach($connections[$type] as $singleConnection)
 202              {
 203                  $flags = array(
 204                      PDO::ATTR_PERSISTENT => false,
 205                      PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
 206                      PDO::ATTR_EMULATE_PREPARES => false,
 207                  );
 208  
 209                  if (!empty($singleConnection['pconnect'])) {
 210                      $flags[PDO::ATTR_PERSISTENT] = true;
 211                  }
 212  
 213                  $link = "{$type}_link";
 214  
 215                  get_execution_time();
 216  
 217                  list($hostname, $port) = self::parseHostname($singleConnection['hostname']);
 218  
 219                  $dsn = $this->getDsn(
 220                      $hostname,
 221                      $config['database'],
 222                      $port,
 223                      $this->db_encoding
 224                  );
 225  
 226                  try {
 227                      $this->$link = new PDO(
 228                          $dsn,
 229                          $singleConnection['username'],
 230                          $singleConnection['password'],
 231                          $flags
 232                      );
 233  
 234                      $this->lastPdoException = null;
 235                  } catch (PDOException $e) {
 236                      $this->$link = null;
 237                      $this->lastPdoException = $e;
 238                  }
 239  
 240                  $time_spent = get_execution_time();
 241                  $this->query_time += $time_spent;
 242  
 243                  // Successful connection? break down brother!
 244                  if ($this->$link !== null) {
 245                      $this->connections[] = "[".strtoupper($type)."] {$singleConnection['username']}@{$singleConnection['hostname']} (Connected in ".format_time_duration($time_spent).")";
 246                      break;
 247                  } else {
 248                      $this->connections[] = "<span style=\"color: red\">[FAILED] [".strtoupper($type)."] {$singleConnection['username']}@{$singleConnection['hostname']}</span>";
 249                  }
 250              }
 251          }
 252  
 253          // No write server was specified (simple connection or just multiple servers) - mirror write link
 254          if (empty($connections['write'])) {
 255              $this->write_link = $this->read_link;
 256          }
 257  
 258          // Have no read connection?
 259          if ($this->read_link === null) {
 260              $this->error("[READ] Unable to connect to database server");
 261              return false;
 262          } else if($this->write_link === null) {
 263              $this->error("[WRITE] Unable to connect to database server");
 264              return false;
 265          }
 266  
 267          $this->database = $config['database'];
 268  
 269          if (version_compare(PHP_VERSION, '5.3.6', '<') === true) {
 270              // character set in DSN was ignored before PHP 5.3.6, so we must SET NAMES
 271              $this->setCharacterSet($this->db_encoding);
 272          }
 273  
 274          $this->current_link = $this->read_link;
 275          return true;
 276      }
 277  
 278      /**
 279       * Parse a hostname and possible port combination.
 280       *
 281       * @param string $hostname The hostname string. Can be any of the following formats:
 282       * - `127.0.0.1` - IPv4 address.
 283       * - `[::1]` - IPv6 address.
 284       * - `localhost` - hostname.
 285       * - `127.0.0.1:3306` - IPv4 address and port combination.
 286       *  `[::1]:3306` - IPv6 address and port combination.
 287       * - `localhost:3306` - hostname and port combination.
 288       *
 289       * @return array Array of host and port.
 290       *
 291       * @throws InvalidArgumentException Thrown if {@see $hostname} is an IPv6 address which lacks a closing square bracket.
 292       */
 293  	private static function parseHostname($hostname)
 294      {
 295          // first, check for an IPv6 address - IPv6 addresses always start with `[`
 296          $openingSquareBracket = strpos($hostname, '[');
 297          if ($openingSquareBracket === 0) {
 298              // find ending `]`
 299              $closingSquareBracket = strpos($hostname, ']', $openingSquareBracket);
 300  
 301              if ($closingSquareBracket !== false) {
 302                  $portSeparator = strpos($hostname, ':', $closingSquareBracket);
 303  
 304                  // there is no port specified
 305                  if ($portSeparator === false) {
 306                      return array($hostname, null);
 307                  } else {
 308                      $host = substr($hostname, $openingSquareBracket, $closingSquareBracket + 1);
 309                      $port = (int) substr($hostname, $portSeparator + 1);
 310  
 311                      return array($host, $port);
 312                  }
 313              } else {
 314                  throw new InvalidArgumentException("Hostname is missing a closing square bracket for IPv6 address: {$hostname}");
 315              }
 316          }
 317  
 318          // either an IPv4 address or a hostname
 319          $portSeparator = strpos($hostname, ':', 0);
 320          if ($portSeparator === false) {
 321              return array($hostname, null);
 322          } else {
 323              $host = substr($hostname, 0, $portSeparator);
 324              $port = (int) substr($hostname, $portSeparator + 1);
 325  
 326              return array($host, $port);
 327          }
 328      }
 329  
 330      /**
 331       * Set the character set to use. This issues a `SET NAMES` query to both the read and write links.
 332       *
 333       * @param string $characterSet The character set to use.
 334       *
 335       * @return void
 336       */
 337  	public function setCharacterSet($characterSet)
 338      {
 339          $query = "SET NAMES '{$characterSet}'";
 340  
 341          self::execIgnoreError($this->read_link, $query);
 342  
 343          if ($this->write_link !== $this->read_link) {
 344              self::execIgnoreError($this->write_link, $query);
 345          }
 346      }
 347  
 348      /**
 349       * Execute a query, ignoring any errors.
 350       *
 351       * @param PDO $connection The connection to execute the query on.
 352       * @param string $query The query to execute.
 353       */
 354  	private static function execIgnoreError($connection, $query)
 355      {
 356          try {
 357              $connection->exec($query);
 358          } catch (PDOException $e) {
 359              // ignored on purpose
 360          }
 361      }
 362  
 363      /**
 364       * Output a database error.
 365       *
 366       * @param string $string The string to present as an error.
 367       *
 368       * @return bool Whether error reporting is enabled or not
 369       */
 370  	public function error($string = '')
 371      {
 372          if ($this->error_reporting) {
 373              if (class_exists("errorHandler")) {
 374                  global $error_handler;
 375  
 376                  if(!is_object($error_handler))
 377                  {
 378                      require_once  MYBB_ROOT."inc/class_error.php";
 379                      $error_handler = new errorHandler();
 380                  }
 381  
 382                  $error = array(
 383                      "error_no" => $this->error_number(),
 384                      "error" => $this->error_string(),
 385                      "query" => $string
 386                  );
 387  
 388                  $error_handler->error(MYBB_SQL, $error);
 389              } else {
 390                  trigger_error("<strong>[SQL] [". $this->error_number() ."]" . $this->error_string() . " </strong><br />{$string}", E_USER_ERROR);
 391              }
 392  
 393              return true;
 394          } else {
 395              return false;
 396          }
 397      }
 398  
 399      /**
 400       * Return the error code for the last error that occurred.
 401       *
 402       * @return string|null The error code for the last error that occurred, or null if no error occurred.
 403       */
 404  	public function error_number()
 405      {
 406          if ($this->lastPdoException !== null) {
 407              return $this->lastPdoException->getCode();
 408          }
 409  
 410          return null;
 411      }
 412  
 413      /**
 414       * Return athe error message for the last error that occurred.
 415       *
 416       * @return string|null The error message for the last error that occurred, or null if no error occurred.
 417       */
 418  	public function error_string()
 419      {
 420          if ($this->lastPdoException !== null && isset($this->lastPdoException->errorInfo[2])) {
 421              return $this->lastPdoException->errorInfo[2];
 422          }
 423  
 424          return null;
 425      }
 426  
 427      /**
 428       * Query the database.
 429       *
 430       * @param string $string The query SQL.
 431       * @param boolean|int $hideErrors Whether to hide any errors that occur.
 432       * @param boolean|int $writeQuery Whether to run the query on the write connection rather than the read connection.
 433       *
 434       * @return PDOStatement|null The result of the query, or null if an error occurred and {@see $hideErrors} was set.
 435       */
 436  	public function query($string, $hideErrors = false, $writeQuery = false)
 437      {
 438          global $mybb;
 439  
 440          get_execution_time();
 441  
 442          // Only execute write queries on master server
 443          if (($writeQuery || $this->last_query_type) && $this->write_link) {
 444              $this->current_link = &$this->write_link;
 445          } else {
 446              $this->current_link = &$this->read_link;
 447          }
 448  
 449          /** @var PDOStatement|null $query */
 450          $query = null;
 451  
 452          try {
 453              if (preg_match('/^\\s*SELECT\\b/i', $string) === 1) {
 454                  // NOTE: we use prepare + execute here rather than just query so that we may request a scrollable cursor...
 455                  $query = $this->current_link->prepare($string, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL));
 456                  $query->execute();
 457                  $this->lastPdoException = null;
 458              } else {
 459                  $query = $this->current_link->query($string);
 460                  $this->lastPdoException = null;
 461              }
 462          } catch (PDOException $e) {
 463              $this->lastPdoException = $e;
 464              $query = null;
 465  
 466              if (!$hideErrors) {
 467                  $this->error($string);
 468                  exit;
 469              }
 470          }
 471  
 472          if ($writeQuery) {
 473              $this->last_query_type = 1;
 474          } else {
 475              $this->last_query_type = 0;
 476          }
 477  
 478          $query_time = get_execution_time();
 479          $this->query_time += $query_time;
 480          $this->query_count++;
 481          $this->lastResult = $query;
 482  
 483          if ($mybb->debug_mode) {
 484              $this->explain_query($string, $query_time);
 485          }
 486  
 487          return $query;
 488      }
 489  
 490      /**
 491       * Execute a write query on the master database
 492       *
 493       * @param string $query The query SQL.
 494       * @param boolean|int $hideErrors Whether to hide any errors that occur.
 495       *
 496       * @return PDOStatement|null The result of the query, or null if an error occurred and {@see $hideErrors} was set.
 497       */
 498  	public function write_query($query, $hideErrors = false)
 499      {
 500          return $this->query($query, $hideErrors, true);
 501      }
 502  
 503      /**
 504       * Return a result array for a query.
 505       *
 506       * @param PDOStatement $query The query to retrieve a result for.
 507       * @param int $resultType The type of array to return. Can be any of the following values:
 508       *  - {@see PDO::FETCH_ASSOC} Fetch an array of results keyed by column name. This is the default.
 509       *  - {@see PDO::FETCH_NUM} Fetch an array of results keyed by column number, starting at 0.
 510       *  - {@see PDO::FETCH_BOTH} Fetch an array of results keyed by both column name and number.
 511       *
 512       * @return array|bool The array of results, or false if there are no more results.
 513       */
 514  	public function fetch_array($query, $resultType = PDO::FETCH_ASSOC)
 515      {
 516          if (is_null($query) || !($query instanceof PDOStatement)) {
 517              return false;
 518          }
 519  
 520          switch($resultType)
 521          {
 522              case PDO::FETCH_NUM:
 523              case PDO::FETCH_BOTH:
 524                  break;
 525              default:
 526                  $resultType = PDO::FETCH_ASSOC;
 527                  break;
 528          }
 529  
 530          $hash = spl_object_hash($query);
 531  
 532          if (isset($this->resultSeekPositions[$hash])) {
 533              return $query->fetch($resultType, PDO::FETCH_ORI_ABS, $this->resultSeekPositions[$hash]);
 534          }
 535  
 536          return $query->fetch($resultType);
 537      }
 538  
 539      /**
 540       * Return a specific field from a query.
 541       *
 542       * @param PDOStatement $query The query to retrieve a result for.
 543       * @param string $field The name of the field to return.
 544       * @param int|bool $row The number of the row to fetch it from, or false to fetch from the next row in the result set.
 545       *
 546       * @return mixed The resulting field, of false if no more rows are in th result set.
 547       *  Note that when querying fields that have a boolean value, this method should not be used.
 548       */
 549  	public function fetch_field($query, $field, $row = false)
 550      {
 551          if (is_null($query) || !($query instanceof PDOStatement)) {
 552              return false;
 553          }
 554  
 555          if ($row !== false) {
 556              $this->data_seek($query, (int) $row);
 557          }
 558  
 559          // NOTE: PDOStatement::fetchColumn only operates on numbered columns, so we must fetch the array result
 560          $array = $this->fetch_array($query, PDO::FETCH_ASSOC);
 561  
 562          if ($array === false) {
 563              return false;
 564          }
 565  
 566          return $array[$field];
 567      }
 568  
 569      /**
 570       * Move the internal row pointer to the specified row.
 571       *
 572       * @param PDOStatement $query The query to move the row pointer for.
 573       * @param int $row The row to move to. Rows are numbered from 0.
 574       *
 575       * @return bool Whether seeking was successful.
 576       */
 577  	public function data_seek($query, $row)
 578      {
 579          if (is_null($query) || !($query instanceof PDOStatement)) {
 580              return false;
 581          }
 582  
 583          $hash = spl_object_hash($query);
 584  
 585          // NOTE: PDO numbers rows from 1, but all other drivers are 0 based. We add 1 to the row number for compatibility
 586          $this->resultSeekPositions[$hash] = ((int) $row) + 1;
 587  
 588          return true;
 589      }
 590  
 591      /**
 592       * Return the number of rows resulting from a query.
 593       *
 594       * @param PDOStatement $query The query data.
 595       * @return int|bool The number of rows in the result, or false on failure.
 596       */
 597  	public function num_rows($query)
 598      {
 599          if (is_null($query) || !($query instanceof PDOStatement)) {
 600              return false;
 601          }
 602  
 603          if (preg_match('/^\\s*SELECT\\b/i', $query->queryString) === 1) {
 604              // rowCount does not return the number of rows in a select query on most DBMS, so we instead fetch all results then count them
 605              // TODO: how do we handle the case where we issued a prepared statement with parameters..?
 606              $countQuery = $this->read_link->query($query->queryString);
 607              $result = $countQuery->fetchAll(PDO::FETCH_COLUMN, 0);
 608  
 609              return count($result);
 610          } else {
 611              return $query->rowCount();
 612          }
 613      }
 614  
 615      /**
 616       * Return the last id number of inserted data.
 617       *
 618       * @return string The id number.
 619       */
 620  	public function insert_id()
 621      {
 622          return $this->current_link->lastInsertId();
 623      }
 624  
 625      /**
 626       * Close the connection with the DBMS.
 627       */
 628  	public function close()
 629      {
 630          $this->read_link = $this->write_link = $this->current_link = null;
 631      }
 632  
 633      /**
 634       * Returns the number of affected rows in a query.
 635       *
 636       * @return int The number of affected rows.
 637       */
 638  	public function affected_rows()
 639      {
 640          if ($this->lastResult === null) {
 641              return 0;
 642          }
 643  
 644          return $this->lastResult->rowCount();
 645      }
 646  
 647      /**
 648       * Return the number of fields.
 649       *
 650       * @param PDOStatement $query The query result to get the number of fields for.
 651       *
 652       * @return int|bool The number of fields, or false if the number of fields could not be retrieved.
 653       */
 654  	public function num_fields($query)
 655      {
 656          if (is_null($query) || !($query instanceof PDOStatement)) {
 657              return false;
 658          }
 659  
 660          return $query->columnCount();
 661      }
 662  
 663  	 public function shutdown_query($query, $name = '')
 664       {
 665           global $shutdown_queries;
 666  
 667           if($name) {
 668               $shutdown_queries[$name] = $query;
 669           } else {
 670               $shutdown_queries[] = $query;
 671           }
 672       }
 673  
 674  	 public function escape_string($string)
 675       {
 676           $string = $this->read_link->quote($string);
 677  
 678           // Remove ' from the beginning of the string and at the end of the string, because we already quote parameters
 679           $string = substr($string, 1);
 680           $string = substr($string, 0, -1);
 681  
 682           return $string;
 683       }
 684  
 685  	 public function free_result($query)
 686       {
 687            if (is_object($query) && $query instanceof PDOStatement) {
 688               return $query->closeCursor();
 689           }
 690  
 691            return false;
 692       }
 693  
 694  	 public function escape_string_like($string)
 695       {
 696           return $this->escape_string(str_replace(array('\\', '%', '_') , array('\\\\', '\\%' , '\\_') , $string));
 697       }
 698  
 699  	 public function get_version()
 700       {
 701           if ($this->version) {
 702               return $this->version;
 703           }
 704  
 705           $this->version = $this->read_link->getAttribute(PDO::ATTR_SERVER_VERSION);
 706  
 707           return $this->version;
 708       }
 709  
 710  	 public function set_table_prefix($prefix)
 711       {
 712           $this->table_prefix = $prefix;
 713       }
 714  
 715  	 public function get_execution_time()
 716       {
 717           return get_execution_time();
 718       }
 719   }


2005 - 2021 © MyBB.de | Alle Rechte vorbehalten! | Sponsor: netcup Cross-referenced by PHPXref