1<?php 2/** 3 * @package FrameworkOnFramework 4 * @subpackage database 5 * @copyright Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved. 6 * @license GNU General Public License version 2 or later; see LICENSE.txt 7 * @note This file has been modified by the Joomla! Project and no longer reflects the original work of its author. 8 * 9 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects 10 * instead of plain stdClass objects 11 */ 12 13// Protect from unauthorized access 14defined('FOF_INCLUDED') or die; 15 16/** 17 * MySQL database driver supporting PDO based connections 18 * 19 * @package Joomla.Platform 20 * @subpackage Database 21 * @see http://php.net/manual/en/ref.pdo-mysql.php 22 * @since 3.4 23 */ 24class FOFDatabaseDriverPdomysql extends FOFDatabaseDriverPdo 25{ 26 /** 27 * The name of the database driver. 28 * 29 * @var string 30 * @since 3.4 31 */ 32 public $name = 'pdomysql'; 33 34 /** 35 * The type of the database server family supported by this driver. 36 * 37 * @var string 38 * @since CMS 3.5.0 39 */ 40 public $serverType = 'mysql'; 41 42 /** 43 * The character(s) used to quote SQL statement names such as table names or field names, 44 * etc. The child classes should define this as necessary. If a single character string the 45 * same character is used for both sides of the quoted name, else the first character will be 46 * used for the opening quote and the second for the closing quote. 47 * 48 * @var string 49 * @since 3.4 50 */ 51 protected $nameQuote = '`'; 52 53 /** 54 * The null or zero representation of a timestamp for the database driver. This should be 55 * defined in child classes to hold the appropriate value for the engine. 56 * 57 * @var string 58 * @since 3.4 59 */ 60 protected $nullDate = '0000-00-00 00:00:00'; 61 62 /** 63 * The minimum supported database version. 64 * 65 * @var string 66 * @since 3.4 67 */ 68 protected static $dbMinimum = '5.0.4'; 69 70 /** 71 * Constructor. 72 * 73 * @param array $options Array of database options with keys: host, user, password, database, select. 74 * 75 * @since 3.4 76 */ 77 public function __construct($options) 78 { 79 /** 80 * Pre-populate the UTF-8 Multibyte compatibility flag. Unfortunately PDO won't report the server version 81 * unless we're connected to it and we cannot connect to it unless we know if it supports utf8mb4 which requires 82 * us knowing the server version. Between this chicken and egg issue we _assume_ it's supported and we'll just 83 * catch any problems at connection time. 84 */ 85 $this->utf8mb4 = true; 86 87 // Get some basic values from the options. 88 $options['driver'] = 'mysql'; 89 $options['charset'] = (isset($options['charset'])) ? $options['charset'] : 'utf8'; 90 91 if ($this->utf8mb4 && ($options['charset'] == 'utf8')) 92 { 93 $options['charset'] = 'utf8mb4'; 94 } 95 96 $this->charset = $options['charset']; 97 98 // Finalize initialisation. 99 parent::__construct($options); 100 } 101 102 /** 103 * Connects to the database if needed. 104 * 105 * @return void Returns void if the database connected successfully. 106 * 107 * @since 3.4 108 * @throws RuntimeException 109 */ 110 public function connect() 111 { 112 try 113 { 114 // Try to connect to MySQL 115 parent::connect(); 116 } 117 catch (\RuntimeException $e) 118 { 119 // If the connection failed but not because of the wrong character set bubble up the exception 120 if (!$this->utf8mb4 || ($this->options['charset'] != 'utf8mb4')) 121 { 122 throw $e; 123 } 124 125 /** 126 * If the connection failed and I was trying to use the utf8mb4 charset then it is likely that the server 127 * doesn't support utf8mb4 despite claiming otherwise. 128 * 129 * This happens on old MySQL server versions (less than 5.5.3) using the mysqlnd PHP driver. Since mysqlnd 130 * masks the server version and reports only its own we can not be sure if the server actually does support 131 * UTF-8 Multibyte (i.e. it's MySQL 5.5.3 or later). Since the utf8mb4 charset is undefined in this case we 132 * catch the error and determine that utf8mb4 is not supported! 133 */ 134 $this->utf8mb4 = false; 135 $this->options['charset'] = 'utf8'; 136 137 parent::connect(); 138 } 139 140 $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 141 $this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); 142 } 143 144 /** 145 * Test to see if the MySQL connector is available. 146 * 147 * @return boolean True on success, false otherwise. 148 * 149 * @since 3.4 150 */ 151 public static function isSupported() 152 { 153 return class_exists('PDO') && in_array('mysql', PDO::getAvailableDrivers()); 154 } 155 156 /** 157 * Drops a table from the database. 158 * 159 * @param string $tableName The name of the database table to drop. 160 * @param boolean $ifExists Optionally specify that the table must exist before it is dropped. 161 * 162 * @return FOFDatabaseDriverPdomysql Returns this object to support chaining. 163 * 164 * @since 3.4 165 * @throws RuntimeException 166 */ 167 public function dropTable($tableName, $ifExists = true) 168 { 169 $this->connect(); 170 171 $query = $this->getQuery(true); 172 173 $query->setQuery('DROP TABLE ' . ($ifExists ? 'IF EXISTS ' : '') . $this->quoteName($tableName)); 174 175 $this->setQuery($query); 176 177 $this->execute(); 178 179 return $this; 180 } 181 182 /** 183 * Select a database for use. 184 * 185 * @param string $database The name of the database to select for use. 186 * 187 * @return boolean True if the database was successfully selected. 188 * 189 * @since 3.4 190 * @throws RuntimeException 191 */ 192 public function select($database) 193 { 194 $this->connect(); 195 196 $this->setQuery('USE ' . $this->quoteName($database)); 197 198 $this->execute(); 199 200 return $this; 201 } 202 203 /** 204 * Method to get the database collation in use by sampling a text field of a table in the database. 205 * 206 * @return mixed The collation in use by the database (string) or boolean false if not supported. 207 * 208 * @since 3.4 209 * @throws RuntimeException 210 */ 211 public function getCollation() 212 { 213 $this->connect(); 214 215 // Attempt to get the database collation by accessing the server system variable. 216 $this->setQuery('SHOW VARIABLES LIKE "collation_database"'); 217 $result = $this->loadObject(); 218 219 if (property_exists($result, 'Value')) 220 { 221 return $result->Value; 222 } 223 else 224 { 225 return false; 226 } 227 } 228 229 /** 230 * Method to get the database connection collation, as reported by the driver. If the connector doesn't support 231 * reporting this value please return an empty string. 232 * 233 * @return string 234 */ 235 public function getConnectionCollation() 236 { 237 $this->connect(); 238 239 // Attempt to get the database collation by accessing the server system variable. 240 $this->setQuery('SHOW VARIABLES LIKE "collation_connection"'); 241 $result = $this->loadObject(); 242 243 if (property_exists($result, 'Value')) 244 { 245 return $result->Value; 246 } 247 else 248 { 249 return false; 250 } 251 } 252 253 /** 254 * Shows the table CREATE statement that creates the given tables. 255 * 256 * @param mixed $tables A table name or a list of table names. 257 * 258 * @return array A list of the create SQL for the tables. 259 * 260 * @since 3.4 261 * @throws RuntimeException 262 */ 263 public function getTableCreate($tables) 264 { 265 $this->connect(); 266 267 // Initialise variables. 268 $result = array(); 269 270 // Sanitize input to an array and iterate over the list. 271 settype($tables, 'array'); 272 273 foreach ($tables as $table) 274 { 275 $this->setQuery('SHOW CREATE TABLE ' . $this->quoteName($table)); 276 277 $row = $this->loadRow(); 278 279 // Populate the result array based on the create statements. 280 $result[$table] = $row[1]; 281 } 282 283 return $result; 284 } 285 286 /** 287 * Retrieves field information about a given table. 288 * 289 * @param string $table The name of the database table. 290 * @param boolean $typeOnly True to only return field types. 291 * 292 * @return array An array of fields for the database table. 293 * 294 * @since 3.4 295 * @throws RuntimeException 296 */ 297 public function getTableColumns($table, $typeOnly = true) 298 { 299 $this->connect(); 300 301 $result = array(); 302 303 // Set the query to get the table fields statement. 304 $this->setQuery('SHOW FULL COLUMNS FROM ' . $this->quoteName($table)); 305 306 $fields = $this->loadObjectList(); 307 308 // If we only want the type as the value add just that to the list. 309 if ($typeOnly) 310 { 311 foreach ($fields as $field) 312 { 313 $result[$field->Field] = preg_replace("/[(0-9)]/", '', $field->Type); 314 } 315 } 316 // If we want the whole field data object add that to the list. 317 else 318 { 319 foreach ($fields as $field) 320 { 321 $result[$field->Field] = $field; 322 } 323 } 324 325 return $result; 326 } 327 328 /** 329 * Get the details list of keys for a table. 330 * 331 * @param string $table The name of the table. 332 * 333 * @return array An array of the column specification for the table. 334 * 335 * @since 3.4 336 * @throws RuntimeException 337 */ 338 public function getTableKeys($table) 339 { 340 $this->connect(); 341 342 // Get the details columns information. 343 $this->setQuery('SHOW KEYS FROM ' . $this->quoteName($table)); 344 345 $keys = $this->loadObjectList(); 346 347 return $keys; 348 } 349 350 /** 351 * Method to get an array of all tables in the database. 352 * 353 * @return array An array of all the tables in the database. 354 * 355 * @since 3.4 356 * @throws RuntimeException 357 */ 358 public function getTableList() 359 { 360 $this->connect(); 361 362 // Set the query to get the tables statement. 363 $this->setQuery('SHOW TABLES'); 364 $tables = $this->loadColumn(); 365 366 return $tables; 367 } 368 369 /** 370 * Get the version of the database connector. 371 * 372 * @return string The database connector version. 373 * 374 * @since 3.4 375 */ 376 public function getVersion() 377 { 378 $this->connect(); 379 380 return $this->getOption(PDO::ATTR_SERVER_VERSION); 381 } 382 383 /** 384 * Locks a table in the database. 385 * 386 * @param string $table The name of the table to unlock. 387 * 388 * @return FOFDatabaseDriverPdomysql Returns this object to support chaining. 389 * 390 * @since 3.4 391 * @throws RuntimeException 392 */ 393 public function lockTable($table) 394 { 395 $this->setQuery('LOCK TABLES ' . $this->quoteName($table) . ' WRITE')->execute(); 396 397 return $this; 398 } 399 400 /** 401 * Renames a table in the database. 402 * 403 * @param string $oldTable The name of the table to be renamed 404 * @param string $newTable The new name for the table. 405 * @param string $backup Not used by MySQL. 406 * @param string $prefix Not used by MySQL. 407 * 408 * @return FOFDatabaseDriverPdomysql Returns this object to support chaining. 409 * 410 * @since 3.4 411 * @throws RuntimeException 412 */ 413 public function renameTable($oldTable, $newTable, $backup = null, $prefix = null) 414 { 415 $this->setQuery('RENAME TABLE ' . $this->quoteName($oldTable) . ' TO ' . $this->quoteName($newTable)); 416 417 $this->execute(); 418 419 return $this; 420 } 421 422 /** 423 * Method to escape a string for usage in an SQL statement. 424 * 425 * Oracle escaping reference: 426 * http://www.orafaq.com/wiki/SQL_FAQ#How_does_one_escape_special_characters_when_writing_SQL_queries.3F 427 * 428 * SQLite escaping notes: 429 * http://www.sqlite.org/faq.html#q14 430 * 431 * Method body is as implemented by the Zend Framework 432 * 433 * Note: Using query objects with bound variables is 434 * preferable to the below. 435 * 436 * @param string $text The string to be escaped. 437 * @param boolean $extra Unused optional parameter to provide extra escaping. 438 * 439 * @return string The escaped string. 440 * 441 * @since 3.4 442 */ 443 public function escape($text, $extra = false) 444 { 445 $this->connect(); 446 447 if (is_int($text) || is_float($text)) 448 { 449 return $text; 450 } 451 452 $result = substr($this->connection->quote($text), 1, -1); 453 454 if ($extra) 455 { 456 $result = addcslashes($result, '%_'); 457 } 458 459 return $result; 460 } 461 462 /** 463 * Unlocks tables in the database. 464 * 465 * @return FOFDatabaseDriverPdomysql Returns this object to support chaining. 466 * 467 * @since 3.4 468 * @throws RuntimeException 469 */ 470 public function unlockTables() 471 { 472 $this->setQuery('UNLOCK TABLES')->execute(); 473 474 return $this; 475 } 476 477 /** 478 * Method to commit a transaction. 479 * 480 * @param boolean $toSavepoint If true, commit to the last savepoint. 481 * 482 * @return void 483 * 484 * @since 3.4 485 * @throws RuntimeException 486 */ 487 public function transactionCommit($toSavepoint = false) 488 { 489 $this->connect(); 490 491 if (!$toSavepoint || $this->transactionDepth <= 1) 492 { 493 parent::transactionCommit($toSavepoint); 494 } 495 else 496 { 497 $this->transactionDepth--; 498 } 499 } 500 501 /** 502 * Method to roll back a transaction. 503 * 504 * @param boolean $toSavepoint If true, rollback to the last savepoint. 505 * 506 * @return void 507 * 508 * @since 3.4 509 * @throws RuntimeException 510 */ 511 public function transactionRollback($toSavepoint = false) 512 { 513 $this->connect(); 514 515 if (!$toSavepoint || $this->transactionDepth <= 1) 516 { 517 parent::transactionRollback($toSavepoint); 518 } 519 else 520 { 521 $savepoint = 'SP_' . ($this->transactionDepth - 1); 522 $this->setQuery('ROLLBACK TO SAVEPOINT ' . $this->quoteName($savepoint)); 523 524 if ($this->execute()) 525 { 526 $this->transactionDepth--; 527 } 528 } 529 } 530 531 /** 532 * Method to initialize a transaction. 533 * 534 * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created. 535 * 536 * @return void 537 * 538 * @since 3.4 539 * @throws RuntimeException 540 */ 541 public function transactionStart($asSavepoint = false) 542 { 543 $this->connect(); 544 545 if (!$asSavepoint || !$this->transactionDepth) 546 { 547 parent::transactionStart($asSavepoint); 548 } 549 else 550 { 551 $savepoint = 'SP_' . $this->transactionDepth; 552 $this->setQuery('SAVEPOINT ' . $this->quoteName($savepoint)); 553 554 if ($this->execute()) 555 { 556 $this->transactionDepth++; 557 } 558 } 559 } 560} 561