option, or $ydb->set_option(), use yourls_*_options() functions instead). * * @since 1.7.3 */ namespace YOURLS\Database; use \Aura\Sql\ExtendedPdo; use \YOURLS\Database\Profiler; use \YOURLS\Database\Logger; use PDO; class YDB extends ExtendedPdo { /** * Debug mode, default false * @var bool */ protected $debug = false; /** * Page context (ie "infos", "bookmark", "plugins"...) * @var string */ protected $context = ''; /** * Information related to a short URL keyword (eg timestamp, long URL, ...) * * @var array * */ protected $infos = []; /** * Is YOURLS installed and ready to run? * @var bool */ protected $installed = false; /** * Options * @var array */ protected $option = []; /** * Plugin admin pages informations * @var array */ protected $plugin_pages = []; /** * Plugin informations * @var array */ protected $plugins = []; /** * Are we emulating prepare statements ? * @var bool */ protected $is_emulate_prepare; /** * @since 1.7.3 * @param string $dsn The data source name * @param string $user The username * @param string $pass The password * @param array $options Driver-specific options * @param array $attributes Attributes to set after a connection */ public function __construct($dsn, $user, $pass, $options, $attributes) { parent::__construct($dsn, $user, $pass, $options, $attributes); } /** * Init everything needed * * Everything we need to set up is done here in init(), not in the constructor, so even * when the connection fails (eg config error or DB dead), the constructor has worked * and we have a $ydb object properly instantiated (and for instance yourls_die() can * correctly die, even if using $ydb methods) * * @since 1.7.3 * @return void */ public function init() { $this->connect_to_DB(); $this->set_emulate_state(); $this->start_profiler(); } /** * Check if we emulate prepare statements, and set bool flag accordingly * * Check if current driver can PDO::getAttribute(PDO::ATTR_EMULATE_PREPARES) * Some combinations of PHP/MySQL don't support this function. See * https://travis-ci.org/YOURLS/YOURLS/jobs/271423782#L481 * * @since 1.7.3 * @return void */ public function set_emulate_state() { try { $this->is_emulate_prepare = $this->getAttribute(PDO::ATTR_EMULATE_PREPARES); } catch (\PDOException $e) { $this->is_emulate_prepare = false; } } /** * Get emulate status * * @since 1.7.3 * @return bool */ public function get_emulate_state() { return $this->is_emulate_prepare; } /** * Initiate real connection to DB server * * This is to check that the server is running and/or the config is OK * * @since 1.7.3 * @return void * @throws \PDOException */ public function connect_to_DB() { try { $this->connect(); } catch ( \Exception $e ) { $this->dead_or_error($e); } } /** * Die with an error message * * @since 1.7.3 * * @param \Exception $exception * * @return void */ public function dead_or_error(\Exception $exception) { // Use any /user/db_error.php file if( file_exists( YOURLS_USERDIR . '/db_error.php' ) ) { include_once( YOURLS_USERDIR . '/db_error.php' ); die(); } $message = yourls__( 'Incorrect DB config, or could not connect to DB' ); $message .= '
' . get_class($exception) .': ' . $exception->getMessage(); yourls_die( yourls__( $message ), yourls__( 'Fatal error' ), 503 ); die(); } /** * Start a Message Logger * * @since 1.7.3 * @see \Aura\Sql\Profiler\Profiler * @see \Aura\Sql\Profiler\MemoryLogger * @return void */ public function start_profiler() { // Instantiate a custom logger and make it the profiler $yourls_logger = new Logger(); $profiler = new Profiler($yourls_logger); $this->setProfiler($profiler); /* By default, make "query" the log level. This way, each internal logging triggered * by Aura SQL will be a "query", and logging triggered by yourls_debug() will be * a "message". See includes/functions-debug.php:yourls_debug() */ $profiler->setLoglevel('query'); } /** * @param string $context */ public function set_html_context($context) { $this->context = $context; } /** * @return string */ public function get_html_context() { return $this->context; } // Options low level functions, see \YOURLS\Database\Options /** * @param string $name * @param mixed $value */ public function set_option($name, $value) { $this->option[$name] = $value; } /** * @param string $name * @return bool */ public function has_option($name) { return array_key_exists($name, $this->option); } /** * @param string $name * @return string */ public function get_option($name) { return $this->option[$name]; } /** * @param string $name */ public function delete_option($name) { unset($this->option[$name]); } // Infos (related to keyword) low level functions /** * @param string $keyword * @param mixed $infos */ public function set_infos($keyword, $infos) { $this->infos[$keyword] = $infos; } /** * @param string $keyword * @return bool */ public function has_infos($keyword) { return array_key_exists($keyword, $this->infos); } /** * @param string $keyword * @return array */ public function get_infos($keyword) { return $this->infos[$keyword]; } /** * @param string $keyword */ public function delete_infos($keyword) { unset($this->infos[$keyword]); } /** * @todo: infos & options are working the same way here. Abstract this. */ // Plugin low level functions, see functions-plugins.php /** * @return array */ public function get_plugins() { return $this->plugins; } /** * @param array $plugins */ public function set_plugins(array $plugins) { $this->plugins = $plugins; } /** * @param string $plugin plugin filename */ public function add_plugin($plugin) { $this->plugins[] = $plugin; } /** * @param string $plugin plugin filename */ public function remove_plugin($plugin) { unset($this->plugins[$plugin]); } // Plugin Pages low level functions, see functions-plugins.php /** * @return array */ public function get_plugin_pages() { return is_array( $this->plugin_pages ) ? $this->plugin_pages : []; } /** * @param array $pages */ public function set_plugin_pages(array $pages) { $this->plugin_pages = $pages; } /** * @param string $slug * @param string $title * @param callable $function */ public function add_plugin_page( $slug, $title, $function ) { $this->plugin_pages[ $slug ] = [ 'slug' => $slug, 'title' => $title, 'function' => $function, ]; } /** * @param string $slug */ public function remove_plugin_page( $slug ) { unset( $this->plugin_pages[ $slug ] ); } /** * Return count of SQL queries performed * * @since 1.7.3 * @return int */ public function get_num_queries() { return count( (array) $this->get_queries() ); } /** * Return SQL queries performed * * @since 1.7.3 * @return array */ public function get_queries() { $queries = $this->getProfiler()->getLogger()->getMessages(); // Only keep messages that start with "SQL " $queries = array_filter($queries, function($query) {return substr( $query, 0, 4 ) === "SQL ";}); return $queries; } /** * Set YOURLS installed state * * @since 1.7.3 * @param bool $bool * @return void */ public function set_installed($bool) { $this->installed = $bool; } /** * Get YOURLS installed state * * @since 1.7.3 * @return bool */ public function is_installed() { return $this->installed; } /** * Return standardized DB version * * The regex removes everything that's not a number at the start of the string, or remove anything that's not a number and what * follows after that. * 'omgmysql-5.5-ubuntu-4.20' => '5.5' * 'mysql5.5-ubuntu-4.20' => '5.5' * '5.5-ubuntu-4.20' => '5.5' * '5.5-beta2' => '5.5' * '5.5' => '5.5' * * @since 1.7.3 * @return string */ public function mysql_version() { $version = $this->pdo->getAttribute(PDO::ATTR_SERVER_VERSION); return $version; } }