mirror of https://github.com/YOURLS/YOURLS
426 lines
9.9 KiB
PHP
426 lines
9.9 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Aura SQL wrapper for YOURLS that creates the allmighty YDB object.
|
|
*
|
|
* A fine example of a "class that knows too much" (see https://en.wikipedia.org/wiki/God_object)
|
|
*
|
|
* Note to plugin authors: you most likely SHOULD NOT use directly methods and properties of this class. Use instead
|
|
* function wrappers (eg don't use $ydb->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
|
|
$file = YOURLS_USERDIR . '/db_error.php';
|
|
if(file_exists($file)) {
|
|
if(yourls_include_file_sandbox( $file ) === true) {
|
|
die();
|
|
}
|
|
}
|
|
|
|
$message = yourls__( 'Incorrect DB config, or could not connect to DB' );
|
|
$message .= '<br/>' . 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
|
|
* @return void
|
|
*/
|
|
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
|
|
* @return void
|
|
*/
|
|
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
|
|
* @return void
|
|
*/
|
|
public function delete_option($name) {
|
|
unset($this->option[$name]);
|
|
}
|
|
|
|
|
|
// Infos (related to keyword) low level functions
|
|
|
|
/**
|
|
* @param string $keyword
|
|
* @param mixed $infos
|
|
* @return void
|
|
*/
|
|
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
|
|
* @return void
|
|
*/
|
|
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
|
|
* @return void
|
|
*/
|
|
public function set_plugins(array $plugins) {
|
|
$this->plugins = $plugins;
|
|
}
|
|
|
|
/**
|
|
* @param string $plugin plugin filename
|
|
* @return void
|
|
*/
|
|
public function add_plugin($plugin) {
|
|
$this->plugins[] = $plugin;
|
|
}
|
|
|
|
/**
|
|
* @param string $plugin plugin filename
|
|
* @return void
|
|
*/
|
|
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
|
|
* @return void
|
|
*/
|
|
public function set_plugin_pages(array $pages) {
|
|
$this->plugin_pages = $pages;
|
|
}
|
|
|
|
/**
|
|
* @param string $slug
|
|
* @param string $title
|
|
* @param callable $function
|
|
* @return void
|
|
*/
|
|
public function add_plugin_page( $slug, $title, $function ) {
|
|
$this->plugin_pages[ $slug ] = [
|
|
'slug' => $slug,
|
|
'title' => $title,
|
|
'function' => $function,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param string $slug
|
|
* @return void
|
|
*/
|
|
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;
|
|
}
|
|
|
|
}
|