From 90a957ad26f52e26c3979464c5ac15b1fd0fcc28 Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sat, 21 Jul 2012 17:43:43 +0200 Subject: [ticket/11015] Make DBAL classes autoloadable PHPBB3-11015 This allows us to just create the object without having to include the driver first. However, it also means that users must specify the full class name in config.php --- phpBB/includes/config/db.php | 2 +- phpBB/includes/db/dbal.php | 1049 ------------------------------ phpBB/includes/db/driver/driver.php | 1044 +++++++++++++++++++++++++++++ phpBB/includes/db/driver/firebird.php | 538 +++++++++++++++ phpBB/includes/db/driver/mssql.php | 454 +++++++++++++ phpBB/includes/db/driver/mssql_odbc.php | 395 +++++++++++ phpBB/includes/db/driver/mssqlnative.php | 642 ++++++++++++++++++ phpBB/includes/db/driver/mysql.php | 565 ++++++++++++++++ phpBB/includes/db/driver/mysqli.php | 566 ++++++++++++++++ phpBB/includes/db/driver/oracle.php | 766 ++++++++++++++++++++++ phpBB/includes/db/driver/postgres.php | 491 ++++++++++++++ phpBB/includes/db/driver/sqlite.php | 334 ++++++++++ phpBB/includes/db/firebird.php | 540 --------------- phpBB/includes/db/mssql.php | 456 ------------- phpBB/includes/db/mssql_odbc.php | 397 ----------- phpBB/includes/db/mssqlnative.php | 644 ------------------ phpBB/includes/db/mysql.php | 567 ---------------- phpBB/includes/db/mysqli.php | 568 ---------------- phpBB/includes/db/oracle.php | 768 ---------------------- phpBB/includes/db/postgres.php | 498 -------------- phpBB/includes/db/sqlite.php | 336 ---------- phpBB/includes/extension/manager.php | 4 +- phpBB/includes/functions_install.php | 59 +- 23 files changed, 5824 insertions(+), 5859 deletions(-) delete mode 100644 phpBB/includes/db/dbal.php create mode 100644 phpBB/includes/db/driver/driver.php create mode 100644 phpBB/includes/db/driver/firebird.php create mode 100644 phpBB/includes/db/driver/mssql.php create mode 100644 phpBB/includes/db/driver/mssql_odbc.php create mode 100644 phpBB/includes/db/driver/mssqlnative.php create mode 100644 phpBB/includes/db/driver/mysql.php create mode 100644 phpBB/includes/db/driver/mysqli.php create mode 100644 phpBB/includes/db/driver/oracle.php create mode 100644 phpBB/includes/db/driver/postgres.php create mode 100644 phpBB/includes/db/driver/sqlite.php delete mode 100644 phpBB/includes/db/firebird.php delete mode 100644 phpBB/includes/db/mssql.php delete mode 100644 phpBB/includes/db/mssql_odbc.php delete mode 100644 phpBB/includes/db/mssqlnative.php delete mode 100644 phpBB/includes/db/mysql.php delete mode 100644 phpBB/includes/db/mysqli.php delete mode 100644 phpBB/includes/db/oracle.php delete mode 100644 phpBB/includes/db/postgres.php delete mode 100644 phpBB/includes/db/sqlite.php (limited to 'phpBB/includes') diff --git a/phpBB/includes/config/db.php b/phpBB/includes/config/db.php index 993a764a7f..4293498d97 100644 --- a/phpBB/includes/config/db.php +++ b/phpBB/includes/config/db.php @@ -46,7 +46,7 @@ class phpbb_config_db extends phpbb_config * @param phpbb_cache_driver_interface $cache Cache instance * @param string $table Configuration table name */ - public function __construct(dbal $db, phpbb_cache_driver_interface $cache, $table) + public function __construct(phpbb_db_driver $db, phpbb_cache_driver_interface $cache, $table) { $this->db = $db; $this->cache = $cache; diff --git a/phpBB/includes/db/dbal.php b/phpBB/includes/db/dbal.php deleted file mode 100644 index 159703d3be..0000000000 --- a/phpBB/includes/db/dbal.php +++ /dev/null @@ -1,1049 +0,0 @@ -num_queries = array( - 'cached' => 0, - 'normal' => 0, - 'total' => 0, - ); - - // Fill default sql layer based on the class being called. - // This can be changed by the specified layer itself later if needed. - $this->sql_layer = substr(get_class($this), 5); - - // Do not change this please! This variable is used to easy the use of it - and is hardcoded. - $this->any_char = chr(0) . '%'; - $this->one_char = chr(0) . '_'; - } - - /** - * return on error or display error message - */ - function sql_return_on_error($fail = false) - { - $this->sql_error_triggered = false; - $this->sql_error_sql = ''; - - $this->return_on_error = $fail; - } - - /** - * Return number of sql queries and cached sql queries used - */ - function sql_num_queries($cached = false) - { - return ($cached) ? $this->num_queries['cached'] : $this->num_queries['normal']; - } - - /** - * Add to query count - */ - function sql_add_num_queries($cached = false) - { - $this->num_queries['cached'] += ($cached !== false) ? 1 : 0; - $this->num_queries['normal'] += ($cached !== false) ? 0 : 1; - $this->num_queries['total'] += 1; - } - - /** - * DBAL garbage collection, close sql connection - */ - function sql_close() - { - if (!$this->db_connect_id) - { - return false; - } - - if ($this->transaction) - { - do - { - $this->sql_transaction('commit'); - } - while ($this->transaction); - } - - foreach ($this->open_queries as $query_id) - { - $this->sql_freeresult($query_id); - } - - // Connection closed correctly. Set db_connect_id to false to prevent errors - if ($result = $this->_sql_close()) - { - $this->db_connect_id = false; - } - - return $result; - } - - /** - * Build LIMIT query - * Doing some validation here. - */ - function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) - { - if (empty($query)) - { - return false; - } - - // Never use a negative total or offset - $total = ($total < 0) ? 0 : $total; - $offset = ($offset < 0) ? 0 : $offset; - - return $this->_sql_query_limit($query, $total, $offset, $cache_ttl); - } - - /** - * Fetch all rows - */ - function sql_fetchrowset($query_id = false) - { - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($query_id !== false) - { - $result = array(); - while ($row = $this->sql_fetchrow($query_id)) - { - $result[] = $row; - } - - return $result; - } - - return false; - } - - /** - * Seek to given row number - * rownum is zero-based - */ - function sql_rowseek($rownum, &$query_id) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_rowseek($rownum, $query_id); - } - - if ($query_id === false) - { - return false; - } - - $this->sql_freeresult($query_id); - $query_id = $this->sql_query($this->last_query_text); - - if ($query_id === false) - { - return false; - } - - // We do not fetch the row for rownum == 0 because then the next resultset would be the second row - for ($i = 0; $i < $rownum; $i++) - { - if (!$this->sql_fetchrow($query_id)) - { - return false; - } - } - - return true; - } - - /** - * Fetch field - * if rownum is false, the current row is used, else it is pointing to the row (zero-based) - */ - function sql_fetchfield($field, $rownum = false, $query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($query_id !== false) - { - if ($rownum !== false) - { - $this->sql_rowseek($rownum, $query_id); - } - - if (!is_object($query_id) && isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_fetchfield($query_id, $field); - } - - $row = $this->sql_fetchrow($query_id); - return (isset($row[$field])) ? $row[$field] : false; - } - - return false; - } - - /** - * Correctly adjust LIKE expression for special characters - * Some DBMS are handling them in a different way - * - * @param string $expression The expression to use. Every wildcard is escaped, except $this->any_char and $this->one_char - * @return string LIKE expression including the keyword! - */ - function sql_like_expression($expression) - { - $expression = utf8_str_replace(array('_', '%'), array("\_", "\%"), $expression); - $expression = utf8_str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression); - - return $this->_sql_like_expression('LIKE \'' . $this->sql_escape($expression) . '\''); - } - - /** - * Build a case expression - * - * Note: The two statements action_true and action_false must have the same data type (int, vchar, ...) in the database! - * - * @param string $condition The condition which must be true, to use action_true rather then action_else - * @param string $action_true SQL expression that is used, if the condition is true - * @param string $action_else SQL expression that is used, if the condition is false, optional - * @return string CASE expression including the condition and statements - */ - public function sql_case($condition, $action_true, $action_false = false) - { - $sql_case = 'CASE WHEN ' . $condition; - $sql_case .= ' THEN ' . $action_true; - $sql_case .= ($action_false !== false) ? ' ELSE ' . $action_false : ''; - $sql_case .= ' END'; - return $sql_case; - } - - /** - * Build a concatenated expression - * - * @param string $expr1 Base SQL expression where we append the second one - * @param string $expr2 SQL expression that is appended to the first expression - * @return string Concatenated string - */ - public function sql_concatenate($expr1, $expr2) - { - return $expr1 . ' || ' . $expr2; - } - - /** - * Returns whether results of a query need to be buffered to run a transaction while iterating over them. - * - * @return bool Whether buffering is required. - */ - function sql_buffer_nested_transactions() - { - return false; - } - - /** - * SQL Transaction - * @access private - */ - function sql_transaction($status = 'begin') - { - switch ($status) - { - case 'begin': - // If we are within a transaction we will not open another one, but enclose the current one to not loose data (prevening auto commit) - if ($this->transaction) - { - $this->transactions++; - return true; - } - - $result = $this->_sql_transaction('begin'); - - if (!$result) - { - $this->sql_error(); - } - - $this->transaction = true; - break; - - case 'commit': - // If there was a previously opened transaction we do not commit yet... but count back the number of inner transactions - if ($this->transaction && $this->transactions) - { - $this->transactions--; - return true; - } - - // Check if there is a transaction (no transaction can happen if there was an error, with a combined rollback and error returning enabled) - // This implies we have transaction always set for autocommit db's - if (!$this->transaction) - { - return false; - } - - $result = $this->_sql_transaction('commit'); - - if (!$result) - { - $this->sql_error(); - } - - $this->transaction = false; - $this->transactions = 0; - break; - - case 'rollback': - $result = $this->_sql_transaction('rollback'); - $this->transaction = false; - $this->transactions = 0; - break; - - default: - $result = $this->_sql_transaction($status); - break; - } - - return $result; - } - - /** - * Build sql statement from array for insert/update/select statements - * - * Idea for this from Ikonboard - * Possible query values: INSERT, INSERT_SELECT, UPDATE, SELECT - * - */ - function sql_build_array($query, $assoc_ary = false) - { - if (!is_array($assoc_ary)) - { - return false; - } - - $fields = $values = array(); - - if ($query == 'INSERT' || $query == 'INSERT_SELECT') - { - foreach ($assoc_ary as $key => $var) - { - $fields[] = $key; - - if (is_array($var) && is_string($var[0])) - { - // This is used for INSERT_SELECT(s) - $values[] = $var[0]; - } - else - { - $values[] = $this->_sql_validate_value($var); - } - } - - $query = ($query == 'INSERT') ? ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')' : ' (' . implode(', ', $fields) . ') SELECT ' . implode(', ', $values) . ' '; - } - else if ($query == 'MULTI_INSERT') - { - trigger_error('The MULTI_INSERT query value is no longer supported. Please use sql_multi_insert() instead.', E_USER_ERROR); - } - else if ($query == 'UPDATE' || $query == 'SELECT') - { - $values = array(); - foreach ($assoc_ary as $key => $var) - { - $values[] = "$key = " . $this->_sql_validate_value($var); - } - $query = implode(($query == 'UPDATE') ? ', ' : ' AND ', $values); - } - - return $query; - } - - /** - * Build IN or NOT IN sql comparison string, uses <> or = on single element - * arrays to improve comparison speed - * - * @access public - * @param string $field name of the sql column that shall be compared - * @param array $array array of values that are allowed (IN) or not allowed (NOT IN) - * @param bool $negate true for NOT IN (), false for IN () (default) - * @param bool $allow_empty_set If true, allow $array to be empty, this function will return 1=1 or 1=0 then. Default to false. - */ - function sql_in_set($field, $array, $negate = false, $allow_empty_set = false) - { - if (!sizeof($array)) - { - if (!$allow_empty_set) - { - // Print the backtrace to help identifying the location of the problematic code - $this->sql_error('No values specified for SQL IN comparison'); - } - else - { - // NOT IN () actually means everything so use a tautology - if ($negate) - { - return '1=1'; - } - // IN () actually means nothing so use a contradiction - else - { - return '1=0'; - } - } - } - - if (!is_array($array)) - { - $array = array($array); - } - - if (sizeof($array) == 1) - { - @reset($array); - $var = current($array); - - return $field . ($negate ? ' <> ' : ' = ') . $this->_sql_validate_value($var); - } - else - { - return $field . ($negate ? ' NOT IN ' : ' IN ') . '(' . implode(', ', array_map(array($this, '_sql_validate_value'), $array)) . ')'; - } - } - - /** - * Run binary AND operator on DB column. - * Results in sql statement: "{$column_name} & (1 << {$bit}) {$compare}" - * - * @param string $column_name The column name to use - * @param int $bit The value to use for the AND operator, will be converted to (1 << $bit). Is used by options, using the number schema... 0, 1, 2...29 - * @param string $compare Any custom SQL code after the check (for example "= 0") - */ - function sql_bit_and($column_name, $bit, $compare = '') - { - if (method_exists($this, '_sql_bit_and')) - { - return $this->_sql_bit_and($column_name, $bit, $compare); - } - - return $column_name . ' & ' . (1 << $bit) . (($compare) ? ' ' . $compare : ''); - } - - /** - * Run binary OR operator on DB column. - * Results in sql statement: "{$column_name} | (1 << {$bit}) {$compare}" - * - * @param string $column_name The column name to use - * @param int $bit The value to use for the OR operator, will be converted to (1 << $bit). Is used by options, using the number schema... 0, 1, 2...29 - * @param string $compare Any custom SQL code after the check (for example "= 0") - */ - function sql_bit_or($column_name, $bit, $compare = '') - { - if (method_exists($this, '_sql_bit_or')) - { - return $this->_sql_bit_or($column_name, $bit, $compare); - } - - return $column_name . ' | ' . (1 << $bit) . (($compare) ? ' ' . $compare : ''); - } - - /** - * Returns SQL string to cast a string expression to an int. - * - * @param string $expression An expression evaluating to string - * @return string Expression returning an int - */ - function cast_expr_to_bigint($expression) - { - return $expression; - } - - /** - * Returns SQL string to cast an integer expression to a string. - * - * @param string $expression An expression evaluating to int - * @return string Expression returning a string - */ - function cast_expr_to_string($expression) - { - return $expression; - } - - /** - * Run LOWER() on DB column of type text (i.e. neither varchar nor char). - * - * @param string $column_name The column name to use - * - * @return string A SQL statement like "LOWER($column_name)" - */ - function sql_lower_text($column_name) - { - return "LOWER($column_name)"; - } - - /** - * Run more than one insert statement. - * - * @param string $table table name to run the statements on - * @param array &$sql_ary multi-dimensional array holding the statement data. - * - * @return bool false if no statements were executed. - * @access public - */ - function sql_multi_insert($table, &$sql_ary) - { - if (!sizeof($sql_ary)) - { - return false; - } - - if ($this->multi_insert) - { - $ary = array(); - foreach ($sql_ary as $id => $_sql_ary) - { - // If by accident the sql array is only one-dimensional we build a normal insert statement - if (!is_array($_sql_ary)) - { - return $this->sql_query('INSERT INTO ' . $table . ' ' . $this->sql_build_array('INSERT', $sql_ary)); - } - - $values = array(); - foreach ($_sql_ary as $key => $var) - { - $values[] = $this->_sql_validate_value($var); - } - $ary[] = '(' . implode(', ', $values) . ')'; - } - - return $this->sql_query('INSERT INTO ' . $table . ' ' . ' (' . implode(', ', array_keys($sql_ary[0])) . ') VALUES ' . implode(', ', $ary)); - } - else - { - foreach ($sql_ary as $ary) - { - if (!is_array($ary)) - { - return false; - } - - $result = $this->sql_query('INSERT INTO ' . $table . ' ' . $this->sql_build_array('INSERT', $ary)); - - if (!$result) - { - return false; - } - } - } - - return true; - } - - /** - * Function for validating values - * @access private - */ - function _sql_validate_value($var) - { - if (is_null($var)) - { - return 'NULL'; - } - else if (is_string($var)) - { - return "'" . $this->sql_escape($var) . "'"; - } - else - { - return (is_bool($var)) ? intval($var) : $var; - } - } - - /** - * Build sql statement from array for select and select distinct statements - * - * Possible query values: SELECT, SELECT_DISTINCT - */ - function sql_build_query($query, $array) - { - $sql = ''; - switch ($query) - { - case 'SELECT': - case 'SELECT_DISTINCT'; - - $sql = str_replace('_', ' ', $query) . ' ' . $array['SELECT'] . ' FROM '; - - // Build table array. We also build an alias array for later checks. - $table_array = $aliases = array(); - $used_multi_alias = false; - - foreach ($array['FROM'] as $table_name => $alias) - { - if (is_array($alias)) - { - $used_multi_alias = true; - - foreach ($alias as $multi_alias) - { - $table_array[] = $table_name . ' ' . $multi_alias; - $aliases[] = $multi_alias; - } - } - else - { - $table_array[] = $table_name . ' ' . $alias; - $aliases[] = $alias; - } - } - - // We run the following code to determine if we need to re-order the table array. ;) - // The reason for this is that for multi-aliased tables (two equal tables) in the FROM statement the last table need to match the first comparison. - // DBMS who rely on this: Oracle, PostgreSQL and MSSQL. For all other DBMS it makes absolutely no difference in which order the table is. - if (!empty($array['LEFT_JOIN']) && sizeof($array['FROM']) > 1 && $used_multi_alias !== false) - { - // Take first LEFT JOIN - $join = current($array['LEFT_JOIN']); - - // Determine the table used there (even if there are more than one used, we only want to have one - preg_match('/(' . implode('|', $aliases) . ')\.[^\s]+/U', str_replace(array('(', ')', 'AND', 'OR', ' '), '', $join['ON']), $matches); - - // If there is a first join match, we need to make sure the table order is correct - if (!empty($matches[1])) - { - $first_join_match = trim($matches[1]); - $table_array = $last = array(); - - foreach ($array['FROM'] as $table_name => $alias) - { - if (is_array($alias)) - { - foreach ($alias as $multi_alias) - { - ($multi_alias === $first_join_match) ? $last[] = $table_name . ' ' . $multi_alias : $table_array[] = $table_name . ' ' . $multi_alias; - } - } - else - { - ($alias === $first_join_match) ? $last[] = $table_name . ' ' . $alias : $table_array[] = $table_name . ' ' . $alias; - } - } - - $table_array = array_merge($table_array, $last); - } - } - - $sql .= $this->_sql_custom_build('FROM', implode(' CROSS JOIN ', $table_array)); - - if (!empty($array['LEFT_JOIN'])) - { - foreach ($array['LEFT_JOIN'] as $join) - { - $sql .= ' LEFT JOIN ' . key($join['FROM']) . ' ' . current($join['FROM']) . ' ON (' . $join['ON'] . ')'; - } - } - - if (!empty($array['WHERE'])) - { - $sql .= ' WHERE ' . $this->_sql_custom_build('WHERE', $array['WHERE']); - } - - if (!empty($array['GROUP_BY'])) - { - $sql .= ' GROUP BY ' . $array['GROUP_BY']; - } - - if (!empty($array['ORDER_BY'])) - { - $sql .= ' ORDER BY ' . $array['ORDER_BY']; - } - - break; - } - - return $sql; - } - - /** - * display sql error page - */ - function sql_error($sql = '') - { - global $auth, $user, $config; - - // Set var to retrieve errored status - $this->sql_error_triggered = true; - $this->sql_error_sql = $sql; - - $this->sql_error_returned = $this->_sql_error(); - - if (!$this->return_on_error) - { - $message = 'SQL ERROR [ ' . $this->sql_layer . ' ]

' . $this->sql_error_returned['message'] . ' [' . $this->sql_error_returned['code'] . ']'; - - // Show complete SQL error and path to administrators only - // Additionally show complete error on installation or if extended debug mode is enabled - // The DEBUG_EXTRA constant is for development only! - if ((isset($auth) && $auth->acl_get('a_')) || defined('IN_INSTALL') || defined('DEBUG_EXTRA')) - { - $message .= ($sql) ? '

SQL

' . htmlspecialchars($sql) : ''; - } - else - { - // If error occurs in initiating the session we need to use a pre-defined language string - // This could happen if the connection could not be established for example (then we are not able to grab the default language) - if (!isset($user->lang['SQL_ERROR_OCCURRED'])) - { - $message .= '

An sql error occurred while fetching this page. Please contact an administrator if this problem persists.'; - } - else - { - if (!empty($config['board_contact'])) - { - $message .= '

' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '', ''); - } - else - { - $message .= '

' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '', ''); - } - } - } - - if ($this->transaction) - { - $this->sql_transaction('rollback'); - } - - if (strlen($message) > 1024) - { - // We need to define $msg_long_text here to circumvent text stripping. - global $msg_long_text; - $msg_long_text = $message; - - trigger_error(false, E_USER_ERROR); - } - - trigger_error($message, E_USER_ERROR); - } - - if ($this->transaction) - { - $this->sql_transaction('rollback'); - } - - return $this->sql_error_returned; - } - - /** - * Explain queries - */ - function sql_report($mode, $query = '') - { - global $cache, $starttime, $phpbb_root_path, $user; - global $request; - - if (is_object($request) && !$request->variable('explain', false)) - { - return false; - } - - if (!$query && $this->query_hold != '') - { - $query = $this->query_hold; - } - - switch ($mode) - { - case 'display': - if (!empty($cache)) - { - $cache->unload(); - } - $this->sql_close(); - - $mtime = explode(' ', microtime()); - $totaltime = $mtime[0] + $mtime[1] - $starttime; - - echo ' - - - - SQL Report - - - -
- -
-
-
- -
-

SQL Report

-
-

Page generated in ' . round($totaltime, 4) . " seconds with {$this->num_queries['normal']} queries" . (($this->num_queries['cached']) ? " + {$this->num_queries['cached']} " . (($this->num_queries['cached'] == 1) ? 'query' : 'queries') . ' returning data from cache' : '') . '

- -

Time spent on ' . $this->sql_layer . ' queries: ' . round($this->sql_time, 5) . 's | Time spent on PHP: ' . round($totaltime - $this->sql_time, 5) . 's

- -

- ' . $this->sql_report . ' -
- -
-
-
- -
- - '; - - exit_handler(); - - break; - - case 'stop': - $endtime = explode(' ', microtime()); - $endtime = $endtime[0] + $endtime[1]; - - $this->sql_report .= ' - - - - - - - - - - - - -
Query #' . $this->num_queries['total'] . '
- - ' . $this->html_hold . ' - -

- '; - - if ($this->query_result) - { - if (preg_match('/^(UPDATE|DELETE|REPLACE)/', $query)) - { - $this->sql_report .= 'Affected rows: ' . $this->sql_affectedrows($this->query_result) . ' | '; - } - $this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed: ' . sprintf('%.5f', $endtime - $this->curtime) . 's'; - } - else - { - $error = $this->sql_error(); - $this->sql_report .= 'FAILED - ' . $this->sql_layer . ' Error ' . $error['code'] . ': ' . htmlspecialchars($error['message']); - } - - $this->sql_report .= '



'; - - $this->sql_time += $endtime - $this->curtime; - break; - - case 'start': - $this->query_hold = $query; - $this->html_hold = ''; - - $this->_sql_report($mode, $query); - - $this->curtime = explode(' ', microtime()); - $this->curtime = $this->curtime[0] + $this->curtime[1]; - - break; - - case 'add_select_row': - - $html_table = func_get_arg(2); - $row = func_get_arg(3); - - if (!$html_table && sizeof($row)) - { - $html_table = true; - $this->html_hold .= ''; - - foreach (array_keys($row) as $val) - { - $this->html_hold .= ''; - } - $this->html_hold .= ''; - } - $this->html_hold .= ''; - - $class = 'row1'; - foreach (array_values($row) as $val) - { - $class = ($class == 'row1') ? 'row2' : 'row1'; - $this->html_hold .= ''; - } - $this->html_hold .= ''; - - return $html_table; - - break; - - case 'fromcache': - - $this->_sql_report($mode, $query); - - break; - - case 'record_fromcache': - - $endtime = func_get_arg(2); - $splittime = func_get_arg(3); - - $time_cache = $endtime - $this->curtime; - $time_db = $splittime - $endtime; - $color = ($time_db > $time_cache) ? 'green' : 'red'; - - $this->sql_report .= '
' . (($val) ? ucwords(str_replace('_', ' ', $val)) : ' ') . '
' . (($val) ? $val : ' ') . '
'; - $this->sql_report .= '
Query results obtained from the cache
'; - $this->sql_report .= '

'; - $this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed [cache]: ' . sprintf('%.5f', ($time_cache)) . 's | Elapsed [db]: ' . sprintf('%.5f', $time_db) . 's



'; - - // Pad the start time to not interfere with page timing - $starttime += $time_db; - - break; - - default: - - $this->_sql_report($mode, $query); - - break; - } - - return true; - } - - /** - * Gets the estimated number of rows in a specified table. - * - * @param string $table_name Table name - * - * @return string Number of rows in $table_name. - * Prefixed with ~ if estimated (otherwise exact). - * - * @access public - */ - function get_estimated_row_count($table_name) - { - return $this->get_row_count($table_name); - } - - /** - * Gets the exact number of rows in a specified table. - * - * @param string $table_name Table name - * - * @return string Exact number of rows in $table_name. - * - * @access public - */ - function get_row_count($table_name) - { - $sql = 'SELECT COUNT(*) AS rows_total - FROM ' . $this->sql_escape($table_name); - $result = $this->sql_query($sql); - $rows_total = $this->sql_fetchfield('rows_total'); - $this->sql_freeresult($result); - - return $rows_total; - } -} - -/** -* This variable holds the class name to use later -*/ -$sql_db = (!empty($dbms)) ? 'dbal_' . basename($dbms) : 'dbal'; diff --git a/phpBB/includes/db/driver/driver.php b/phpBB/includes/db/driver/driver.php new file mode 100644 index 0000000000..39211a5af4 --- /dev/null +++ b/phpBB/includes/db/driver/driver.php @@ -0,0 +1,1044 @@ +num_queries = array( + 'cached' => 0, + 'normal' => 0, + 'total' => 0, + ); + + // Fill default sql layer based on the class being called. + // This can be changed by the specified layer itself later if needed. + $this->sql_layer = substr(get_class($this), 5); + + // Do not change this please! This variable is used to easy the use of it - and is hardcoded. + $this->any_char = chr(0) . '%'; + $this->one_char = chr(0) . '_'; + } + + /** + * return on error or display error message + */ + function sql_return_on_error($fail = false) + { + $this->sql_error_triggered = false; + $this->sql_error_sql = ''; + + $this->return_on_error = $fail; + } + + /** + * Return number of sql queries and cached sql queries used + */ + function sql_num_queries($cached = false) + { + return ($cached) ? $this->num_queries['cached'] : $this->num_queries['normal']; + } + + /** + * Add to query count + */ + function sql_add_num_queries($cached = false) + { + $this->num_queries['cached'] += ($cached !== false) ? 1 : 0; + $this->num_queries['normal'] += ($cached !== false) ? 0 : 1; + $this->num_queries['total'] += 1; + } + + /** + * DBAL garbage collection, close sql connection + */ + function sql_close() + { + if (!$this->db_connect_id) + { + return false; + } + + if ($this->transaction) + { + do + { + $this->sql_transaction('commit'); + } + while ($this->transaction); + } + + foreach ($this->open_queries as $query_id) + { + $this->sql_freeresult($query_id); + } + + // Connection closed correctly. Set db_connect_id to false to prevent errors + if ($result = $this->_sql_close()) + { + $this->db_connect_id = false; + } + + return $result; + } + + /** + * Build LIMIT query + * Doing some validation here. + */ + function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + if (empty($query)) + { + return false; + } + + // Never use a negative total or offset + $total = ($total < 0) ? 0 : $total; + $offset = ($offset < 0) ? 0 : $offset; + + return $this->_sql_query_limit($query, $total, $offset, $cache_ttl); + } + + /** + * Fetch all rows + */ + function sql_fetchrowset($query_id = false) + { + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($query_id !== false) + { + $result = array(); + while ($row = $this->sql_fetchrow($query_id)) + { + $result[] = $row; + } + + return $result; + } + + return false; + } + + /** + * Seek to given row number + * rownum is zero-based + */ + function sql_rowseek($rownum, &$query_id) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_rowseek($rownum, $query_id); + } + + if ($query_id === false) + { + return false; + } + + $this->sql_freeresult($query_id); + $query_id = $this->sql_query($this->last_query_text); + + if ($query_id === false) + { + return false; + } + + // We do not fetch the row for rownum == 0 because then the next resultset would be the second row + for ($i = 0; $i < $rownum; $i++) + { + if (!$this->sql_fetchrow($query_id)) + { + return false; + } + } + + return true; + } + + /** + * Fetch field + * if rownum is false, the current row is used, else it is pointing to the row (zero-based) + */ + function sql_fetchfield($field, $rownum = false, $query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($query_id !== false) + { + if ($rownum !== false) + { + $this->sql_rowseek($rownum, $query_id); + } + + if (!is_object($query_id) && isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_fetchfield($query_id, $field); + } + + $row = $this->sql_fetchrow($query_id); + return (isset($row[$field])) ? $row[$field] : false; + } + + return false; + } + + /** + * Correctly adjust LIKE expression for special characters + * Some DBMS are handling them in a different way + * + * @param string $expression The expression to use. Every wildcard is escaped, except $this->any_char and $this->one_char + * @return string LIKE expression including the keyword! + */ + function sql_like_expression($expression) + { + $expression = utf8_str_replace(array('_', '%'), array("\_", "\%"), $expression); + $expression = utf8_str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression); + + return $this->_sql_like_expression('LIKE \'' . $this->sql_escape($expression) . '\''); + } + + /** + * Build a case expression + * + * Note: The two statements action_true and action_false must have the same data type (int, vchar, ...) in the database! + * + * @param string $condition The condition which must be true, to use action_true rather then action_else + * @param string $action_true SQL expression that is used, if the condition is true + * @param string $action_else SQL expression that is used, if the condition is false, optional + * @return string CASE expression including the condition and statements + */ + public function sql_case($condition, $action_true, $action_false = false) + { + $sql_case = 'CASE WHEN ' . $condition; + $sql_case .= ' THEN ' . $action_true; + $sql_case .= ($action_false !== false) ? ' ELSE ' . $action_false : ''; + $sql_case .= ' END'; + return $sql_case; + } + + /** + * Build a concatenated expression + * + * @param string $expr1 Base SQL expression where we append the second one + * @param string $expr2 SQL expression that is appended to the first expression + * @return string Concatenated string + */ + public function sql_concatenate($expr1, $expr2) + { + return $expr1 . ' || ' . $expr2; + } + + /** + * Returns whether results of a query need to be buffered to run a transaction while iterating over them. + * + * @return bool Whether buffering is required. + */ + function sql_buffer_nested_transactions() + { + return false; + } + + /** + * SQL Transaction + * @access private + */ + function sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + // If we are within a transaction we will not open another one, but enclose the current one to not loose data (prevening auto commit) + if ($this->transaction) + { + $this->transactions++; + return true; + } + + $result = $this->_sql_transaction('begin'); + + if (!$result) + { + $this->sql_error(); + } + + $this->transaction = true; + break; + + case 'commit': + // If there was a previously opened transaction we do not commit yet... but count back the number of inner transactions + if ($this->transaction && $this->transactions) + { + $this->transactions--; + return true; + } + + // Check if there is a transaction (no transaction can happen if there was an error, with a combined rollback and error returning enabled) + // This implies we have transaction always set for autocommit db's + if (!$this->transaction) + { + return false; + } + + $result = $this->_sql_transaction('commit'); + + if (!$result) + { + $this->sql_error(); + } + + $this->transaction = false; + $this->transactions = 0; + break; + + case 'rollback': + $result = $this->_sql_transaction('rollback'); + $this->transaction = false; + $this->transactions = 0; + break; + + default: + $result = $this->_sql_transaction($status); + break; + } + + return $result; + } + + /** + * Build sql statement from array for insert/update/select statements + * + * Idea for this from Ikonboard + * Possible query values: INSERT, INSERT_SELECT, UPDATE, SELECT + * + */ + function sql_build_array($query, $assoc_ary = false) + { + if (!is_array($assoc_ary)) + { + return false; + } + + $fields = $values = array(); + + if ($query == 'INSERT' || $query == 'INSERT_SELECT') + { + foreach ($assoc_ary as $key => $var) + { + $fields[] = $key; + + if (is_array($var) && is_string($var[0])) + { + // This is used for INSERT_SELECT(s) + $values[] = $var[0]; + } + else + { + $values[] = $this->_sql_validate_value($var); + } + } + + $query = ($query == 'INSERT') ? ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')' : ' (' . implode(', ', $fields) . ') SELECT ' . implode(', ', $values) . ' '; + } + else if ($query == 'MULTI_INSERT') + { + trigger_error('The MULTI_INSERT query value is no longer supported. Please use sql_multi_insert() instead.', E_USER_ERROR); + } + else if ($query == 'UPDATE' || $query == 'SELECT') + { + $values = array(); + foreach ($assoc_ary as $key => $var) + { + $values[] = "$key = " . $this->_sql_validate_value($var); + } + $query = implode(($query == 'UPDATE') ? ', ' : ' AND ', $values); + } + + return $query; + } + + /** + * Build IN or NOT IN sql comparison string, uses <> or = on single element + * arrays to improve comparison speed + * + * @access public + * @param string $field name of the sql column that shall be compared + * @param array $array array of values that are allowed (IN) or not allowed (NOT IN) + * @param bool $negate true for NOT IN (), false for IN () (default) + * @param bool $allow_empty_set If true, allow $array to be empty, this function will return 1=1 or 1=0 then. Default to false. + */ + function sql_in_set($field, $array, $negate = false, $allow_empty_set = false) + { + if (!sizeof($array)) + { + if (!$allow_empty_set) + { + // Print the backtrace to help identifying the location of the problematic code + $this->sql_error('No values specified for SQL IN comparison'); + } + else + { + // NOT IN () actually means everything so use a tautology + if ($negate) + { + return '1=1'; + } + // IN () actually means nothing so use a contradiction + else + { + return '1=0'; + } + } + } + + if (!is_array($array)) + { + $array = array($array); + } + + if (sizeof($array) == 1) + { + @reset($array); + $var = current($array); + + return $field . ($negate ? ' <> ' : ' = ') . $this->_sql_validate_value($var); + } + else + { + return $field . ($negate ? ' NOT IN ' : ' IN ') . '(' . implode(', ', array_map(array($this, '_sql_validate_value'), $array)) . ')'; + } + } + + /** + * Run binary AND operator on DB column. + * Results in sql statement: "{$column_name} & (1 << {$bit}) {$compare}" + * + * @param string $column_name The column name to use + * @param int $bit The value to use for the AND operator, will be converted to (1 << $bit). Is used by options, using the number schema... 0, 1, 2...29 + * @param string $compare Any custom SQL code after the check (for example "= 0") + */ + function sql_bit_and($column_name, $bit, $compare = '') + { + if (method_exists($this, '_sql_bit_and')) + { + return $this->_sql_bit_and($column_name, $bit, $compare); + } + + return $column_name . ' & ' . (1 << $bit) . (($compare) ? ' ' . $compare : ''); + } + + /** + * Run binary OR operator on DB column. + * Results in sql statement: "{$column_name} | (1 << {$bit}) {$compare}" + * + * @param string $column_name The column name to use + * @param int $bit The value to use for the OR operator, will be converted to (1 << $bit). Is used by options, using the number schema... 0, 1, 2...29 + * @param string $compare Any custom SQL code after the check (for example "= 0") + */ + function sql_bit_or($column_name, $bit, $compare = '') + { + if (method_exists($this, '_sql_bit_or')) + { + return $this->_sql_bit_or($column_name, $bit, $compare); + } + + return $column_name . ' | ' . (1 << $bit) . (($compare) ? ' ' . $compare : ''); + } + + /** + * Returns SQL string to cast a string expression to an int. + * + * @param string $expression An expression evaluating to string + * @return string Expression returning an int + */ + function cast_expr_to_bigint($expression) + { + return $expression; + } + + /** + * Returns SQL string to cast an integer expression to a string. + * + * @param string $expression An expression evaluating to int + * @return string Expression returning a string + */ + function cast_expr_to_string($expression) + { + return $expression; + } + + /** + * Run LOWER() on DB column of type text (i.e. neither varchar nor char). + * + * @param string $column_name The column name to use + * + * @return string A SQL statement like "LOWER($column_name)" + */ + function sql_lower_text($column_name) + { + return "LOWER($column_name)"; + } + + /** + * Run more than one insert statement. + * + * @param string $table table name to run the statements on + * @param array &$sql_ary multi-dimensional array holding the statement data. + * + * @return bool false if no statements were executed. + * @access public + */ + function sql_multi_insert($table, &$sql_ary) + { + if (!sizeof($sql_ary)) + { + return false; + } + + if ($this->multi_insert) + { + $ary = array(); + foreach ($sql_ary as $id => $_sql_ary) + { + // If by accident the sql array is only one-dimensional we build a normal insert statement + if (!is_array($_sql_ary)) + { + return $this->sql_query('INSERT INTO ' . $table . ' ' . $this->sql_build_array('INSERT', $sql_ary)); + } + + $values = array(); + foreach ($_sql_ary as $key => $var) + { + $values[] = $this->_sql_validate_value($var); + } + $ary[] = '(' . implode(', ', $values) . ')'; + } + + return $this->sql_query('INSERT INTO ' . $table . ' ' . ' (' . implode(', ', array_keys($sql_ary[0])) . ') VALUES ' . implode(', ', $ary)); + } + else + { + foreach ($sql_ary as $ary) + { + if (!is_array($ary)) + { + return false; + } + + $result = $this->sql_query('INSERT INTO ' . $table . ' ' . $this->sql_build_array('INSERT', $ary)); + + if (!$result) + { + return false; + } + } + } + + return true; + } + + /** + * Function for validating values + * @access private + */ + function _sql_validate_value($var) + { + if (is_null($var)) + { + return 'NULL'; + } + else if (is_string($var)) + { + return "'" . $this->sql_escape($var) . "'"; + } + else + { + return (is_bool($var)) ? intval($var) : $var; + } + } + + /** + * Build sql statement from array for select and select distinct statements + * + * Possible query values: SELECT, SELECT_DISTINCT + */ + function sql_build_query($query, $array) + { + $sql = ''; + switch ($query) + { + case 'SELECT': + case 'SELECT_DISTINCT'; + + $sql = str_replace('_', ' ', $query) . ' ' . $array['SELECT'] . ' FROM '; + + // Build table array. We also build an alias array for later checks. + $table_array = $aliases = array(); + $used_multi_alias = false; + + foreach ($array['FROM'] as $table_name => $alias) + { + if (is_array($alias)) + { + $used_multi_alias = true; + + foreach ($alias as $multi_alias) + { + $table_array[] = $table_name . ' ' . $multi_alias; + $aliases[] = $multi_alias; + } + } + else + { + $table_array[] = $table_name . ' ' . $alias; + $aliases[] = $alias; + } + } + + // We run the following code to determine if we need to re-order the table array. ;) + // The reason for this is that for multi-aliased tables (two equal tables) in the FROM statement the last table need to match the first comparison. + // DBMS who rely on this: Oracle, PostgreSQL and MSSQL. For all other DBMS it makes absolutely no difference in which order the table is. + if (!empty($array['LEFT_JOIN']) && sizeof($array['FROM']) > 1 && $used_multi_alias !== false) + { + // Take first LEFT JOIN + $join = current($array['LEFT_JOIN']); + + // Determine the table used there (even if there are more than one used, we only want to have one + preg_match('/(' . implode('|', $aliases) . ')\.[^\s]+/U', str_replace(array('(', ')', 'AND', 'OR', ' '), '', $join['ON']), $matches); + + // If there is a first join match, we need to make sure the table order is correct + if (!empty($matches[1])) + { + $first_join_match = trim($matches[1]); + $table_array = $last = array(); + + foreach ($array['FROM'] as $table_name => $alias) + { + if (is_array($alias)) + { + foreach ($alias as $multi_alias) + { + ($multi_alias === $first_join_match) ? $last[] = $table_name . ' ' . $multi_alias : $table_array[] = $table_name . ' ' . $multi_alias; + } + } + else + { + ($alias === $first_join_match) ? $last[] = $table_name . ' ' . $alias : $table_array[] = $table_name . ' ' . $alias; + } + } + + $table_array = array_merge($table_array, $last); + } + } + + $sql .= $this->_sql_custom_build('FROM', implode(' CROSS JOIN ', $table_array)); + + if (!empty($array['LEFT_JOIN'])) + { + foreach ($array['LEFT_JOIN'] as $join) + { + $sql .= ' LEFT JOIN ' . key($join['FROM']) . ' ' . current($join['FROM']) . ' ON (' . $join['ON'] . ')'; + } + } + + if (!empty($array['WHERE'])) + { + $sql .= ' WHERE ' . $this->_sql_custom_build('WHERE', $array['WHERE']); + } + + if (!empty($array['GROUP_BY'])) + { + $sql .= ' GROUP BY ' . $array['GROUP_BY']; + } + + if (!empty($array['ORDER_BY'])) + { + $sql .= ' ORDER BY ' . $array['ORDER_BY']; + } + + break; + } + + return $sql; + } + + /** + * display sql error page + */ + function sql_error($sql = '') + { + global $auth, $user, $config; + + // Set var to retrieve errored status + $this->sql_error_triggered = true; + $this->sql_error_sql = $sql; + + $this->sql_error_returned = $this->_sql_error(); + + if (!$this->return_on_error) + { + $message = 'SQL ERROR [ ' . $this->sql_layer . ' ]

' . $this->sql_error_returned['message'] . ' [' . $this->sql_error_returned['code'] . ']'; + + // Show complete SQL error and path to administrators only + // Additionally show complete error on installation or if extended debug mode is enabled + // The DEBUG_EXTRA constant is for development only! + if ((isset($auth) && $auth->acl_get('a_')) || defined('IN_INSTALL') || defined('DEBUG_EXTRA')) + { + $message .= ($sql) ? '

SQL

' . htmlspecialchars($sql) : ''; + } + else + { + // If error occurs in initiating the session we need to use a pre-defined language string + // This could happen if the connection could not be established for example (then we are not able to grab the default language) + if (!isset($user->lang['SQL_ERROR_OCCURRED'])) + { + $message .= '

An sql error occurred while fetching this page. Please contact an administrator if this problem persists.'; + } + else + { + if (!empty($config['board_contact'])) + { + $message .= '

' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '', ''); + } + else + { + $message .= '

' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '', ''); + } + } + } + + if ($this->transaction) + { + $this->sql_transaction('rollback'); + } + + if (strlen($message) > 1024) + { + // We need to define $msg_long_text here to circumvent text stripping. + global $msg_long_text; + $msg_long_text = $message; + + trigger_error(false, E_USER_ERROR); + } + + trigger_error($message, E_USER_ERROR); + } + + if ($this->transaction) + { + $this->sql_transaction('rollback'); + } + + return $this->sql_error_returned; + } + + /** + * Explain queries + */ + function sql_report($mode, $query = '') + { + global $cache, $starttime, $phpbb_root_path, $user; + global $request; + + if (is_object($request) && !$request->variable('explain', false)) + { + return false; + } + + if (!$query && $this->query_hold != '') + { + $query = $this->query_hold; + } + + switch ($mode) + { + case 'display': + if (!empty($cache)) + { + $cache->unload(); + } + $this->sql_close(); + + $mtime = explode(' ', microtime()); + $totaltime = $mtime[0] + $mtime[1] - $starttime; + + echo ' + + + + SQL Report + + + +
+ +
+
+
+ +
+

SQL Report

+
+

Page generated in ' . round($totaltime, 4) . " seconds with {$this->num_queries['normal']} queries" . (($this->num_queries['cached']) ? " + {$this->num_queries['cached']} " . (($this->num_queries['cached'] == 1) ? 'query' : 'queries') . ' returning data from cache' : '') . '

+ +

Time spent on ' . $this->sql_layer . ' queries: ' . round($this->sql_time, 5) . 's | Time spent on PHP: ' . round($totaltime - $this->sql_time, 5) . 's

+ +

+ ' . $this->sql_report . ' +
+ +
+
+
+ +
+ + '; + + exit_handler(); + + break; + + case 'stop': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $this->sql_report .= ' + + + + + + + + + + + + +
Query #' . $this->num_queries['total'] . '
+ + ' . $this->html_hold . ' + +

+ '; + + if ($this->query_result) + { + if (preg_match('/^(UPDATE|DELETE|REPLACE)/', $query)) + { + $this->sql_report .= 'Affected rows: ' . $this->sql_affectedrows($this->query_result) . ' | '; + } + $this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed: ' . sprintf('%.5f', $endtime - $this->curtime) . 's'; + } + else + { + $error = $this->sql_error(); + $this->sql_report .= 'FAILED - ' . $this->sql_layer . ' Error ' . $error['code'] . ': ' . htmlspecialchars($error['message']); + } + + $this->sql_report .= '



'; + + $this->sql_time += $endtime - $this->curtime; + break; + + case 'start': + $this->query_hold = $query; + $this->html_hold = ''; + + $this->_sql_report($mode, $query); + + $this->curtime = explode(' ', microtime()); + $this->curtime = $this->curtime[0] + $this->curtime[1]; + + break; + + case 'add_select_row': + + $html_table = func_get_arg(2); + $row = func_get_arg(3); + + if (!$html_table && sizeof($row)) + { + $html_table = true; + $this->html_hold .= ''; + + foreach (array_keys($row) as $val) + { + $this->html_hold .= ''; + } + $this->html_hold .= ''; + } + $this->html_hold .= ''; + + $class = 'row1'; + foreach (array_values($row) as $val) + { + $class = ($class == 'row1') ? 'row2' : 'row1'; + $this->html_hold .= ''; + } + $this->html_hold .= ''; + + return $html_table; + + break; + + case 'fromcache': + + $this->_sql_report($mode, $query); + + break; + + case 'record_fromcache': + + $endtime = func_get_arg(2); + $splittime = func_get_arg(3); + + $time_cache = $endtime - $this->curtime; + $time_db = $splittime - $endtime; + $color = ($time_db > $time_cache) ? 'green' : 'red'; + + $this->sql_report .= '
' . (($val) ? ucwords(str_replace('_', ' ', $val)) : ' ') . '
' . (($val) ? $val : ' ') . '
'; + $this->sql_report .= '
Query results obtained from the cache
'; + $this->sql_report .= '

'; + $this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed [cache]: ' . sprintf('%.5f', ($time_cache)) . 's | Elapsed [db]: ' . sprintf('%.5f', $time_db) . 's



'; + + // Pad the start time to not interfere with page timing + $starttime += $time_db; + + break; + + default: + + $this->_sql_report($mode, $query); + + break; + } + + return true; + } + + /** + * Gets the estimated number of rows in a specified table. + * + * @param string $table_name Table name + * + * @return string Number of rows in $table_name. + * Prefixed with ~ if estimated (otherwise exact). + * + * @access public + */ + function get_estimated_row_count($table_name) + { + return $this->get_row_count($table_name); + } + + /** + * Gets the exact number of rows in a specified table. + * + * @param string $table_name Table name + * + * @return string Exact number of rows in $table_name. + * + * @access public + */ + function get_row_count($table_name) + { + $sql = 'SELECT COUNT(*) AS rows_total + FROM ' . $this->sql_escape($table_name); + $result = $this->sql_query($sql); + $rows_total = $this->sql_fetchfield('rows_total'); + $this->sql_freeresult($result); + + return $rows_total; + } +} diff --git a/phpBB/includes/db/driver/firebird.php b/phpBB/includes/db/driver/firebird.php new file mode 100644 index 0000000000..c793e0a51f --- /dev/null +++ b/phpBB/includes/db/driver/firebird.php @@ -0,0 +1,538 @@ +persistency = $persistency; + $this->user = $sqluser; + $this->server = $sqlserver . (($port) ? ':' . $port : ''); + $this->dbname = str_replace('\\', '/', $database); + + // There are three possibilities to connect to an interbase db + if (!$this->server) + { + $use_database = $this->dbname; + } + else if (strpos($this->server, '//') === 0) + { + $use_database = $this->server . $this->dbname; + } + else + { + $use_database = $this->server . ':' . $this->dbname; + } + + if ($this->persistency) + { + if (!function_exists('ibase_pconnect')) + { + $this->connect_error = 'ibase_pconnect function does not exist, is interbase extension installed?'; + return $this->sql_error(''); + } + $this->db_connect_id = @ibase_pconnect($use_database, $this->user, $sqlpassword, false, false, 3); + } + else + { + if (!function_exists('ibase_connect')) + { + $this->connect_error = 'ibase_connect function does not exist, is interbase extension installed?'; + return $this->sql_error(''); + } + $this->db_connect_id = @ibase_connect($use_database, $this->user, $sqlpassword, false, false, 3); + } + + // Do not call ibase_service_attach if connection failed, + // otherwise error message from ibase_(p)connect call will be clobbered. + if ($this->db_connect_id && function_exists('ibase_service_attach') && $this->server) + { + $this->service_handle = @ibase_service_attach($this->server, $this->user, $sqlpassword); + } + else + { + $this->service_handle = false; + } + + return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); + } + + /** + * Version information about used database + * @param bool $raw if true, only return the fetched sql_server_version + * @param bool $use_cache forced to false for Interbase + * @return string sql server version + */ + function sql_server_info($raw = false, $use_cache = true) + { + /** + * force $use_cache false. I didn't research why the caching code there is no caching code + * but I assume its because the IB extension provides a direct method to access it + * without a query. + */ + + $use_cache = false; + + if ($this->service_handle !== false && function_exists('ibase_server_info')) + { + return @ibase_server_info($this->service_handle, IBASE_SVC_SERVER_VERSION); + } + + return ($raw) ? '2.1' : 'Firebird/Interbase'; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return true; + break; + + case 'commit': + return @ibase_commit(); + break; + + case 'rollback': + return @ibase_rollback(); + break; + } + + return true; + } + + /** + * Base query method + * + * @param string $query Contains the SQL query which shall be executed + * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache + * @return mixed When casted to bool the returned value returns true on success and false on failure + * + * @access public + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('start', $query); + } + + $this->last_query_text = $query; + $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + $array = array(); + // We overcome Firebird's 32767 char limit by binding vars + if (strlen($query) > 32767) + { + if (preg_match('/^(INSERT INTO[^(]++)\\(([^()]+)\\) VALUES[^(]++\\((.*?)\\)$/s', $query, $regs)) + { + if (strlen($regs[3]) > 32767) + { + preg_match_all('/\'(?:[^\']++|\'\')*+\'|[\d-.]+/', $regs[3], $vals, PREG_PATTERN_ORDER); + + $inserts = $vals[0]; + unset($vals); + + foreach ($inserts as $key => $value) + { + if (!empty($value) && $value[0] === "'" && strlen($value) > 32769) // check to see if this thing is greater than the max + 'x2 + { + $inserts[$key] = '?'; + $array[] = str_replace("''", "'", substr($value, 1, -1)); + } + } + + $query = $regs[1] . '(' . $regs[2] . ') VALUES (' . implode(', ', $inserts) . ')'; + } + } + else if (preg_match('/^(UPDATE ([\\w_]++)\\s+SET )([\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|\\d+)(?:,\\s*[\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+))*+)\\s+(WHERE.*)$/s', $query, $data)) + { + if (strlen($data[3]) > 32767) + { + $update = $data[1]; + $where = $data[4]; + preg_match_all('/(\\w++)\\s*=\\s*(\'(?:[^\']++|\'\')*+\'|[\d-.]++)/', $data[3], $temp, PREG_SET_ORDER); + unset($data); + + $cols = array(); + foreach ($temp as $value) + { + if (!empty($value[2]) && $value[2][0] === "'" && strlen($value[2]) > 32769) // check to see if this thing is greater than the max + 'x2 + { + $array[] = str_replace("''", "'", substr($value[2], 1, -1)); + $cols[] = $value[1] . '=?'; + } + else + { + $cols[] = $value[1] . '=' . $value[2]; + } + } + + $query = $update . implode(', ', $cols) . ' ' . $where; + unset($cols); + } + } + } + + if (!function_exists('ibase_affected_rows') && (preg_match('/^UPDATE ([\w_]++)\s+SET [\w_]++\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+)(?:,\s*[\w_]++\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+))*+\s+(WHERE.*)?$/s', $query, $regs) || preg_match('/^DELETE FROM ([\w_]++)\s*(WHERE\s*.*)?$/s', $query, $regs))) + { + $affected_sql = 'SELECT COUNT(*) as num_rows_affected FROM ' . $regs[1]; + if (!empty($regs[2])) + { + $affected_sql .= ' ' . $regs[2]; + } + + if (!($temp_q_id = @ibase_query($this->db_connect_id, $affected_sql))) + { + return false; + } + + $temp_result = @ibase_fetch_assoc($temp_q_id); + @ibase_free_result($temp_q_id); + + $this->affected_rows = ($temp_result) ? $temp_result['NUM_ROWS_AFFECTED'] : false; + } + + if (sizeof($array)) + { + $p_query = @ibase_prepare($this->db_connect_id, $query); + array_unshift($array, $p_query); + $this->query_result = call_user_func_array('ibase_execute', $array); + unset($array); + + if ($this->query_result === false) + { + $this->sql_error($query); + } + } + else if (($this->query_result = @ibase_query($this->db_connect_id, $query)) === false) + { + $this->sql_error($query); + } + + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('stop', $query); + } + + if (!$this->transaction) + { + if (function_exists('ibase_commit_ret')) + { + @ibase_commit_ret(); + } + else + { + // way cooler than ibase_commit_ret :D + @ibase_query('COMMIT RETAIN;'); + } + } + + if ($cache_ttl && method_exists($cache, 'sql_save')) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $cache->sql_save($query, $this->query_result, $cache_ttl); + } + else if (strpos($query, 'SELECT') === 0 && $this->query_result) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG_EXTRA')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return $this->query_result; + } + + /** + * Build LIMIT query + */ + function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + $this->query_result = false; + + $query = 'SELECT FIRST ' . $total . ((!empty($offset)) ? ' SKIP ' . $offset : '') . substr($query, 6); + + return $this->sql_query($query, $cache_ttl); + } + + /** + * Return number of affected rows + */ + function sql_affectedrows() + { + // PHP 5+ function + if (function_exists('ibase_affected_rows')) + { + return ($this->db_connect_id) ? @ibase_affected_rows($this->db_connect_id) : false; + } + else + { + return $this->affected_rows; + } + } + + /** + * Fetch current row + */ + function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_fetchrow($query_id); + } + + if ($query_id === false) + { + return false; + } + + $row = array(); + $cur_row = @ibase_fetch_object($query_id, IBASE_TEXT); + + if (!$cur_row) + { + return false; + } + + foreach (get_object_vars($cur_row) as $key => $value) + { + $row[strtolower($key)] = (is_string($value)) ? trim(str_replace(array("\\0", "\\n"), array("\0", "\n"), $value)) : $value; + } + + return (sizeof($row)) ? $row : false; + } + + /** + * Get last inserted id after insert statement + */ + function sql_nextid() + { + $query_id = $this->query_result; + + if ($query_id !== false && $this->last_query_text != '') + { + if ($this->query_result && preg_match('#^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)#i', $this->last_query_text, $tablename)) + { + $sql = 'SELECT GEN_ID(' . $tablename[1] . '_gen, 0) AS new_id FROM RDB$DATABASE'; + + if (!($temp_q_id = @ibase_query($this->db_connect_id, $sql))) + { + return false; + } + + $temp_result = @ibase_fetch_assoc($temp_q_id); + @ibase_free_result($temp_q_id); + + return ($temp_result) ? $temp_result['NEW_ID'] : false; + } + } + + return false; + } + + /** + * Free sql result + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_freeresult($query_id); + } + + if (isset($this->open_queries[(int) $query_id])) + { + unset($this->open_queries[(int) $query_id]); + return @ibase_free_result($query_id); + } + + return false; + } + + /** + * Escape string used in sql query + */ + function sql_escape($msg) + { + return str_replace(array("'", "\0"), array("''", ''), $msg); + } + + /** + * Build LIKE expression + * @access private + */ + function _sql_like_expression($expression) + { + return $expression . " ESCAPE '\\'"; + } + + /** + * Build db-specific query data + * @access private + */ + function _sql_custom_build($stage, $data) + { + return $data; + } + + function _sql_bit_and($column_name, $bit, $compare = '') + { + return 'BIN_AND(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : ''); + } + + function _sql_bit_or($column_name, $bit, $compare = '') + { + return 'BIN_OR(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : ''); + } + + /** + * @inheritdoc + */ + function cast_expr_to_bigint($expression) + { + // Precision must be from 1 to 18 + return 'CAST(' . $expression . ' as DECIMAL(18, 0))'; + } + + /** + * @inheritdoc + */ + function cast_expr_to_string($expression) + { + return 'CAST(' . $expression . ' as VARCHAR(255))'; + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + // Need special handling here because ibase_errmsg returns + // connection errors, however if the interbase extension + // is not installed then ibase_errmsg does not exist and + // we cannot call it. + if (function_exists('ibase_errmsg')) + { + $msg = @ibase_errmsg(); + if (!$msg) + { + $msg = $this->connect_error; + } + } + else + { + $msg = $this->connect_error; + } + return array( + 'message' => $msg, + 'code' => (@function_exists('ibase_errcode') ? @ibase_errcode() : '') + ); + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + if ($this->service_handle !== false) + { + @ibase_service_detach($this->service_handle); + } + + return @ibase_close($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + switch ($mode) + { + case 'start': + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @ibase_query($this->db_connect_id, $query); + while ($void = @ibase_fetch_object($result, IBASE_TEXT)) + { + // Take the time spent on parsing rows into account + } + @ibase_free_result($result); + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } +} diff --git a/phpBB/includes/db/driver/mssql.php b/phpBB/includes/db/driver/mssql.php new file mode 100644 index 0000000000..e68738f918 --- /dev/null +++ b/phpBB/includes/db/driver/mssql.php @@ -0,0 +1,454 @@ +persistency = $persistency; + $this->user = $sqluser; + $this->dbname = $database; + + $port_delimiter = (defined('PHP_OS') && substr(PHP_OS, 0, 3) === 'WIN') ? ',' : ':'; + $this->server = $sqlserver . (($port) ? $port_delimiter . $port : ''); + + @ini_set('mssql.charset', 'UTF-8'); + @ini_set('mssql.textlimit', 2147483647); + @ini_set('mssql.textsize', 2147483647); + + $this->db_connect_id = ($this->persistency) ? @mssql_pconnect($this->server, $this->user, $sqlpassword, $new_link) : @mssql_connect($this->server, $this->user, $sqlpassword, $new_link); + + if ($this->db_connect_id && $this->dbname != '') + { + if (!@mssql_select_db($this->dbname, $this->db_connect_id)) + { + @mssql_close($this->db_connect_id); + return false; + } + } + + return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); + } + + /** + * Version information about used database + * @param bool $raw if true, only return the fetched sql_server_version + * @param bool $use_cache If true, it is safe to retrieve the value from the cache + * @return string sql server version + */ + function sql_server_info($raw = false, $use_cache = true) + { + global $cache; + + if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mssql_version')) === false) + { + $result_id = @mssql_query("SELECT SERVERPROPERTY('productversion'), SERVERPROPERTY('productlevel'), SERVERPROPERTY('edition')", $this->db_connect_id); + + $row = false; + if ($result_id) + { + $row = @mssql_fetch_assoc($result_id); + @mssql_free_result($result_id); + } + + $this->sql_server_version = ($row) ? trim(implode(' ', $row)) : 0; + + if (!empty($cache) && $use_cache) + { + $cache->put('mssql_version', $this->sql_server_version); + } + } + + if ($raw) + { + return $this->sql_server_version; + } + + return ($this->sql_server_version) ? 'MSSQL
' . $this->sql_server_version : 'MSSQL'; + } + + /** + * {@inheritDoc} + */ + public function sql_concatenate($expr1, $expr2) + { + return $expr1 . ' + ' . $expr2; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return @mssql_query('BEGIN TRANSACTION', $this->db_connect_id); + break; + + case 'commit': + return @mssql_query('COMMIT TRANSACTION', $this->db_connect_id); + break; + + case 'rollback': + return @mssql_query('ROLLBACK TRANSACTION', $this->db_connect_id); + break; + } + + return true; + } + + /** + * Base query method + * + * @param string $query Contains the SQL query which shall be executed + * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache + * @return mixed When casted to bool the returned value returns true on success and false on failure + * + * @access public + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('start', $query); + } + + $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + if (($this->query_result = @mssql_query($query, $this->db_connect_id)) === false) + { + $this->sql_error($query); + } + + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('stop', $query); + } + + if ($cache_ttl && method_exists($cache, 'sql_save')) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $cache->sql_save($query, $this->query_result, $cache_ttl); + } + else if (strpos($query, 'SELECT') === 0 && $this->query_result) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG_EXTRA')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return $this->query_result; + } + + /** + * Build LIMIT query + */ + function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + $this->query_result = false; + + // Since TOP is only returning a set number of rows we won't need it if total is set to 0 (return all rows) + if ($total) + { + // We need to grab the total number of rows + the offset number of rows to get the correct result + if (strpos($query, 'SELECT DISTINCT') === 0) + { + $query = 'SELECT DISTINCT TOP ' . ($total + $offset) . ' ' . substr($query, 15); + } + else + { + $query = 'SELECT TOP ' . ($total + $offset) . ' ' . substr($query, 6); + } + } + + $result = $this->sql_query($query, $cache_ttl); + + // Seek by $offset rows + if ($offset) + { + $this->sql_rowseek($offset, $result); + } + + return $result; + } + + /** + * Return number of affected rows + */ + function sql_affectedrows() + { + return ($this->db_connect_id) ? @mssql_rows_affected($this->db_connect_id) : false; + } + + /** + * Fetch current row + */ + function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_fetchrow($query_id); + } + + if ($query_id === false) + { + return false; + } + + $row = @mssql_fetch_assoc($query_id); + + // I hope i am able to remove this later... hopefully only a PHP or MSSQL bug + if ($row) + { + foreach ($row as $key => $value) + { + $row[$key] = ($value === ' ' || $value === NULL) ? '' : $value; + } + } + + return $row; + } + + /** + * Seek to given row number + * rownum is zero-based + */ + function sql_rowseek($rownum, &$query_id) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_rowseek($rownum, $query_id); + } + + return ($query_id !== false) ? @mssql_data_seek($query_id, $rownum) : false; + } + + /** + * Get last inserted id after insert statement + */ + function sql_nextid() + { + $result_id = @mssql_query('SELECT SCOPE_IDENTITY()', $this->db_connect_id); + if ($result_id) + { + if ($row = @mssql_fetch_assoc($result_id)) + { + @mssql_free_result($result_id); + return $row['computed']; + } + @mssql_free_result($result_id); + } + + return false; + } + + /** + * Free sql result + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_freeresult($query_id); + } + + if (isset($this->open_queries[$query_id])) + { + unset($this->open_queries[$query_id]); + return @mssql_free_result($query_id); + } + + return false; + } + + /** + * Escape string used in sql query + */ + function sql_escape($msg) + { + return str_replace(array("'", "\0"), array("''", ''), $msg); + } + + /** + * {@inheritDoc} + */ + function sql_lower_text($column_name) + { + return "LOWER(SUBSTRING($column_name, 1, DATALENGTH($column_name)))"; + } + + /** + * Build LIKE expression + * @access private + */ + function _sql_like_expression($expression) + { + return $expression . " ESCAPE '\\'"; + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + $error = array( + 'message' => @mssql_get_last_message(), + 'code' => '' + ); + + // Get error code number + $result_id = @mssql_query('SELECT @@ERROR as code', $this->db_connect_id); + if ($result_id) + { + $row = @mssql_fetch_assoc($result_id); + $error['code'] = $row['code']; + @mssql_free_result($result_id); + } + + // Get full error message if possible + $sql = 'SELECT CAST(description as varchar(255)) as message + FROM master.dbo.sysmessages + WHERE error = ' . $error['code']; + $result_id = @mssql_query($sql); + + if ($result_id) + { + $row = @mssql_fetch_assoc($result_id); + if (!empty($row['message'])) + { + $error['message'] .= '
' . $row['message']; + } + @mssql_free_result($result_id); + } + + return $error; + } + + /** + * Build db-specific query data + * @access private + */ + function _sql_custom_build($stage, $data) + { + return $data; + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + return @mssql_close($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + switch ($mode) + { + case 'start': + $html_table = false; + @mssql_query('SET SHOWPLAN_TEXT ON;', $this->db_connect_id); + if ($result = @mssql_query($query, $this->db_connect_id)) + { + @mssql_next_result($result); + while ($row = @mssql_fetch_row($result)) + { + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + } + @mssql_query('SET SHOWPLAN_TEXT OFF;', $this->db_connect_id); + @mssql_free_result($result); + + if ($html_table) + { + $this->html_hold .= ''; + } + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @mssql_query($query, $this->db_connect_id); + while ($void = @mssql_fetch_assoc($result)) + { + // Take the time spent on parsing rows into account + } + @mssql_free_result($result); + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } +} diff --git a/phpBB/includes/db/driver/mssql_odbc.php b/phpBB/includes/db/driver/mssql_odbc.php new file mode 100644 index 0000000000..7b35ce3d11 --- /dev/null +++ b/phpBB/includes/db/driver/mssql_odbc.php @@ -0,0 +1,395 @@ +persistency = $persistency; + $this->user = $sqluser; + $this->dbname = $database; + + $port_delimiter = (defined('PHP_OS') && substr(PHP_OS, 0, 3) === 'WIN') ? ',' : ':'; + $this->server = $sqlserver . (($port) ? $port_delimiter . $port : ''); + + $max_size = @ini_get('odbc.defaultlrl'); + if (!empty($max_size)) + { + $unit = strtolower(substr($max_size, -1, 1)); + $max_size = (int) $max_size; + + if ($unit == 'k') + { + $max_size = floor($max_size / 1024); + } + else if ($unit == 'g') + { + $max_size *= 1024; + } + else if (is_numeric($unit)) + { + $max_size = floor((int) ($max_size . $unit) / 1048576); + } + $max_size = max(8, $max_size) . 'M'; + + @ini_set('odbc.defaultlrl', $max_size); + } + + $this->db_connect_id = ($this->persistency) ? @odbc_pconnect($this->server, $this->user, $sqlpassword) : @odbc_connect($this->server, $this->user, $sqlpassword); + + return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); + } + + /** + * Version information about used database + * @param bool $raw if true, only return the fetched sql_server_version + * @param bool $use_cache If true, it is safe to retrieve the value from the cache + * @return string sql server version + */ + function sql_server_info($raw = false, $use_cache = true) + { + global $cache; + + if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mssqlodbc_version')) === false) + { + $result_id = @odbc_exec($this->db_connect_id, "SELECT SERVERPROPERTY('productversion'), SERVERPROPERTY('productlevel'), SERVERPROPERTY('edition')"); + + $row = false; + if ($result_id) + { + $row = @odbc_fetch_array($result_id); + @odbc_free_result($result_id); + } + + $this->sql_server_version = ($row) ? trim(implode(' ', $row)) : 0; + + if (!empty($cache) && $use_cache) + { + $cache->put('mssqlodbc_version', $this->sql_server_version); + } + } + + if ($raw) + { + return $this->sql_server_version; + } + + return ($this->sql_server_version) ? 'MSSQL (ODBC)
' . $this->sql_server_version : 'MSSQL (ODBC)'; + } + + /** + * {@inheritDoc} + */ + public function sql_concatenate($expr1, $expr2) + { + return $expr1 . ' + ' . $expr2; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return @odbc_exec($this->db_connect_id, 'BEGIN TRANSACTION'); + break; + + case 'commit': + return @odbc_exec($this->db_connect_id, 'COMMIT TRANSACTION'); + break; + + case 'rollback': + return @odbc_exec($this->db_connect_id, 'ROLLBACK TRANSACTION'); + break; + } + + return true; + } + + /** + * Base query method + * + * @param string $query Contains the SQL query which shall be executed + * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache + * @return mixed When casted to bool the returned value returns true on success and false on failure + * + * @access public + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('start', $query); + } + + $this->last_query_text = $query; + $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + if (($this->query_result = @odbc_exec($this->db_connect_id, $query)) === false) + { + $this->sql_error($query); + } + + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('stop', $query); + } + + if ($cache_ttl && method_exists($cache, 'sql_save')) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $cache->sql_save($query, $this->query_result, $cache_ttl); + } + else if (strpos($query, 'SELECT') === 0 && $this->query_result) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG_EXTRA')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return $this->query_result; + } + + /** + * Build LIMIT query + */ + function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + $this->query_result = false; + + // Since TOP is only returning a set number of rows we won't need it if total is set to 0 (return all rows) + if ($total) + { + // We need to grab the total number of rows + the offset number of rows to get the correct result + if (strpos($query, 'SELECT DISTINCT') === 0) + { + $query = 'SELECT DISTINCT TOP ' . ($total + $offset) . ' ' . substr($query, 15); + } + else + { + $query = 'SELECT TOP ' . ($total + $offset) . ' ' . substr($query, 6); + } + } + + $result = $this->sql_query($query, $cache_ttl); + + // Seek by $offset rows + if ($offset) + { + $this->sql_rowseek($offset, $result); + } + + return $result; + } + + /** + * Return number of affected rows + */ + function sql_affectedrows() + { + return ($this->db_connect_id) ? @odbc_num_rows($this->query_result) : false; + } + + /** + * Fetch current row + * @note number of bytes returned depends on odbc.defaultlrl php.ini setting. If it is limited to 4K for example only 4K of data is returned max. + */ + function sql_fetchrow($query_id = false, $debug = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_fetchrow($query_id); + } + + return ($query_id !== false) ? @odbc_fetch_array($query_id) : false; + } + + /** + * Get last inserted id after insert statement + */ + function sql_nextid() + { + $result_id = @odbc_exec($this->db_connect_id, 'SELECT @@IDENTITY'); + + if ($result_id) + { + if (@odbc_fetch_array($result_id)) + { + $id = @odbc_result($result_id, 1); + @odbc_free_result($result_id); + return $id; + } + @odbc_free_result($result_id); + } + + return false; + } + + /** + * Free sql result + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_freeresult($query_id); + } + + if (isset($this->open_queries[(int) $query_id])) + { + unset($this->open_queries[(int) $query_id]); + return @odbc_free_result($query_id); + } + + return false; + } + + /** + * Escape string used in sql query + */ + function sql_escape($msg) + { + return str_replace(array("'", "\0"), array("''", ''), $msg); + } + + /** + * {@inheritDoc} + */ + function sql_lower_text($column_name) + { + return "LOWER(SUBSTRING($column_name, 1, DATALENGTH($column_name)))"; + } + + /** + * Build LIKE expression + * @access private + */ + function _sql_like_expression($expression) + { + return $expression . " ESCAPE '\\'"; + } + + /** + * Build db-specific query data + * @access private + */ + function _sql_custom_build($stage, $data) + { + return $data; + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + return array( + 'message' => @odbc_errormsg(), + 'code' => @odbc_error() + ); + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + return @odbc_close($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + switch ($mode) + { + case 'start': + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @odbc_exec($this->db_connect_id, $query); + while ($void = @odbc_fetch_array($result)) + { + // Take the time spent on parsing rows into account + } + @odbc_free_result($result); + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } +} diff --git a/phpBB/includes/db/driver/mssqlnative.php b/phpBB/includes/db/driver/mssqlnative.php new file mode 100644 index 0000000000..99b9d7975a --- /dev/null +++ b/phpBB/includes/db/driver/mssqlnative.php @@ -0,0 +1,642 @@ +m_cursor = 0; + $this->m_rows = array(); + $this->m_num_fields = sqlsrv_num_fields($queryresult); + $this->m_field_meta = sqlsrv_field_metadata($queryresult); + + while ($row = sqlsrv_fetch_array($queryresult, SQLSRV_FETCH_ASSOC)) + { + if ($row !== null) + { + foreach($row as $k => $v) + { + if (is_object($v) && method_exists($v, 'format')) + { + $row[$k] = $v->format("Y-m-d\TH:i:s\Z"); + } + } + $this->m_rows[] = $row;//read results into memory, cursors are not supported + } + } + + $this->m_row_count = sizeof($this->m_rows); + } + + private function array_to_obj($array, &$obj) + { + foreach ($array as $key => $value) + { + if (is_array($value)) + { + $obj->$key = new stdClass(); + array_to_obj($value, $obj->$key); + } + else + { + $obj->$key = $value; + } + } + return $obj; + } + + public function fetch($mode = SQLSRV_FETCH_BOTH, $object_class = 'stdClass') + { + if ($this->m_cursor >= $this->m_row_count || $this->m_row_count == 0) + { + return false; + } + + $ret = false; + $arr_num = array(); + + if ($mode == SQLSRV_FETCH_NUMERIC || $mode == SQLSRV_FETCH_BOTH) + { + foreach($this->m_rows[$this->m_cursor] as $key => $value) + { + $arr_num[] = $value; + } + } + + switch ($mode) + { + case SQLSRV_FETCH_ASSOC: + $ret = $this->m_rows[$this->m_cursor]; + break; + case SQLSRV_FETCH_NUMERIC: + $ret = $arr_num; + break; + case 'OBJECT': + $ret = $this->array_to_obj($this->m_rows[$this->m_cursor], $o = new $object_class); + break; + case SQLSRV_FETCH_BOTH: + default: + $ret = $this->m_rows[$this->m_cursor] + $arr_num; + break; + } + $this->m_cursor++; + return $ret; + } + + public function get($pos, $fld) + { + return $this->m_rows[$pos][$fld]; + } + + public function num_rows() + { + return $this->m_row_count; + } + + public function seek($iRow) + { + $this->m_cursor = min($iRow, $this->m_row_count); + } + + public function num_fields() + { + return $this->m_num_fields; + } + + public function field_name($nr) + { + $arr_keys = array_keys($this->m_rows[0]); + return $arr_keys[$nr]; + } + + public function field_type($nr) + { + $i = 0; + $int_type = -1; + $str_type = ''; + + foreach ($this->m_field_meta as $meta) + { + if ($nr == $i) + { + $int_type = $meta['Type']; + break; + } + $i++; + } + + //http://msdn.microsoft.com/en-us/library/cc296183.aspx contains type table + switch ($int_type) + { + case SQLSRV_SQLTYPE_BIGINT: $str_type = 'bigint'; break; + case SQLSRV_SQLTYPE_BINARY: $str_type = 'binary'; break; + case SQLSRV_SQLTYPE_BIT: $str_type = 'bit'; break; + case SQLSRV_SQLTYPE_CHAR: $str_type = 'char'; break; + case SQLSRV_SQLTYPE_DATETIME: $str_type = 'datetime'; break; + case SQLSRV_SQLTYPE_DECIMAL/*($precision, $scale)*/: $str_type = 'decimal'; break; + case SQLSRV_SQLTYPE_FLOAT: $str_type = 'float'; break; + case SQLSRV_SQLTYPE_IMAGE: $str_type = 'image'; break; + case SQLSRV_SQLTYPE_INT: $str_type = 'int'; break; + case SQLSRV_SQLTYPE_MONEY: $str_type = 'money'; break; + case SQLSRV_SQLTYPE_NCHAR/*($charCount)*/: $str_type = 'nchar'; break; + case SQLSRV_SQLTYPE_NUMERIC/*($precision, $scale)*/: $str_type = 'numeric'; break; + case SQLSRV_SQLTYPE_NVARCHAR/*($charCount)*/: $str_type = 'nvarchar'; break; + case SQLSRV_SQLTYPE_NTEXT: $str_type = 'ntext'; break; + case SQLSRV_SQLTYPE_REAL: $str_type = 'real'; break; + case SQLSRV_SQLTYPE_SMALLDATETIME: $str_type = 'smalldatetime'; break; + case SQLSRV_SQLTYPE_SMALLINT: $str_type = 'smallint'; break; + case SQLSRV_SQLTYPE_SMALLMONEY: $str_type = 'smallmoney'; break; + case SQLSRV_SQLTYPE_TEXT: $str_type = 'text'; break; + case SQLSRV_SQLTYPE_TIMESTAMP: $str_type = 'timestamp'; break; + case SQLSRV_SQLTYPE_TINYINT: $str_type = 'tinyint'; break; + case SQLSRV_SQLTYPE_UNIQUEIDENTIFIER: $str_type = 'uniqueidentifier'; break; + case SQLSRV_SQLTYPE_UDT: $str_type = 'UDT'; break; + case SQLSRV_SQLTYPE_VARBINARY/*($byteCount)*/: $str_type = 'varbinary'; break; + case SQLSRV_SQLTYPE_VARCHAR/*($charCount)*/: $str_type = 'varchar'; break; + case SQLSRV_SQLTYPE_XML: $str_type = 'xml'; break; + default: $str_type = $int_type; + } + return $str_type; + } + + public function free() + { + unset($this->m_rows); + return; + } +} + +/** +* @package dbal +*/ +class phpbb_db_driver_mssqlnative extends phpbb_db_driver +{ + var $m_insert_id = NULL; + var $last_query_text = ''; + var $query_options = array(); + + /** + * Connect to server + */ + function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false) + { + # Test for driver support, to avoid suppressed fatal error + if (!function_exists('sqlsrv_connect')) + { + trigger_error('Native MS SQL Server driver for PHP is missing or needs to be updated. Version 1.1 or later is required to install phpBB3. You can download the driver from: http://www.microsoft.com/sqlserver/2005/en/us/PHP-Driver.aspx\n', E_USER_ERROR); + } + + //set up connection variables + $this->persistency = $persistency; + $this->user = $sqluser; + $this->dbname = $database; + $port_delimiter = (defined('PHP_OS') && substr(PHP_OS, 0, 3) === 'WIN') ? ',' : ':'; + $this->server = $sqlserver . (($port) ? $port_delimiter . $port : ''); + + //connect to database + error_reporting(E_ALL); + $this->db_connect_id = sqlsrv_connect($this->server, array( + 'Database' => $this->dbname, + 'UID' => $this->user, + 'PWD' => $sqlpassword + )); + + return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); + } + + /** + * Version information about used database + * @param bool $raw if true, only return the fetched sql_server_version + * @param bool $use_cache If true, it is safe to retrieve the value from the cache + * @return string sql server version + */ + function sql_server_info($raw = false, $use_cache = true) + { + global $cache; + + if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mssql_version')) === false) + { + $arr_server_info = sqlsrv_server_info($this->db_connect_id); + $this->sql_server_version = $arr_server_info['SQLServerVersion']; + + if (!empty($cache) && $use_cache) + { + $cache->put('mssql_version', $this->sql_server_version); + } + } + + if ($raw) + { + return $this->sql_server_version; + } + + return ($this->sql_server_version) ? 'MSSQL
' . $this->sql_server_version : 'MSSQL'; + } + + /** + * {@inheritDoc} + */ + public function sql_concatenate($expr1, $expr2) + { + return $expr1 . ' + ' . $expr2; + } + + /** + * {@inheritDoc} + */ + function sql_buffer_nested_transactions() + { + return true; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return sqlsrv_begin_transaction($this->db_connect_id); + break; + + case 'commit': + return sqlsrv_commit($this->db_connect_id); + break; + + case 'rollback': + return sqlsrv_rollback($this->db_connect_id); + break; + } + return true; + } + + /** + * Base query method + * + * @param string $query Contains the SQL query which shall be executed + * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache + * @return mixed When casted to bool the returned value returns true on success and false on failure + * + * @access public + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('start', $query); + } + + $this->last_query_text = $query; + $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + if (($this->query_result = @sqlsrv_query($this->db_connect_id, $query, array(), $this->query_options)) === false) + { + $this->sql_error($query); + } + // reset options for next query + $this->query_options = array(); + + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('stop', $query); + } + + if ($cache_ttl && method_exists($cache, 'sql_save')) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $cache->sql_save($query, $this->query_result, $cache_ttl); + } + else if (strpos($query, 'SELECT') === 0 && $this->query_result) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG_EXTRA')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + return $this->query_result; + } + + /** + * Build LIMIT query + */ + function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + $this->query_result = false; + + // total == 0 means all results - not zero results + if ($offset == 0 && $total !== 0) + { + if (strpos($query, "SELECT") === false) + { + $query = "TOP {$total} " . $query; + } + else + { + $query = preg_replace('/SELECT(\s*DISTINCT)?/Dsi', 'SELECT$1 TOP '.$total, $query); + } + } + else if ($offset > 0) + { + $query = preg_replace('/SELECT(\s*DISTINCT)?/Dsi', 'SELECT$1 TOP(10000000) ', $query); + $query = 'SELECT * + FROM (SELECT sub2.*, ROW_NUMBER() OVER(ORDER BY sub2.line2) AS line3 + FROM (SELECT 1 AS line2, sub1.* FROM (' . $query . ') AS sub1) as sub2) AS sub3'; + + if ($total > 0) + { + $query .= ' WHERE line3 BETWEEN ' . ($offset+1) . ' AND ' . ($offset + $total); + } + else + { + $query .= ' WHERE line3 > ' . $offset; + } + } + + $result = $this->sql_query($query, $cache_ttl); + + return $result; + } + + /** + * Return number of affected rows + */ + function sql_affectedrows() + { + return (!empty($this->query_result)) ? @sqlsrv_rows_affected($this->query_result) : false; + } + + /** + * Fetch current row + */ + function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_fetchrow($query_id); + } + + if ($query_id === false) + { + return false; + } + + $row = @sqlsrv_fetch_array($query_id, SQLSRV_FETCH_ASSOC); + + if ($row) + { + foreach ($row as $key => $value) + { + $row[$key] = ($value === ' ' || $value === NULL) ? '' : $value; + } + + // remove helper values from LIMIT queries + if (isset($row['line2'])) + { + unset($row['line2'], $row['line3']); + } + } + return (sizeof($row)) ? $row : false; + } + + /** + * Get last inserted id after insert statement + */ + function sql_nextid() + { + $result_id = @sqlsrv_query($this->db_connect_id, 'SELECT @@IDENTITY'); + + if ($result_id !== false) + { + $row = @sqlsrv_fetch_array($result_id); + $id = $row[0]; + @sqlsrv_free_stmt($result_id); + return $id; + } + else + { + return false; + } + } + + /** + * Free sql result + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_freeresult($query_id); + } + + if (isset($this->open_queries[$query_id])) + { + unset($this->open_queries[$query_id]); + return @sqlsrv_free_stmt($query_id); + } + return false; + } + + /** + * Escape string used in sql query + */ + function sql_escape($msg) + { + return str_replace(array("'", "\0"), array("''", ''), $msg); + } + + /** + * {@inheritDoc} + */ + function sql_lower_text($column_name) + { + return "LOWER(SUBSTRING($column_name, 1, DATALENGTH($column_name)))"; + } + + /** + * Build LIKE expression + * @access private + */ + function _sql_like_expression($expression) + { + return $expression . " ESCAPE '\\'"; + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + $errors = @sqlsrv_errors(SQLSRV_ERR_ERRORS); + $error_message = ''; + $code = 0; + + if ($errors != null) + { + foreach ($errors as $error) + { + $error_message .= "SQLSTATE: ".$error[ 'SQLSTATE']."\n"; + $error_message .= "code: ".$error[ 'code']."\n"; + $code = $error['code']; + $error_message .= "message: ".$error[ 'message']."\n"; + } + $this->last_error_result = $error_message; + $error = $this->last_error_result; + } + else + { + $error = (isset($this->last_error_result) && $this->last_error_result) ? $this->last_error_result : array(); + } + + return array( + 'message' => $error, + 'code' => $code, + ); + } + + /** + * Build db-specific query data + * @access private + */ + function _sql_custom_build($stage, $data) + { + return $data; + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + return @sqlsrv_close($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + switch ($mode) + { + case 'start': + $html_table = false; + @sqlsrv_query($this->db_connect_id, 'SET SHOWPLAN_TEXT ON;'); + if ($result = @sqlsrv_query($this->db_connect_id, $query)) + { + @sqlsrv_next_result($result); + while ($row = @sqlsrv_fetch_array($result)) + { + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + } + @sqlsrv_query($this->db_connect_id, 'SET SHOWPLAN_TEXT OFF;'); + @sqlsrv_free_stmt($result); + + if ($html_table) + { + $this->html_hold .= ''; + } + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @sqlsrv_query($this->db_connect_id, $query); + while ($void = @sqlsrv_fetch_array($result)) + { + // Take the time spent on parsing rows into account + } + @sqlsrv_free_stmt($result); + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } + + /** + * Utility method used to retrieve number of rows + * Emulates mysql_num_rows + * Used in acp_database.php -> write_data_mssqlnative() + * Requires a static or keyset cursor to be definde via + * mssqlnative_set_query_options() + */ + function mssqlnative_num_rows($res) + { + if ($res !== false) + { + return sqlsrv_num_rows($res); + } + else + { + return false; + } + } + + /** + * Allows setting mssqlnative specific query options passed to sqlsrv_query as 4th parameter. + */ + function mssqlnative_set_query_options($options) + { + $this->query_options = $options; + } +} diff --git a/phpBB/includes/db/driver/mysql.php b/phpBB/includes/db/driver/mysql.php new file mode 100644 index 0000000000..987691341a --- /dev/null +++ b/phpBB/includes/db/driver/mysql.php @@ -0,0 +1,565 @@ +persistency = $persistency; + $this->user = $sqluser; + $this->server = $sqlserver . (($port) ? ':' . $port : ''); + $this->dbname = $database; + + $this->sql_layer = 'mysql4'; + + $this->db_connect_id = ($this->persistency) ? @mysql_pconnect($this->server, $this->user, $sqlpassword) : @mysql_connect($this->server, $this->user, $sqlpassword, $new_link); + + if ($this->db_connect_id && $this->dbname != '') + { + if (@mysql_select_db($this->dbname, $this->db_connect_id)) + { + // Determine what version we are using and if it natively supports UNICODE + if (version_compare($this->sql_server_info(true), '4.1.0', '>=')) + { + @mysql_query("SET NAMES 'utf8'", $this->db_connect_id); + + // enforce strict mode on databases that support it + if (version_compare($this->sql_server_info(true), '5.0.2', '>=')) + { + $result = @mysql_query('SELECT @@session.sql_mode AS sql_mode', $this->db_connect_id); + $row = @mysql_fetch_assoc($result); + @mysql_free_result($result); + $modes = array_map('trim', explode(',', $row['sql_mode'])); + + // TRADITIONAL includes STRICT_ALL_TABLES and STRICT_TRANS_TABLES + if (!in_array('TRADITIONAL', $modes)) + { + if (!in_array('STRICT_ALL_TABLES', $modes)) + { + $modes[] = 'STRICT_ALL_TABLES'; + } + + if (!in_array('STRICT_TRANS_TABLES', $modes)) + { + $modes[] = 'STRICT_TRANS_TABLES'; + } + } + + $mode = implode(',', $modes); + @mysql_query("SET SESSION sql_mode='{$mode}'", $this->db_connect_id); + } + } + else if (version_compare($this->sql_server_info(true), '4.0.0', '<')) + { + $this->sql_layer = 'mysql'; + } + + return $this->db_connect_id; + } + } + + return $this->sql_error(''); + } + + /** + * Version information about used database + * @param bool $raw if true, only return the fetched sql_server_version + * @param bool $use_cache If true, it is safe to retrieve the value from the cache + * @return string sql server version + */ + function sql_server_info($raw = false, $use_cache = true) + { + global $cache; + + if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mysql_version')) === false) + { + $result = @mysql_query('SELECT VERSION() AS version', $this->db_connect_id); + $row = @mysql_fetch_assoc($result); + @mysql_free_result($result); + + $this->sql_server_version = $row['version']; + + if (!empty($cache) && $use_cache) + { + $cache->put('mysql_version', $this->sql_server_version); + } + } + + return ($raw) ? $this->sql_server_version : 'MySQL ' . $this->sql_server_version; + } + + /** + * {@inheritDoc} + */ + public function sql_concatenate($expr1, $expr2) + { + return 'CONCAT(' . $expr1 . ', ' . $expr2 . ')'; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return @mysql_query('BEGIN', $this->db_connect_id); + break; + + case 'commit': + return @mysql_query('COMMIT', $this->db_connect_id); + break; + + case 'rollback': + return @mysql_query('ROLLBACK', $this->db_connect_id); + break; + } + + return true; + } + + /** + * Base query method + * + * @param string $query Contains the SQL query which shall be executed + * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache + * @return mixed When casted to bool the returned value returns true on success and false on failure + * + * @access public + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('start', $query); + } + + $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + if (($this->query_result = @mysql_query($query, $this->db_connect_id)) === false) + { + $this->sql_error($query); + } + + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('stop', $query); + } + + if ($cache_ttl && method_exists($cache, 'sql_save')) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $cache->sql_save($query, $this->query_result, $cache_ttl); + } + else if (strpos($query, 'SELECT') === 0 && $this->query_result) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG_EXTRA')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return $this->query_result; + } + + /** + * Build LIMIT query + */ + function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + $this->query_result = false; + + // if $total is set to 0 we do not want to limit the number of rows + if ($total == 0) + { + // Having a value of -1 was always a bug + $total = '18446744073709551615'; + } + + $query .= "\n LIMIT " . ((!empty($offset)) ? $offset . ', ' . $total : $total); + + return $this->sql_query($query, $cache_ttl); + } + + /** + * Return number of affected rows + */ + function sql_affectedrows() + { + return ($this->db_connect_id) ? @mysql_affected_rows($this->db_connect_id) : false; + } + + /** + * Fetch current row + */ + function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_fetchrow($query_id); + } + + return ($query_id !== false) ? @mysql_fetch_assoc($query_id) : false; + } + + /** + * Seek to given row number + * rownum is zero-based + */ + function sql_rowseek($rownum, &$query_id) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_rowseek($rownum, $query_id); + } + + return ($query_id !== false) ? @mysql_data_seek($query_id, $rownum) : false; + } + + /** + * Get last inserted id after insert statement + */ + function sql_nextid() + { + return ($this->db_connect_id) ? @mysql_insert_id($this->db_connect_id) : false; + } + + /** + * Free sql result + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_freeresult($query_id); + } + + if (isset($this->open_queries[(int) $query_id])) + { + unset($this->open_queries[(int) $query_id]); + return @mysql_free_result($query_id); + } + + return false; + } + + /** + * Escape string used in sql query + */ + function sql_escape($msg) + { + if (!$this->db_connect_id) + { + return @mysql_real_escape_string($msg); + } + + return @mysql_real_escape_string($msg, $this->db_connect_id); + } + + /** + * Gets the estimated number of rows in a specified table. + * + * @param string $table_name Table name + * + * @return string Number of rows in $table_name. + * Prefixed with ~ if estimated (otherwise exact). + * + * @access public + */ + function get_estimated_row_count($table_name) + { + $table_status = $this->get_table_status($table_name); + + if (isset($table_status['Engine'])) + { + if ($table_status['Engine'] === 'MyISAM') + { + return $table_status['Rows']; + } + else if ($table_status['Engine'] === 'InnoDB' && $table_status['Rows'] > 100000) + { + return '~' . $table_status['Rows']; + } + } + + return parent::get_row_count($table_name); + } + + /** + * Gets the exact number of rows in a specified table. + * + * @param string $table_name Table name + * + * @return string Exact number of rows in $table_name. + * + * @access public + */ + function get_row_count($table_name) + { + $table_status = $this->get_table_status($table_name); + + if (isset($table_status['Engine']) && $table_status['Engine'] === 'MyISAM') + { + return $table_status['Rows']; + } + + return parent::get_row_count($table_name); + } + + /** + * Gets some information about the specified table. + * + * @param string $table_name Table name + * + * @return array + * + * @access protected + */ + function get_table_status($table_name) + { + $sql = "SHOW TABLE STATUS + LIKE '" . $this->sql_escape($table_name) . "'"; + $result = $this->sql_query($sql); + $table_status = $this->sql_fetchrow($result); + $this->sql_freeresult($result); + + return $table_status; + } + + /** + * Build LIKE expression + * @access private + */ + function _sql_like_expression($expression) + { + return $expression; + } + + /** + * Build db-specific query data + * @access private + */ + function _sql_custom_build($stage, $data) + { + switch ($stage) + { + case 'FROM': + $data = '(' . $data . ')'; + break; + } + + return $data; + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + if (!$this->db_connect_id) + { + return array( + 'message' => @mysql_error(), + 'code' => @mysql_errno() + ); + } + + return array( + 'message' => @mysql_error($this->db_connect_id), + 'code' => @mysql_errno($this->db_connect_id) + ); + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + return @mysql_close($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + static $test_prof; + + // current detection method, might just switch to see the existance of INFORMATION_SCHEMA.PROFILING + if ($test_prof === null) + { + $test_prof = false; + if (version_compare($this->sql_server_info(true), '5.0.37', '>=') && version_compare($this->sql_server_info(true), '5.1', '<')) + { + $test_prof = true; + } + } + + switch ($mode) + { + case 'start': + + $explain_query = $query; + if (preg_match('/UPDATE ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + else if (preg_match('/DELETE FROM ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + + if (preg_match('/^SELECT/', $explain_query)) + { + $html_table = false; + + // begin profiling + if ($test_prof) + { + @mysql_query('SET profiling = 1;', $this->db_connect_id); + } + + if ($result = @mysql_query("EXPLAIN $explain_query", $this->db_connect_id)) + { + while ($row = @mysql_fetch_assoc($result)) + { + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + } + @mysql_free_result($result); + + if ($html_table) + { + $this->html_hold .= ''; + } + + if ($test_prof) + { + $html_table = false; + + // get the last profile + if ($result = @mysql_query('SHOW PROFILE ALL;', $this->db_connect_id)) + { + $this->html_hold .= '
'; + while ($row = @mysql_fetch_assoc($result)) + { + // make HTML safe + if (!empty($row['Source_function'])) + { + $row['Source_function'] = str_replace(array('<', '>'), array('<', '>'), $row['Source_function']); + } + + // remove unsupported features + foreach ($row as $key => $val) + { + if ($val === null) + { + unset($row[$key]); + } + } + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + } + @mysql_free_result($result); + + if ($html_table) + { + $this->html_hold .= ''; + } + + @mysql_query('SET profiling = 0;', $this->db_connect_id); + } + } + + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @mysql_query($query, $this->db_connect_id); + while ($void = @mysql_fetch_assoc($result)) + { + // Take the time spent on parsing rows into account + } + @mysql_free_result($result); + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } +} diff --git a/phpBB/includes/db/driver/mysqli.php b/phpBB/includes/db/driver/mysqli.php new file mode 100644 index 0000000000..c473c7fe99 --- /dev/null +++ b/phpBB/includes/db/driver/mysqli.php @@ -0,0 +1,566 @@ +persistency = (version_compare(PHP_VERSION, '5.3.0', '>=')) ? $persistency : false; + $this->user = $sqluser; + + // If persistent connection, set dbhost to localhost when empty and prepend it with 'p:' prefix + $this->server = ($this->persistency) ? 'p:' . (($sqlserver) ? $sqlserver : 'localhost') : $sqlserver; + + $this->dbname = $database; + $port = (!$port) ? NULL : $port; + + // If port is set and it is not numeric, most likely mysqli socket is set. + // Try to map it to the $socket parameter. + $socket = NULL; + if ($port) + { + if (is_numeric($port)) + { + $port = (int) $port; + } + else + { + $socket = $port; + $port = NULL; + } + } + + $this->db_connect_id = @mysqli_connect($this->server, $this->user, $sqlpassword, $this->dbname, $port, $socket); + + if ($this->db_connect_id && $this->dbname != '') + { + @mysqli_query($this->db_connect_id, "SET NAMES 'utf8'"); + + // enforce strict mode on databases that support it + if (version_compare($this->sql_server_info(true), '5.0.2', '>=')) + { + $result = @mysqli_query($this->db_connect_id, 'SELECT @@session.sql_mode AS sql_mode'); + $row = @mysqli_fetch_assoc($result); + @mysqli_free_result($result); + + $modes = array_map('trim', explode(',', $row['sql_mode'])); + + // TRADITIONAL includes STRICT_ALL_TABLES and STRICT_TRANS_TABLES + if (!in_array('TRADITIONAL', $modes)) + { + if (!in_array('STRICT_ALL_TABLES', $modes)) + { + $modes[] = 'STRICT_ALL_TABLES'; + } + + if (!in_array('STRICT_TRANS_TABLES', $modes)) + { + $modes[] = 'STRICT_TRANS_TABLES'; + } + } + + $mode = implode(',', $modes); + @mysqli_query($this->db_connect_id, "SET SESSION sql_mode='{$mode}'"); + } + return $this->db_connect_id; + } + + return $this->sql_error(''); + } + + /** + * Version information about used database + * @param bool $use_cache If true, it is safe to retrieve the value from the cache + * @return string sql server version + */ + function sql_server_info($raw = false, $use_cache = true) + { + global $cache; + + if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mysqli_version')) === false) + { + $result = @mysqli_query($this->db_connect_id, 'SELECT VERSION() AS version'); + $row = @mysqli_fetch_assoc($result); + @mysqli_free_result($result); + + $this->sql_server_version = $row['version']; + + if (!empty($cache) && $use_cache) + { + $cache->put('mysqli_version', $this->sql_server_version); + } + } + + return ($raw) ? $this->sql_server_version : 'MySQL(i) ' . $this->sql_server_version; + } + + /** + * {@inheritDoc} + */ + public function sql_concatenate($expr1, $expr2) + { + return 'CONCAT(' . $expr1 . ', ' . $expr2 . ')'; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return @mysqli_autocommit($this->db_connect_id, false); + break; + + case 'commit': + $result = @mysqli_commit($this->db_connect_id); + @mysqli_autocommit($this->db_connect_id, true); + return $result; + break; + + case 'rollback': + $result = @mysqli_rollback($this->db_connect_id); + @mysqli_autocommit($this->db_connect_id, true); + return $result; + break; + } + + return true; + } + + /** + * Base query method + * + * @param string $query Contains the SQL query which shall be executed + * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache + * @return mixed When casted to bool the returned value returns true on success and false on failure + * + * @access public + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('start', $query); + } + + $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + if (($this->query_result = @mysqli_query($this->db_connect_id, $query)) === false) + { + $this->sql_error($query); + } + + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('stop', $query); + } + + if ($cache_ttl && method_exists($cache, 'sql_save')) + { + $cache->sql_save($query, $this->query_result, $cache_ttl); + } + } + else if (defined('DEBUG_EXTRA')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return $this->query_result; + } + + /** + * Build LIMIT query + */ + function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + $this->query_result = false; + + // if $total is set to 0 we do not want to limit the number of rows + if ($total == 0) + { + // MySQL 4.1+ no longer supports -1 in limit queries + $total = '18446744073709551615'; + } + + $query .= "\n LIMIT " . ((!empty($offset)) ? $offset . ', ' . $total : $total); + + return $this->sql_query($query, $cache_ttl); + } + + /** + * Return number of affected rows + */ + function sql_affectedrows() + { + return ($this->db_connect_id) ? @mysqli_affected_rows($this->db_connect_id) : false; + } + + /** + * Fetch current row + */ + function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (!is_object($query_id) && isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_fetchrow($query_id); + } + + if ($query_id !== false) + { + $result = @mysqli_fetch_assoc($query_id); + return $result !== null ? $result : false; + } + + return false; + } + + /** + * Seek to given row number + * rownum is zero-based + */ + function sql_rowseek($rownum, &$query_id) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (!is_object($query_id) && isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_rowseek($rownum, $query_id); + } + + return ($query_id !== false) ? @mysqli_data_seek($query_id, $rownum) : false; + } + + /** + * Get last inserted id after insert statement + */ + function sql_nextid() + { + return ($this->db_connect_id) ? @mysqli_insert_id($this->db_connect_id) : false; + } + + /** + * Free sql result + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (!is_object($query_id) && isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_freeresult($query_id); + } + + return @mysqli_free_result($query_id); + } + + /** + * Escape string used in sql query + */ + function sql_escape($msg) + { + return @mysqli_real_escape_string($this->db_connect_id, $msg); + } + + /** + * Gets the estimated number of rows in a specified table. + * + * @param string $table_name Table name + * + * @return string Number of rows in $table_name. + * Prefixed with ~ if estimated (otherwise exact). + * + * @access public + */ + function get_estimated_row_count($table_name) + { + $table_status = $this->get_table_status($table_name); + + if (isset($table_status['Engine'])) + { + if ($table_status['Engine'] === 'MyISAM') + { + return $table_status['Rows']; + } + else if ($table_status['Engine'] === 'InnoDB' && $table_status['Rows'] > 100000) + { + return '~' . $table_status['Rows']; + } + } + + return parent::get_row_count($table_name); + } + + /** + * Gets the exact number of rows in a specified table. + * + * @param string $table_name Table name + * + * @return string Exact number of rows in $table_name. + * + * @access public + */ + function get_row_count($table_name) + { + $table_status = $this->get_table_status($table_name); + + if (isset($table_status['Engine']) && $table_status['Engine'] === 'MyISAM') + { + return $table_status['Rows']; + } + + return parent::get_row_count($table_name); + } + + /** + * Gets some information about the specified table. + * + * @param string $table_name Table name + * + * @return array + * + * @access protected + */ + function get_table_status($table_name) + { + $sql = "SHOW TABLE STATUS + LIKE '" . $this->sql_escape($table_name) . "'"; + $result = $this->sql_query($sql); + $table_status = $this->sql_fetchrow($result); + $this->sql_freeresult($result); + + return $table_status; + } + + /** + * Build LIKE expression + * @access private + */ + function _sql_like_expression($expression) + { + return $expression; + } + + /** + * Build db-specific query data + * @access private + */ + function _sql_custom_build($stage, $data) + { + switch ($stage) + { + case 'FROM': + $data = '(' . $data . ')'; + break; + } + + return $data; + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + if (!$this->db_connect_id) + { + return array( + 'message' => @mysqli_connect_error(), + 'code' => @mysqli_connect_errno() + ); + } + + return array( + 'message' => @mysqli_error($this->db_connect_id), + 'code' => @mysqli_errno($this->db_connect_id) + ); + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + return @mysqli_close($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + static $test_prof; + + // current detection method, might just switch to see the existance of INFORMATION_SCHEMA.PROFILING + if ($test_prof === null) + { + $test_prof = false; + if (strpos(mysqli_get_server_info($this->db_connect_id), 'community') !== false) + { + $ver = mysqli_get_server_version($this->db_connect_id); + if ($ver >= 50037 && $ver < 50100) + { + $test_prof = true; + } + } + } + + switch ($mode) + { + case 'start': + + $explain_query = $query; + if (preg_match('/UPDATE ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + else if (preg_match('/DELETE FROM ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + + if (preg_match('/^SELECT/', $explain_query)) + { + $html_table = false; + + // begin profiling + if ($test_prof) + { + @mysqli_query($this->db_connect_id, 'SET profiling = 1;'); + } + + if ($result = @mysqli_query($this->db_connect_id, "EXPLAIN $explain_query")) + { + while ($row = @mysqli_fetch_assoc($result)) + { + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + } + @mysqli_free_result($result); + + if ($html_table) + { + $this->html_hold .= ''; + } + + if ($test_prof) + { + $html_table = false; + + // get the last profile + if ($result = @mysqli_query($this->db_connect_id, 'SHOW PROFILE ALL;')) + { + $this->html_hold .= '
'; + while ($row = @mysqli_fetch_assoc($result)) + { + // make HTML safe + if (!empty($row['Source_function'])) + { + $row['Source_function'] = str_replace(array('<', '>'), array('<', '>'), $row['Source_function']); + } + + // remove unsupported features + foreach ($row as $key => $val) + { + if ($val === null) + { + unset($row[$key]); + } + } + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + } + @mysqli_free_result($result); + + if ($html_table) + { + $this->html_hold .= ''; + } + + @mysqli_query($this->db_connect_id, 'SET profiling = 0;'); + } + } + + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @mysqli_query($this->db_connect_id, $query); + while ($void = @mysqli_fetch_assoc($result)) + { + // Take the time spent on parsing rows into account + } + @mysqli_free_result($result); + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } +} diff --git a/phpBB/includes/db/driver/oracle.php b/phpBB/includes/db/driver/oracle.php new file mode 100644 index 0000000000..25803e57bd --- /dev/null +++ b/phpBB/includes/db/driver/oracle.php @@ -0,0 +1,766 @@ +persistency = $persistency; + $this->user = $sqluser; + $this->server = $sqlserver . (($port) ? ':' . $port : ''); + $this->dbname = $database; + + $connect = $database; + + // support for "easy connect naming" + if ($sqlserver !== '' && $sqlserver !== '/') + { + if (substr($sqlserver, -1, 1) == '/') + { + $sqlserver == substr($sqlserver, 0, -1); + } + $connect = $sqlserver . (($port) ? ':' . $port : '') . '/' . $database; + } + + $this->db_connect_id = ($new_link) ? @ocinlogon($this->user, $sqlpassword, $connect, 'UTF8') : (($this->persistency) ? @ociplogon($this->user, $sqlpassword, $connect, 'UTF8') : @ocilogon($this->user, $sqlpassword, $connect, 'UTF8')); + + return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); + } + + /** + * Version information about used database + * @param bool $raw if true, only return the fetched sql_server_version + * @param bool $use_cache forced to false for Oracle + * @return string sql server version + */ + function sql_server_info($raw = false, $use_cache = true) + { + /** + * force $use_cache false. I didn't research why the caching code below is commented out + * but I assume its because the Oracle extension provides a direct method to access it + * without a query. + */ + + $use_cache = false; +/* + global $cache; + + if (empty($cache) || ($this->sql_server_version = $cache->get('oracle_version')) === false) + { + $result = @ociparse($this->db_connect_id, 'SELECT * FROM v$version WHERE banner LIKE \'Oracle%\''); + @ociexecute($result, OCI_DEFAULT); + @ocicommit($this->db_connect_id); + + $row = array(); + @ocifetchinto($result, $row, OCI_ASSOC + OCI_RETURN_NULLS); + @ocifreestatement($result); + $this->sql_server_version = trim($row['BANNER']); + + $cache->put('oracle_version', $this->sql_server_version); + } +*/ + $this->sql_server_version = @ociserverversion($this->db_connect_id); + + return $this->sql_server_version; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return true; + break; + + case 'commit': + return @ocicommit($this->db_connect_id); + break; + + case 'rollback': + return @ocirollback($this->db_connect_id); + break; + } + + return true; + } + + /** + * Oracle specific code to handle the fact that it does not compare columns properly + * @access private + */ + function _rewrite_col_compare($args) + { + if (sizeof($args) == 4) + { + if ($args[2] == '=') + { + return '(' . $args[0] . ' OR (' . $args[1] . ' is NULL AND ' . $args[3] . ' is NULL))'; + } + else if ($args[2] == '<>') + { + // really just a fancy way of saying foo <> bar or (foo is NULL XOR bar is NULL) but SQL has no XOR :P + return '(' . $args[0] . ' OR ((' . $args[1] . ' is NULL AND ' . $args[3] . ' is NOT NULL) OR (' . $args[1] . ' is NOT NULL AND ' . $args[3] . ' is NULL)))'; + } + } + else + { + return $this->_rewrite_where($args[0]); + } + } + + /** + * Oracle specific code to handle it's lack of sanity + * @access private + */ + function _rewrite_where($where_clause) + { + preg_match_all('/\s*(AND|OR)?\s*([\w_.()]++)\s*(?:(=|<[=>]?|>=?|LIKE)\s*((?>\'(?>[^\']++|\'\')*+\'|[\d-.()]+))|((NOT )?IN\s*\((?>\'(?>[^\']++|\'\')*+\',? ?|[\d-.]+,? ?)*+\)))/', $where_clause, $result, PREG_SET_ORDER); + $out = ''; + foreach ($result as $val) + { + if (!isset($val[5])) + { + if ($val[4] !== "''") + { + $out .= $val[0]; + } + else + { + $out .= ' ' . $val[1] . ' ' . $val[2]; + if ($val[3] == '=') + { + $out .= ' is NULL'; + } + else if ($val[3] == '<>') + { + $out .= ' is NOT NULL'; + } + } + } + else + { + $in_clause = array(); + $sub_exp = substr($val[5], strpos($val[5], '(') + 1, -1); + $extra = false; + preg_match_all('/\'(?>[^\']++|\'\')*+\'|[\d-.]++/', $sub_exp, $sub_vals, PREG_PATTERN_ORDER); + $i = 0; + foreach ($sub_vals[0] as $sub_val) + { + // two things: + // 1) This determines if an empty string was in the IN clausing, making us turn it into a NULL comparison + // 2) This fixes the 1000 list limit that Oracle has (ORA-01795) + if ($sub_val !== "''") + { + $in_clause[(int) $i++/1000][] = $sub_val; + } + else + { + $extra = true; + } + } + if (!$extra && $i < 1000) + { + $out .= $val[0]; + } + else + { + $out .= ' ' . $val[1] . '('; + $in_array = array(); + + // constuct each IN() clause + foreach ($in_clause as $in_values) + { + $in_array[] = $val[2] . ' ' . (isset($val[6]) ? $val[6] : '') . 'IN(' . implode(', ', $in_values) . ')'; + } + + // Join the IN() clauses against a few ORs (IN is just a nicer OR anyway) + $out .= implode(' OR ', $in_array); + + // handle the empty string case + if ($extra) + { + $out .= ' OR ' . $val[2] . ' is ' . (isset($val[6]) ? $val[6] : '') . 'NULL'; + } + $out .= ')'; + + unset($in_array, $in_clause); + } + } + } + + return $out; + } + + /** + * Base query method + * + * @param string $query Contains the SQL query which shall be executed + * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache + * @return mixed When casted to bool the returned value returns true on success and false on failure + * + * @access public + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('start', $query); + } + + $this->last_query_text = $query; + $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + $in_transaction = false; + if (!$this->transaction) + { + $this->sql_transaction('begin'); + } + else + { + $in_transaction = true; + } + + $array = array(); + + // We overcome Oracle's 4000 char limit by binding vars + if (strlen($query) > 4000) + { + if (preg_match('/^(INSERT INTO[^(]++)\\(([^()]+)\\) VALUES[^(]++\\((.*?)\\)$/sU', $query, $regs)) + { + if (strlen($regs[3]) > 4000) + { + $cols = explode(', ', $regs[2]); + + preg_match_all('/\'(?:[^\']++|\'\')*+\'|[\d-.]+/', $regs[3], $vals, PREG_PATTERN_ORDER); + +/* The code inside this comment block breaks clob handling, but does allow the + database restore script to work. If you want to allow no posts longer than 4KB + and/or need the db restore script, uncomment this. + + + if (sizeof($cols) !== sizeof($vals)) + { + // Try to replace some common data we know is from our restore script or from other sources + $regs[3] = str_replace("'||chr(47)||'", '/', $regs[3]); + $_vals = explode(', ', $regs[3]); + + $vals = array(); + $is_in_val = false; + $i = 0; + $string = ''; + + foreach ($_vals as $value) + { + if (strpos($value, "'") === false && !$is_in_val) + { + $vals[$i++] = $value; + continue; + } + + if (substr($value, -1) === "'") + { + $vals[$i] = $string . (($is_in_val) ? ', ' : '') . $value; + $string = ''; + $is_in_val = false; + + if ($vals[$i][0] !== "'") + { + $vals[$i] = "''" . $vals[$i]; + } + $i++; + continue; + } + else + { + $string .= (($is_in_val) ? ', ' : '') . $value; + $is_in_val = true; + } + } + + if ($string) + { + // New value if cols != value + $vals[(sizeof($cols) !== sizeof($vals)) ? $i : $i - 1] .= $string; + } + + $vals = array(0 => $vals); + } +*/ + + $inserts = $vals[0]; + unset($vals); + + foreach ($inserts as $key => $value) + { + if (!empty($value) && $value[0] === "'" && strlen($value) > 4002) // check to see if this thing is greater than the max + 'x2 + { + $inserts[$key] = ':' . strtoupper($cols[$key]); + $array[$inserts[$key]] = str_replace("''", "'", substr($value, 1, -1)); + } + } + + $query = $regs[1] . '(' . $regs[2] . ') VALUES (' . implode(', ', $inserts) . ')'; + } + } + else if (preg_match_all('/^(UPDATE [\\w_]++\\s+SET )([\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+)(?:,\\s*[\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+))*+)\\s+(WHERE.*)$/s', $query, $data, PREG_SET_ORDER)) + { + if (strlen($data[0][2]) > 4000) + { + $update = $data[0][1]; + $where = $data[0][3]; + preg_match_all('/([\\w_]++)\\s*=\\s*(\'(?:[^\']++|\'\')*+\'|[\d-.]++)/', $data[0][2], $temp, PREG_SET_ORDER); + unset($data); + + $cols = array(); + foreach ($temp as $value) + { + if (!empty($value[2]) && $value[2][0] === "'" && strlen($value[2]) > 4002) // check to see if this thing is greater than the max + 'x2 + { + $cols[] = $value[1] . '=:' . strtoupper($value[1]); + $array[$value[1]] = str_replace("''", "'", substr($value[2], 1, -1)); + } + else + { + $cols[] = $value[1] . '=' . $value[2]; + } + } + + $query = $update . implode(', ', $cols) . ' ' . $where; + unset($cols); + } + } + } + + switch (substr($query, 0, 6)) + { + case 'DELETE': + if (preg_match('/^(DELETE FROM [\w_]++ WHERE)((?:\s*(?:AND|OR)?\s*[\w_]+\s*(?:(?:=|<>)\s*(?>\'(?>[^\']++|\'\')*+\'|[\d-.]+)|(?:NOT )?IN\s*\((?>\'(?>[^\']++|\'\')*+\',? ?|[\d-.]+,? ?)*+\)))*+)$/', $query, $regs)) + { + $query = $regs[1] . $this->_rewrite_where($regs[2]); + unset($regs); + } + break; + + case 'UPDATE': + if (preg_match('/^(UPDATE [\\w_]++\\s+SET [\\w_]+\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]++|:\w++)(?:, [\\w_]+\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]++|:\w++))*+\\s+WHERE)(.*)$/s', $query, $regs)) + { + $query = $regs[1] . $this->_rewrite_where($regs[2]); + unset($regs); + } + break; + + case 'SELECT': + $query = preg_replace_callback('/([\w_.]++)\s*(?:(=|<>)\s*(?>\'(?>[^\']++|\'\')*+\'|[\d-.]++|([\w_.]++))|(?:NOT )?IN\s*\((?>\'(?>[^\']++|\'\')*+\',? ?|[\d-.]++,? ?)*+\))/', array($this, '_rewrite_col_compare'), $query); + break; + } + + $this->query_result = @ociparse($this->db_connect_id, $query); + + foreach ($array as $key => $value) + { + @ocibindbyname($this->query_result, $key, $array[$key], -1); + } + + $success = @ociexecute($this->query_result, OCI_DEFAULT); + + if (!$success) + { + $this->sql_error($query); + $this->query_result = false; + } + else + { + if (!$in_transaction) + { + $this->sql_transaction('commit'); + } + } + + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('stop', $query); + } + + if ($cache_ttl && method_exists($cache, 'sql_save')) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $cache->sql_save($query, $this->query_result, $cache_ttl); + } + else if (strpos($query, 'SELECT') === 0 && $this->query_result) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG_EXTRA')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return $this->query_result; + } + + /** + * Build LIMIT query + */ + function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + $this->query_result = false; + + $query = 'SELECT * FROM (SELECT /*+ FIRST_ROWS */ rownum AS xrownum, a.* FROM (' . $query . ') a WHERE rownum <= ' . ($offset + $total) . ') WHERE xrownum >= ' . $offset; + + return $this->sql_query($query, $cache_ttl); + } + + /** + * Return number of affected rows + */ + function sql_affectedrows() + { + return ($this->query_result) ? @ocirowcount($this->query_result) : false; + } + + /** + * Fetch current row + */ + function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_fetchrow($query_id); + } + + if ($query_id !== false) + { + $row = array(); + $result = @ocifetchinto($query_id, $row, OCI_ASSOC + OCI_RETURN_NULLS); + + if (!$result || !$row) + { + return false; + } + + $result_row = array(); + foreach ($row as $key => $value) + { + // Oracle treats empty strings as null + if (is_null($value)) + { + $value = ''; + } + + // OCI->CLOB? + if (is_object($value)) + { + $value = $value->load(); + } + + $result_row[strtolower($key)] = $value; + } + + return $result_row; + } + + return false; + } + + /** + * Seek to given row number + * rownum is zero-based + */ + function sql_rowseek($rownum, &$query_id) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_rowseek($rownum, $query_id); + } + + if ($query_id === false) + { + return false; + } + + // Reset internal pointer + @ociexecute($query_id, OCI_DEFAULT); + + // We do not fetch the row for rownum == 0 because then the next resultset would be the second row + for ($i = 0; $i < $rownum; $i++) + { + if (!$this->sql_fetchrow($query_id)) + { + return false; + } + } + + return true; + } + + /** + * Get last inserted id after insert statement + */ + function sql_nextid() + { + $query_id = $this->query_result; + + if ($query_id !== false && $this->last_query_text != '') + { + if (preg_match('#^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)#is', $this->last_query_text, $tablename)) + { + $query = 'SELECT ' . $tablename[1] . '_seq.currval FROM DUAL'; + $stmt = @ociparse($this->db_connect_id, $query); + @ociexecute($stmt, OCI_DEFAULT); + + $temp_result = @ocifetchinto($stmt, $temp_array, OCI_ASSOC + OCI_RETURN_NULLS); + @ocifreestatement($stmt); + + if ($temp_result) + { + return $temp_array['CURRVAL']; + } + else + { + return false; + } + } + } + + return false; + } + + /** + * Free sql result + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_freeresult($query_id); + } + + if (isset($this->open_queries[(int) $query_id])) + { + unset($this->open_queries[(int) $query_id]); + return @ocifreestatement($query_id); + } + + return false; + } + + /** + * Escape string used in sql query + */ + function sql_escape($msg) + { + return str_replace(array("'", "\0"), array("''", ''), $msg); + } + + /** + * Build LIKE expression + * @access private + */ + function _sql_like_expression($expression) + { + return $expression . " ESCAPE '\\'"; + } + + function _sql_custom_build($stage, $data) + { + return $data; + } + + function _sql_bit_and($column_name, $bit, $compare = '') + { + return 'BITAND(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : ''); + } + + function _sql_bit_or($column_name, $bit, $compare = '') + { + return 'BITOR(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : ''); + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + $error = @ocierror(); + $error = (!$error) ? @ocierror($this->query_result) : $error; + $error = (!$error) ? @ocierror($this->db_connect_id) : $error; + + if ($error) + { + $this->last_error_result = $error; + } + else + { + $error = (isset($this->last_error_result) && $this->last_error_result) ? $this->last_error_result : array(); + } + + return $error; + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + return @ocilogoff($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + switch ($mode) + { + case 'start': + + $html_table = false; + + // Grab a plan table, any will do + $sql = "SELECT table_name + FROM USER_TABLES + WHERE table_name LIKE '%PLAN_TABLE%'"; + $stmt = ociparse($this->db_connect_id, $sql); + ociexecute($stmt); + $result = array(); + + if (ocifetchinto($stmt, $result, OCI_ASSOC + OCI_RETURN_NULLS)) + { + $table = $result['TABLE_NAME']; + + // This is the statement_id that will allow us to track the plan + $statement_id = substr(md5($query), 0, 30); + + // Remove any stale plans + $stmt2 = ociparse($this->db_connect_id, "DELETE FROM $table WHERE statement_id='$statement_id'"); + ociexecute($stmt2); + ocifreestatement($stmt2); + + // Explain the plan + $sql = "EXPLAIN PLAN + SET STATEMENT_ID = '$statement_id' + FOR $query"; + $stmt2 = ociparse($this->db_connect_id, $sql); + ociexecute($stmt2); + ocifreestatement($stmt2); + + // Get the data from the plan + $sql = "SELECT operation, options, object_name, object_type, cardinality, cost + FROM plan_table + START WITH id = 0 AND statement_id = '$statement_id' + CONNECT BY PRIOR id = parent_id + AND statement_id = '$statement_id'"; + $stmt2 = ociparse($this->db_connect_id, $sql); + ociexecute($stmt2); + + $row = array(); + while (ocifetchinto($stmt2, $row, OCI_ASSOC + OCI_RETURN_NULLS)) + { + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + + ocifreestatement($stmt2); + + // Remove the plan we just made, we delete them on request anyway + $stmt2 = ociparse($this->db_connect_id, "DELETE FROM $table WHERE statement_id='$statement_id'"); + ociexecute($stmt2); + ocifreestatement($stmt2); + } + + ocifreestatement($stmt); + + if ($html_table) + { + $this->html_hold .= ''; + } + + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @ociparse($this->db_connect_id, $query); + $success = @ociexecute($result, OCI_DEFAULT); + $row = array(); + + while (@ocifetchinto($result, $row, OCI_ASSOC + OCI_RETURN_NULLS)) + { + // Take the time spent on parsing rows into account + } + @ocifreestatement($result); + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } +} diff --git a/phpBB/includes/db/driver/postgres.php b/phpBB/includes/db/driver/postgres.php new file mode 100644 index 0000000000..3f54936d23 --- /dev/null +++ b/phpBB/includes/db/driver/postgres.php @@ -0,0 +1,491 @@ +dbname = $database; + if (strpos($database, '.') !== false) + { + list($database, $schema) = explode('.', $database); + } + $connect_string .= "dbname=$database"; + } + + $this->persistency = $persistency; + + if ($this->persistency) + { + if (!function_exists('pg_pconnect')) + { + $this->connect_error = 'pg_pconnect function does not exist, is pgsql extension installed?'; + return $this->sql_error(''); + } + $collector = new phpbb_error_collector; + $collector->install(); + $this->db_connect_id = (!$new_link) ? @pg_pconnect($connect_string) : @pg_pconnect($connect_string, PGSQL_CONNECT_FORCE_NEW); + } + else + { + if (!function_exists('pg_connect')) + { + $this->connect_error = 'pg_connect function does not exist, is pgsql extension installed?'; + return $this->sql_error(''); + } + $collector = new phpbb_error_collector; + $collector->install(); + $this->db_connect_id = (!$new_link) ? @pg_connect($connect_string) : @pg_connect($connect_string, PGSQL_CONNECT_FORCE_NEW); + } + + $collector->uninstall(); + + if ($this->db_connect_id) + { + if (version_compare($this->sql_server_info(true), '8.2', '>=')) + { + $this->multi_insert = true; + } + + if ($schema !== '') + { + @pg_query($this->db_connect_id, 'SET search_path TO ' . $schema); + } + return $this->db_connect_id; + } + + $this->connect_error = $collector->format_errors(); + return $this->sql_error(''); + } + + /** + * Version information about used database + * @param bool $raw if true, only return the fetched sql_server_version + * @param bool $use_cache If true, it is safe to retrieve the value from the cache + * @return string sql server version + */ + function sql_server_info($raw = false, $use_cache = true) + { + global $cache; + + if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('pgsql_version')) === false) + { + $query_id = @pg_query($this->db_connect_id, 'SELECT VERSION() AS version'); + $row = @pg_fetch_assoc($query_id, null); + @pg_free_result($query_id); + + $this->sql_server_version = (!empty($row['version'])) ? trim(substr($row['version'], 10)) : 0; + + if (!empty($cache) && $use_cache) + { + $cache->put('pgsql_version', $this->sql_server_version); + } + } + + return ($raw) ? $this->sql_server_version : 'PostgreSQL ' . $this->sql_server_version; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return @pg_query($this->db_connect_id, 'BEGIN'); + break; + + case 'commit': + return @pg_query($this->db_connect_id, 'COMMIT'); + break; + + case 'rollback': + return @pg_query($this->db_connect_id, 'ROLLBACK'); + break; + } + + return true; + } + + /** + * Base query method + * + * @param string $query Contains the SQL query which shall be executed + * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache + * @return mixed When casted to bool the returned value returns true on success and false on failure + * + * @access public + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('start', $query); + } + + $this->last_query_text = $query; + $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + if (($this->query_result = @pg_query($this->db_connect_id, $query)) === false) + { + $this->sql_error($query); + } + + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('stop', $query); + } + + if ($cache_ttl && method_exists($cache, 'sql_save')) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $cache->sql_save($query, $this->query_result, $cache_ttl); + } + else if (strpos($query, 'SELECT') === 0 && $this->query_result) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG_EXTRA')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return $this->query_result; + } + + /** + * Build db-specific query data + * @access private + */ + function _sql_custom_build($stage, $data) + { + return $data; + } + + /** + * Build LIMIT query + */ + function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + $this->query_result = false; + + // if $total is set to 0 we do not want to limit the number of rows + if ($total == 0) + { + $total = 'ALL'; + } + + $query .= "\n LIMIT $total OFFSET $offset"; + + return $this->sql_query($query, $cache_ttl); + } + + /** + * Return number of affected rows + */ + function sql_affectedrows() + { + return ($this->query_result) ? @pg_affected_rows($this->query_result) : false; + } + + /** + * Fetch current row + */ + function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_fetchrow($query_id); + } + + return ($query_id !== false) ? @pg_fetch_assoc($query_id, null) : false; + } + + /** + * Seek to given row number + * rownum is zero-based + */ + function sql_rowseek($rownum, &$query_id) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_rowseek($rownum, $query_id); + } + + return ($query_id !== false) ? @pg_result_seek($query_id, $rownum) : false; + } + + /** + * Get last inserted id after insert statement + */ + function sql_nextid() + { + $query_id = $this->query_result; + + if ($query_id !== false && $this->last_query_text != '') + { + if (preg_match("/^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)/is", $this->last_query_text, $tablename)) + { + $query = "SELECT currval('" . $tablename[1] . "_seq') AS last_value"; + $temp_q_id = @pg_query($this->db_connect_id, $query); + + if (!$temp_q_id) + { + return false; + } + + $temp_result = @pg_fetch_assoc($temp_q_id, NULL); + @pg_free_result($query_id); + + return ($temp_result) ? $temp_result['last_value'] : false; + } + } + + return false; + } + + /** + * Free sql result + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_freeresult($query_id); + } + + if (isset($this->open_queries[(int) $query_id])) + { + unset($this->open_queries[(int) $query_id]); + return @pg_free_result($query_id); + } + + return false; + } + + /** + * Escape string used in sql query + * Note: Do not use for bytea values if we may use them at a later stage + */ + function sql_escape($msg) + { + return @pg_escape_string($msg); + } + + /** + * Build LIKE expression + * @access private + */ + function _sql_like_expression($expression) + { + return $expression; + } + + /** + * @inheritdoc + */ + function cast_expr_to_bigint($expression) + { + return 'CAST(' . $expression . ' as DECIMAL(255, 0))'; + } + + /** + * @inheritdoc + */ + function cast_expr_to_string($expression) + { + return 'CAST(' . $expression . ' as VARCHAR(255))'; + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + // pg_last_error only works when there is an established connection. + // Connection errors have to be tracked by us manually. + if ($this->db_connect_id) + { + $message = @pg_last_error($this->db_connect_id); + } + else + { + $message = $this->connect_error; + } + + return array( + 'message' => $message, + 'code' => '' + ); + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + return @pg_close($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + switch ($mode) + { + case 'start': + + $explain_query = $query; + if (preg_match('/UPDATE ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + else if (preg_match('/DELETE FROM ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + + if (preg_match('/^SELECT/', $explain_query)) + { + $html_table = false; + + if ($result = @pg_query($this->db_connect_id, "EXPLAIN $explain_query")) + { + while ($row = @pg_fetch_assoc($result, NULL)) + { + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + } + @pg_free_result($result); + + if ($html_table) + { + $this->html_hold .= ''; + } + } + + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @pg_query($this->db_connect_id, $query); + while ($void = @pg_fetch_assoc($result, NULL)) + { + // Take the time spent on parsing rows into account + } + @pg_free_result($result); + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } +} diff --git a/phpBB/includes/db/driver/sqlite.php b/phpBB/includes/db/driver/sqlite.php new file mode 100644 index 0000000000..363f26da2b --- /dev/null +++ b/phpBB/includes/db/driver/sqlite.php @@ -0,0 +1,334 @@ +persistency = $persistency; + $this->user = $sqluser; + $this->server = $sqlserver . (($port) ? ':' . $port : ''); + $this->dbname = $database; + + $error = ''; + $this->db_connect_id = ($this->persistency) ? @sqlite_popen($this->server, 0666, $error) : @sqlite_open($this->server, 0666, $error); + + if ($this->db_connect_id) + { + @sqlite_query('PRAGMA short_column_names = 1', $this->db_connect_id); +// @sqlite_query('PRAGMA encoding = "UTF-8"', $this->db_connect_id); + } + + return ($this->db_connect_id) ? true : array('message' => $error); + } + + /** + * Version information about used database + * @param bool $raw if true, only return the fetched sql_server_version + * @param bool $use_cache if true, it is safe to retrieve the stored value from the cache + * @return string sql server version + */ + function sql_server_info($raw = false, $use_cache = true) + { + global $cache; + + if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('sqlite_version')) === false) + { + $result = @sqlite_query('SELECT sqlite_version() AS version', $this->db_connect_id); + $row = @sqlite_fetch_array($result, SQLITE_ASSOC); + + $this->sql_server_version = (!empty($row['version'])) ? $row['version'] : 0; + + if (!empty($cache) && $use_cache) + { + $cache->put('sqlite_version', $this->sql_server_version); + } + } + + return ($raw) ? $this->sql_server_version : 'SQLite ' . $this->sql_server_version; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return @sqlite_query('BEGIN', $this->db_connect_id); + break; + + case 'commit': + return @sqlite_query('COMMIT', $this->db_connect_id); + break; + + case 'rollback': + return @sqlite_query('ROLLBACK', $this->db_connect_id); + break; + } + + return true; + } + + /** + * Base query method + * + * @param string $query Contains the SQL query which shall be executed + * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache + * @return mixed When casted to bool the returned value returns true on success and false on failure + * + * @access public + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('start', $query); + } + + $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + if (($this->query_result = @sqlite_query($query, $this->db_connect_id)) === false) + { + $this->sql_error($query); + } + + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('stop', $query); + } + + if ($cache_ttl && method_exists($cache, 'sql_save')) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $cache->sql_save($query, $this->query_result, $cache_ttl); + } + else if (strpos($query, 'SELECT') === 0 && $this->query_result) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG_EXTRA')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return $this->query_result; + } + + /** + * Build LIMIT query + */ + function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + $this->query_result = false; + + // if $total is set to 0 we do not want to limit the number of rows + if ($total == 0) + { + $total = -1; + } + + $query .= "\n LIMIT " . ((!empty($offset)) ? $offset . ', ' . $total : $total); + + return $this->sql_query($query, $cache_ttl); + } + + /** + * Return number of affected rows + */ + function sql_affectedrows() + { + return ($this->db_connect_id) ? @sqlite_changes($this->db_connect_id) : false; + } + + /** + * Fetch current row + */ + function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_fetchrow($query_id); + } + + return ($query_id !== false) ? @sqlite_fetch_array($query_id, SQLITE_ASSOC) : false; + } + + /** + * Seek to given row number + * rownum is zero-based + */ + function sql_rowseek($rownum, &$query_id) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_rowseek($rownum, $query_id); + } + + return ($query_id !== false) ? @sqlite_seek($query_id, $rownum) : false; + } + + /** + * Get last inserted id after insert statement + */ + function sql_nextid() + { + return ($this->db_connect_id) ? @sqlite_last_insert_rowid($this->db_connect_id) : false; + } + + /** + * Free sql result + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_freeresult($query_id); + } + + return true; + } + + /** + * Escape string used in sql query + */ + function sql_escape($msg) + { + return @sqlite_escape_string($msg); + } + + /** + * Correctly adjust LIKE expression for special characters + * For SQLite an underscore is a not-known character... this may change with SQLite3 + */ + function sql_like_expression($expression) + { + // Unlike LIKE, GLOB is case sensitive (unfortunatly). SQLite users need to live with it! + // We only catch * and ? here, not the character map possible on file globbing. + $expression = str_replace(array(chr(0) . '_', chr(0) . '%'), array(chr(0) . '?', chr(0) . '*'), $expression); + + $expression = str_replace(array('?', '*'), array("\?", "\*"), $expression); + $expression = str_replace(array(chr(0) . "\?", chr(0) . "\*"), array('?', '*'), $expression); + + return 'GLOB \'' . $this->sql_escape($expression) . '\''; + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + return array( + 'message' => @sqlite_error_string(@sqlite_last_error($this->db_connect_id)), + 'code' => @sqlite_last_error($this->db_connect_id) + ); + } + + /** + * Build db-specific query data + * @access private + */ + function _sql_custom_build($stage, $data) + { + return $data; + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + return @sqlite_close($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + switch ($mode) + { + case 'start': + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @sqlite_query($query, $this->db_connect_id); + while ($void = @sqlite_fetch_array($result, SQLITE_ASSOC)) + { + // Take the time spent on parsing rows into account + } + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } +} diff --git a/phpBB/includes/db/firebird.php b/phpBB/includes/db/firebird.php deleted file mode 100644 index 7709e8fdf5..0000000000 --- a/phpBB/includes/db/firebird.php +++ /dev/null @@ -1,540 +0,0 @@ -persistency = $persistency; - $this->user = $sqluser; - $this->server = $sqlserver . (($port) ? ':' . $port : ''); - $this->dbname = str_replace('\\', '/', $database); - - // There are three possibilities to connect to an interbase db - if (!$this->server) - { - $use_database = $this->dbname; - } - else if (strpos($this->server, '//') === 0) - { - $use_database = $this->server . $this->dbname; - } - else - { - $use_database = $this->server . ':' . $this->dbname; - } - - if ($this->persistency) - { - if (!function_exists('ibase_pconnect')) - { - $this->connect_error = 'ibase_pconnect function does not exist, is interbase extension installed?'; - return $this->sql_error(''); - } - $this->db_connect_id = @ibase_pconnect($use_database, $this->user, $sqlpassword, false, false, 3); - } - else - { - if (!function_exists('ibase_connect')) - { - $this->connect_error = 'ibase_connect function does not exist, is interbase extension installed?'; - return $this->sql_error(''); - } - $this->db_connect_id = @ibase_connect($use_database, $this->user, $sqlpassword, false, false, 3); - } - - // Do not call ibase_service_attach if connection failed, - // otherwise error message from ibase_(p)connect call will be clobbered. - if ($this->db_connect_id && function_exists('ibase_service_attach') && $this->server) - { - $this->service_handle = @ibase_service_attach($this->server, $this->user, $sqlpassword); - } - else - { - $this->service_handle = false; - } - - return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); - } - - /** - * Version information about used database - * @param bool $raw if true, only return the fetched sql_server_version - * @param bool $use_cache forced to false for Interbase - * @return string sql server version - */ - function sql_server_info($raw = false, $use_cache = true) - { - /** - * force $use_cache false. I didn't research why the caching code there is no caching code - * but I assume its because the IB extension provides a direct method to access it - * without a query. - */ - - $use_cache = false; - - if ($this->service_handle !== false && function_exists('ibase_server_info')) - { - return @ibase_server_info($this->service_handle, IBASE_SVC_SERVER_VERSION); - } - - return ($raw) ? '2.1' : 'Firebird/Interbase'; - } - - /** - * SQL Transaction - * @access private - */ - function _sql_transaction($status = 'begin') - { - switch ($status) - { - case 'begin': - return true; - break; - - case 'commit': - return @ibase_commit(); - break; - - case 'rollback': - return @ibase_rollback(); - break; - } - - return true; - } - - /** - * Base query method - * - * @param string $query Contains the SQL query which shall be executed - * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache - * @return mixed When casted to bool the returned value returns true on success and false on failure - * - * @access public - */ - function sql_query($query = '', $cache_ttl = 0) - { - if ($query != '') - { - global $cache; - - // EXPLAIN only in extra debug mode - if (defined('DEBUG_EXTRA')) - { - $this->sql_report('start', $query); - } - - $this->last_query_text = $query; - $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; - $this->sql_add_num_queries($this->query_result); - - if ($this->query_result === false) - { - $array = array(); - // We overcome Firebird's 32767 char limit by binding vars - if (strlen($query) > 32767) - { - if (preg_match('/^(INSERT INTO[^(]++)\\(([^()]+)\\) VALUES[^(]++\\((.*?)\\)$/s', $query, $regs)) - { - if (strlen($regs[3]) > 32767) - { - preg_match_all('/\'(?:[^\']++|\'\')*+\'|[\d-.]+/', $regs[3], $vals, PREG_PATTERN_ORDER); - - $inserts = $vals[0]; - unset($vals); - - foreach ($inserts as $key => $value) - { - if (!empty($value) && $value[0] === "'" && strlen($value) > 32769) // check to see if this thing is greater than the max + 'x2 - { - $inserts[$key] = '?'; - $array[] = str_replace("''", "'", substr($value, 1, -1)); - } - } - - $query = $regs[1] . '(' . $regs[2] . ') VALUES (' . implode(', ', $inserts) . ')'; - } - } - else if (preg_match('/^(UPDATE ([\\w_]++)\\s+SET )([\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|\\d+)(?:,\\s*[\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+))*+)\\s+(WHERE.*)$/s', $query, $data)) - { - if (strlen($data[3]) > 32767) - { - $update = $data[1]; - $where = $data[4]; - preg_match_all('/(\\w++)\\s*=\\s*(\'(?:[^\']++|\'\')*+\'|[\d-.]++)/', $data[3], $temp, PREG_SET_ORDER); - unset($data); - - $cols = array(); - foreach ($temp as $value) - { - if (!empty($value[2]) && $value[2][0] === "'" && strlen($value[2]) > 32769) // check to see if this thing is greater than the max + 'x2 - { - $array[] = str_replace("''", "'", substr($value[2], 1, -1)); - $cols[] = $value[1] . '=?'; - } - else - { - $cols[] = $value[1] . '=' . $value[2]; - } - } - - $query = $update . implode(', ', $cols) . ' ' . $where; - unset($cols); - } - } - } - - if (!function_exists('ibase_affected_rows') && (preg_match('/^UPDATE ([\w_]++)\s+SET [\w_]++\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+)(?:,\s*[\w_]++\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+))*+\s+(WHERE.*)?$/s', $query, $regs) || preg_match('/^DELETE FROM ([\w_]++)\s*(WHERE\s*.*)?$/s', $query, $regs))) - { - $affected_sql = 'SELECT COUNT(*) as num_rows_affected FROM ' . $regs[1]; - if (!empty($regs[2])) - { - $affected_sql .= ' ' . $regs[2]; - } - - if (!($temp_q_id = @ibase_query($this->db_connect_id, $affected_sql))) - { - return false; - } - - $temp_result = @ibase_fetch_assoc($temp_q_id); - @ibase_free_result($temp_q_id); - - $this->affected_rows = ($temp_result) ? $temp_result['NUM_ROWS_AFFECTED'] : false; - } - - if (sizeof($array)) - { - $p_query = @ibase_prepare($this->db_connect_id, $query); - array_unshift($array, $p_query); - $this->query_result = call_user_func_array('ibase_execute', $array); - unset($array); - - if ($this->query_result === false) - { - $this->sql_error($query); - } - } - else if (($this->query_result = @ibase_query($this->db_connect_id, $query)) === false) - { - $this->sql_error($query); - } - - if (defined('DEBUG_EXTRA')) - { - $this->sql_report('stop', $query); - } - - if (!$this->transaction) - { - if (function_exists('ibase_commit_ret')) - { - @ibase_commit_ret(); - } - else - { - // way cooler than ibase_commit_ret :D - @ibase_query('COMMIT RETAIN;'); - } - } - - if ($cache_ttl && method_exists($cache, 'sql_save')) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - $cache->sql_save($query, $this->query_result, $cache_ttl); - } - else if (strpos($query, 'SELECT') === 0 && $this->query_result) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - } - } - else if (defined('DEBUG_EXTRA')) - { - $this->sql_report('fromcache', $query); - } - } - else - { - return false; - } - - return $this->query_result; - } - - /** - * Build LIMIT query - */ - function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) - { - $this->query_result = false; - - $query = 'SELECT FIRST ' . $total . ((!empty($offset)) ? ' SKIP ' . $offset : '') . substr($query, 6); - - return $this->sql_query($query, $cache_ttl); - } - - /** - * Return number of affected rows - */ - function sql_affectedrows() - { - // PHP 5+ function - if (function_exists('ibase_affected_rows')) - { - return ($this->db_connect_id) ? @ibase_affected_rows($this->db_connect_id) : false; - } - else - { - return $this->affected_rows; - } - } - - /** - * Fetch current row - */ - function sql_fetchrow($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_fetchrow($query_id); - } - - if ($query_id === false) - { - return false; - } - - $row = array(); - $cur_row = @ibase_fetch_object($query_id, IBASE_TEXT); - - if (!$cur_row) - { - return false; - } - - foreach (get_object_vars($cur_row) as $key => $value) - { - $row[strtolower($key)] = (is_string($value)) ? trim(str_replace(array("\\0", "\\n"), array("\0", "\n"), $value)) : $value; - } - - return (sizeof($row)) ? $row : false; - } - - /** - * Get last inserted id after insert statement - */ - function sql_nextid() - { - $query_id = $this->query_result; - - if ($query_id !== false && $this->last_query_text != '') - { - if ($this->query_result && preg_match('#^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)#i', $this->last_query_text, $tablename)) - { - $sql = 'SELECT GEN_ID(' . $tablename[1] . '_gen, 0) AS new_id FROM RDB$DATABASE'; - - if (!($temp_q_id = @ibase_query($this->db_connect_id, $sql))) - { - return false; - } - - $temp_result = @ibase_fetch_assoc($temp_q_id); - @ibase_free_result($temp_q_id); - - return ($temp_result) ? $temp_result['NEW_ID'] : false; - } - } - - return false; - } - - /** - * Free sql result - */ - function sql_freeresult($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_freeresult($query_id); - } - - if (isset($this->open_queries[(int) $query_id])) - { - unset($this->open_queries[(int) $query_id]); - return @ibase_free_result($query_id); - } - - return false; - } - - /** - * Escape string used in sql query - */ - function sql_escape($msg) - { - return str_replace(array("'", "\0"), array("''", ''), $msg); - } - - /** - * Build LIKE expression - * @access private - */ - function _sql_like_expression($expression) - { - return $expression . " ESCAPE '\\'"; - } - - /** - * Build db-specific query data - * @access private - */ - function _sql_custom_build($stage, $data) - { - return $data; - } - - function _sql_bit_and($column_name, $bit, $compare = '') - { - return 'BIN_AND(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : ''); - } - - function _sql_bit_or($column_name, $bit, $compare = '') - { - return 'BIN_OR(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : ''); - } - - /** - * @inheritdoc - */ - function cast_expr_to_bigint($expression) - { - // Precision must be from 1 to 18 - return 'CAST(' . $expression . ' as DECIMAL(18, 0))'; - } - - /** - * @inheritdoc - */ - function cast_expr_to_string($expression) - { - return 'CAST(' . $expression . ' as VARCHAR(255))'; - } - - /** - * return sql error array - * @access private - */ - function _sql_error() - { - // Need special handling here because ibase_errmsg returns - // connection errors, however if the interbase extension - // is not installed then ibase_errmsg does not exist and - // we cannot call it. - if (function_exists('ibase_errmsg')) - { - $msg = @ibase_errmsg(); - if (!$msg) - { - $msg = $this->connect_error; - } - } - else - { - $msg = $this->connect_error; - } - return array( - 'message' => $msg, - 'code' => (@function_exists('ibase_errcode') ? @ibase_errcode() : '') - ); - } - - /** - * Close sql connection - * @access private - */ - function _sql_close() - { - if ($this->service_handle !== false) - { - @ibase_service_detach($this->service_handle); - } - - return @ibase_close($this->db_connect_id); - } - - /** - * Build db-specific report - * @access private - */ - function _sql_report($mode, $query = '') - { - switch ($mode) - { - case 'start': - break; - - case 'fromcache': - $endtime = explode(' ', microtime()); - $endtime = $endtime[0] + $endtime[1]; - - $result = @ibase_query($this->db_connect_id, $query); - while ($void = @ibase_fetch_object($result, IBASE_TEXT)) - { - // Take the time spent on parsing rows into account - } - @ibase_free_result($result); - - $splittime = explode(' ', microtime()); - $splittime = $splittime[0] + $splittime[1]; - - $this->sql_report('record_fromcache', $query, $endtime, $splittime); - - break; - } - } -} diff --git a/phpBB/includes/db/mssql.php b/phpBB/includes/db/mssql.php deleted file mode 100644 index fb044b492f..0000000000 --- a/phpBB/includes/db/mssql.php +++ /dev/null @@ -1,456 +0,0 @@ -persistency = $persistency; - $this->user = $sqluser; - $this->dbname = $database; - - $port_delimiter = (defined('PHP_OS') && substr(PHP_OS, 0, 3) === 'WIN') ? ',' : ':'; - $this->server = $sqlserver . (($port) ? $port_delimiter . $port : ''); - - @ini_set('mssql.charset', 'UTF-8'); - @ini_set('mssql.textlimit', 2147483647); - @ini_set('mssql.textsize', 2147483647); - - $this->db_connect_id = ($this->persistency) ? @mssql_pconnect($this->server, $this->user, $sqlpassword, $new_link) : @mssql_connect($this->server, $this->user, $sqlpassword, $new_link); - - if ($this->db_connect_id && $this->dbname != '') - { - if (!@mssql_select_db($this->dbname, $this->db_connect_id)) - { - @mssql_close($this->db_connect_id); - return false; - } - } - - return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); - } - - /** - * Version information about used database - * @param bool $raw if true, only return the fetched sql_server_version - * @param bool $use_cache If true, it is safe to retrieve the value from the cache - * @return string sql server version - */ - function sql_server_info($raw = false, $use_cache = true) - { - global $cache; - - if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mssql_version')) === false) - { - $result_id = @mssql_query("SELECT SERVERPROPERTY('productversion'), SERVERPROPERTY('productlevel'), SERVERPROPERTY('edition')", $this->db_connect_id); - - $row = false; - if ($result_id) - { - $row = @mssql_fetch_assoc($result_id); - @mssql_free_result($result_id); - } - - $this->sql_server_version = ($row) ? trim(implode(' ', $row)) : 0; - - if (!empty($cache) && $use_cache) - { - $cache->put('mssql_version', $this->sql_server_version); - } - } - - if ($raw) - { - return $this->sql_server_version; - } - - return ($this->sql_server_version) ? 'MSSQL
' . $this->sql_server_version : 'MSSQL'; - } - - /** - * {@inheritDoc} - */ - public function sql_concatenate($expr1, $expr2) - { - return $expr1 . ' + ' . $expr2; - } - - /** - * SQL Transaction - * @access private - */ - function _sql_transaction($status = 'begin') - { - switch ($status) - { - case 'begin': - return @mssql_query('BEGIN TRANSACTION', $this->db_connect_id); - break; - - case 'commit': - return @mssql_query('COMMIT TRANSACTION', $this->db_connect_id); - break; - - case 'rollback': - return @mssql_query('ROLLBACK TRANSACTION', $this->db_connect_id); - break; - } - - return true; - } - - /** - * Base query method - * - * @param string $query Contains the SQL query which shall be executed - * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache - * @return mixed When casted to bool the returned value returns true on success and false on failure - * - * @access public - */ - function sql_query($query = '', $cache_ttl = 0) - { - if ($query != '') - { - global $cache; - - // EXPLAIN only in extra debug mode - if (defined('DEBUG_EXTRA')) - { - $this->sql_report('start', $query); - } - - $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; - $this->sql_add_num_queries($this->query_result); - - if ($this->query_result === false) - { - if (($this->query_result = @mssql_query($query, $this->db_connect_id)) === false) - { - $this->sql_error($query); - } - - if (defined('DEBUG_EXTRA')) - { - $this->sql_report('stop', $query); - } - - if ($cache_ttl && method_exists($cache, 'sql_save')) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - $cache->sql_save($query, $this->query_result, $cache_ttl); - } - else if (strpos($query, 'SELECT') === 0 && $this->query_result) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - } - } - else if (defined('DEBUG_EXTRA')) - { - $this->sql_report('fromcache', $query); - } - } - else - { - return false; - } - - return $this->query_result; - } - - /** - * Build LIMIT query - */ - function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) - { - $this->query_result = false; - - // Since TOP is only returning a set number of rows we won't need it if total is set to 0 (return all rows) - if ($total) - { - // We need to grab the total number of rows + the offset number of rows to get the correct result - if (strpos($query, 'SELECT DISTINCT') === 0) - { - $query = 'SELECT DISTINCT TOP ' . ($total + $offset) . ' ' . substr($query, 15); - } - else - { - $query = 'SELECT TOP ' . ($total + $offset) . ' ' . substr($query, 6); - } - } - - $result = $this->sql_query($query, $cache_ttl); - - // Seek by $offset rows - if ($offset) - { - $this->sql_rowseek($offset, $result); - } - - return $result; - } - - /** - * Return number of affected rows - */ - function sql_affectedrows() - { - return ($this->db_connect_id) ? @mssql_rows_affected($this->db_connect_id) : false; - } - - /** - * Fetch current row - */ - function sql_fetchrow($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_fetchrow($query_id); - } - - if ($query_id === false) - { - return false; - } - - $row = @mssql_fetch_assoc($query_id); - - // I hope i am able to remove this later... hopefully only a PHP or MSSQL bug - if ($row) - { - foreach ($row as $key => $value) - { - $row[$key] = ($value === ' ' || $value === NULL) ? '' : $value; - } - } - - return $row; - } - - /** - * Seek to given row number - * rownum is zero-based - */ - function sql_rowseek($rownum, &$query_id) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_rowseek($rownum, $query_id); - } - - return ($query_id !== false) ? @mssql_data_seek($query_id, $rownum) : false; - } - - /** - * Get last inserted id after insert statement - */ - function sql_nextid() - { - $result_id = @mssql_query('SELECT SCOPE_IDENTITY()', $this->db_connect_id); - if ($result_id) - { - if ($row = @mssql_fetch_assoc($result_id)) - { - @mssql_free_result($result_id); - return $row['computed']; - } - @mssql_free_result($result_id); - } - - return false; - } - - /** - * Free sql result - */ - function sql_freeresult($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_freeresult($query_id); - } - - if (isset($this->open_queries[$query_id])) - { - unset($this->open_queries[$query_id]); - return @mssql_free_result($query_id); - } - - return false; - } - - /** - * Escape string used in sql query - */ - function sql_escape($msg) - { - return str_replace(array("'", "\0"), array("''", ''), $msg); - } - - /** - * {@inheritDoc} - */ - function sql_lower_text($column_name) - { - return "LOWER(SUBSTRING($column_name, 1, DATALENGTH($column_name)))"; - } - - /** - * Build LIKE expression - * @access private - */ - function _sql_like_expression($expression) - { - return $expression . " ESCAPE '\\'"; - } - - /** - * return sql error array - * @access private - */ - function _sql_error() - { - $error = array( - 'message' => @mssql_get_last_message(), - 'code' => '' - ); - - // Get error code number - $result_id = @mssql_query('SELECT @@ERROR as code', $this->db_connect_id); - if ($result_id) - { - $row = @mssql_fetch_assoc($result_id); - $error['code'] = $row['code']; - @mssql_free_result($result_id); - } - - // Get full error message if possible - $sql = 'SELECT CAST(description as varchar(255)) as message - FROM master.dbo.sysmessages - WHERE error = ' . $error['code']; - $result_id = @mssql_query($sql); - - if ($result_id) - { - $row = @mssql_fetch_assoc($result_id); - if (!empty($row['message'])) - { - $error['message'] .= '
' . $row['message']; - } - @mssql_free_result($result_id); - } - - return $error; - } - - /** - * Build db-specific query data - * @access private - */ - function _sql_custom_build($stage, $data) - { - return $data; - } - - /** - * Close sql connection - * @access private - */ - function _sql_close() - { - return @mssql_close($this->db_connect_id); - } - - /** - * Build db-specific report - * @access private - */ - function _sql_report($mode, $query = '') - { - switch ($mode) - { - case 'start': - $html_table = false; - @mssql_query('SET SHOWPLAN_TEXT ON;', $this->db_connect_id); - if ($result = @mssql_query($query, $this->db_connect_id)) - { - @mssql_next_result($result); - while ($row = @mssql_fetch_row($result)) - { - $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); - } - } - @mssql_query('SET SHOWPLAN_TEXT OFF;', $this->db_connect_id); - @mssql_free_result($result); - - if ($html_table) - { - $this->html_hold .= ''; - } - break; - - case 'fromcache': - $endtime = explode(' ', microtime()); - $endtime = $endtime[0] + $endtime[1]; - - $result = @mssql_query($query, $this->db_connect_id); - while ($void = @mssql_fetch_assoc($result)) - { - // Take the time spent on parsing rows into account - } - @mssql_free_result($result); - - $splittime = explode(' ', microtime()); - $splittime = $splittime[0] + $splittime[1]; - - $this->sql_report('record_fromcache', $query, $endtime, $splittime); - - break; - } - } -} diff --git a/phpBB/includes/db/mssql_odbc.php b/phpBB/includes/db/mssql_odbc.php deleted file mode 100644 index 64fa9634d1..0000000000 --- a/phpBB/includes/db/mssql_odbc.php +++ /dev/null @@ -1,397 +0,0 @@ -persistency = $persistency; - $this->user = $sqluser; - $this->dbname = $database; - - $port_delimiter = (defined('PHP_OS') && substr(PHP_OS, 0, 3) === 'WIN') ? ',' : ':'; - $this->server = $sqlserver . (($port) ? $port_delimiter . $port : ''); - - $max_size = @ini_get('odbc.defaultlrl'); - if (!empty($max_size)) - { - $unit = strtolower(substr($max_size, -1, 1)); - $max_size = (int) $max_size; - - if ($unit == 'k') - { - $max_size = floor($max_size / 1024); - } - else if ($unit == 'g') - { - $max_size *= 1024; - } - else if (is_numeric($unit)) - { - $max_size = floor((int) ($max_size . $unit) / 1048576); - } - $max_size = max(8, $max_size) . 'M'; - - @ini_set('odbc.defaultlrl', $max_size); - } - - $this->db_connect_id = ($this->persistency) ? @odbc_pconnect($this->server, $this->user, $sqlpassword) : @odbc_connect($this->server, $this->user, $sqlpassword); - - return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); - } - - /** - * Version information about used database - * @param bool $raw if true, only return the fetched sql_server_version - * @param bool $use_cache If true, it is safe to retrieve the value from the cache - * @return string sql server version - */ - function sql_server_info($raw = false, $use_cache = true) - { - global $cache; - - if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mssqlodbc_version')) === false) - { - $result_id = @odbc_exec($this->db_connect_id, "SELECT SERVERPROPERTY('productversion'), SERVERPROPERTY('productlevel'), SERVERPROPERTY('edition')"); - - $row = false; - if ($result_id) - { - $row = @odbc_fetch_array($result_id); - @odbc_free_result($result_id); - } - - $this->sql_server_version = ($row) ? trim(implode(' ', $row)) : 0; - - if (!empty($cache) && $use_cache) - { - $cache->put('mssqlodbc_version', $this->sql_server_version); - } - } - - if ($raw) - { - return $this->sql_server_version; - } - - return ($this->sql_server_version) ? 'MSSQL (ODBC)
' . $this->sql_server_version : 'MSSQL (ODBC)'; - } - - /** - * {@inheritDoc} - */ - public function sql_concatenate($expr1, $expr2) - { - return $expr1 . ' + ' . $expr2; - } - - /** - * SQL Transaction - * @access private - */ - function _sql_transaction($status = 'begin') - { - switch ($status) - { - case 'begin': - return @odbc_exec($this->db_connect_id, 'BEGIN TRANSACTION'); - break; - - case 'commit': - return @odbc_exec($this->db_connect_id, 'COMMIT TRANSACTION'); - break; - - case 'rollback': - return @odbc_exec($this->db_connect_id, 'ROLLBACK TRANSACTION'); - break; - } - - return true; - } - - /** - * Base query method - * - * @param string $query Contains the SQL query which shall be executed - * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache - * @return mixed When casted to bool the returned value returns true on success and false on failure - * - * @access public - */ - function sql_query($query = '', $cache_ttl = 0) - { - if ($query != '') - { - global $cache; - - // EXPLAIN only in extra debug mode - if (defined('DEBUG_EXTRA')) - { - $this->sql_report('start', $query); - } - - $this->last_query_text = $query; - $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; - $this->sql_add_num_queries($this->query_result); - - if ($this->query_result === false) - { - if (($this->query_result = @odbc_exec($this->db_connect_id, $query)) === false) - { - $this->sql_error($query); - } - - if (defined('DEBUG_EXTRA')) - { - $this->sql_report('stop', $query); - } - - if ($cache_ttl && method_exists($cache, 'sql_save')) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - $cache->sql_save($query, $this->query_result, $cache_ttl); - } - else if (strpos($query, 'SELECT') === 0 && $this->query_result) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - } - } - else if (defined('DEBUG_EXTRA')) - { - $this->sql_report('fromcache', $query); - } - } - else - { - return false; - } - - return $this->query_result; - } - - /** - * Build LIMIT query - */ - function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) - { - $this->query_result = false; - - // Since TOP is only returning a set number of rows we won't need it if total is set to 0 (return all rows) - if ($total) - { - // We need to grab the total number of rows + the offset number of rows to get the correct result - if (strpos($query, 'SELECT DISTINCT') === 0) - { - $query = 'SELECT DISTINCT TOP ' . ($total + $offset) . ' ' . substr($query, 15); - } - else - { - $query = 'SELECT TOP ' . ($total + $offset) . ' ' . substr($query, 6); - } - } - - $result = $this->sql_query($query, $cache_ttl); - - // Seek by $offset rows - if ($offset) - { - $this->sql_rowseek($offset, $result); - } - - return $result; - } - - /** - * Return number of affected rows - */ - function sql_affectedrows() - { - return ($this->db_connect_id) ? @odbc_num_rows($this->query_result) : false; - } - - /** - * Fetch current row - * @note number of bytes returned depends on odbc.defaultlrl php.ini setting. If it is limited to 4K for example only 4K of data is returned max. - */ - function sql_fetchrow($query_id = false, $debug = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_fetchrow($query_id); - } - - return ($query_id !== false) ? @odbc_fetch_array($query_id) : false; - } - - /** - * Get last inserted id after insert statement - */ - function sql_nextid() - { - $result_id = @odbc_exec($this->db_connect_id, 'SELECT @@IDENTITY'); - - if ($result_id) - { - if (@odbc_fetch_array($result_id)) - { - $id = @odbc_result($result_id, 1); - @odbc_free_result($result_id); - return $id; - } - @odbc_free_result($result_id); - } - - return false; - } - - /** - * Free sql result - */ - function sql_freeresult($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_freeresult($query_id); - } - - if (isset($this->open_queries[(int) $query_id])) - { - unset($this->open_queries[(int) $query_id]); - return @odbc_free_result($query_id); - } - - return false; - } - - /** - * Escape string used in sql query - */ - function sql_escape($msg) - { - return str_replace(array("'", "\0"), array("''", ''), $msg); - } - - /** - * {@inheritDoc} - */ - function sql_lower_text($column_name) - { - return "LOWER(SUBSTRING($column_name, 1, DATALENGTH($column_name)))"; - } - - /** - * Build LIKE expression - * @access private - */ - function _sql_like_expression($expression) - { - return $expression . " ESCAPE '\\'"; - } - - /** - * Build db-specific query data - * @access private - */ - function _sql_custom_build($stage, $data) - { - return $data; - } - - /** - * return sql error array - * @access private - */ - function _sql_error() - { - return array( - 'message' => @odbc_errormsg(), - 'code' => @odbc_error() - ); - } - - /** - * Close sql connection - * @access private - */ - function _sql_close() - { - return @odbc_close($this->db_connect_id); - } - - /** - * Build db-specific report - * @access private - */ - function _sql_report($mode, $query = '') - { - switch ($mode) - { - case 'start': - break; - - case 'fromcache': - $endtime = explode(' ', microtime()); - $endtime = $endtime[0] + $endtime[1]; - - $result = @odbc_exec($this->db_connect_id, $query); - while ($void = @odbc_fetch_array($result)) - { - // Take the time spent on parsing rows into account - } - @odbc_free_result($result); - - $splittime = explode(' ', microtime()); - $splittime = $splittime[0] + $splittime[1]; - - $this->sql_report('record_fromcache', $query, $endtime, $splittime); - - break; - } - } -} diff --git a/phpBB/includes/db/mssqlnative.php b/phpBB/includes/db/mssqlnative.php deleted file mode 100644 index 1f37d54ecb..0000000000 --- a/phpBB/includes/db/mssqlnative.php +++ /dev/null @@ -1,644 +0,0 @@ -m_cursor = 0; - $this->m_rows = array(); - $this->m_num_fields = sqlsrv_num_fields($queryresult); - $this->m_field_meta = sqlsrv_field_metadata($queryresult); - - while ($row = sqlsrv_fetch_array($queryresult, SQLSRV_FETCH_ASSOC)) - { - if ($row !== null) - { - foreach($row as $k => $v) - { - if (is_object($v) && method_exists($v, 'format')) - { - $row[$k] = $v->format("Y-m-d\TH:i:s\Z"); - } - } - $this->m_rows[] = $row;//read results into memory, cursors are not supported - } - } - - $this->m_row_count = sizeof($this->m_rows); - } - - private function array_to_obj($array, &$obj) - { - foreach ($array as $key => $value) - { - if (is_array($value)) - { - $obj->$key = new stdClass(); - array_to_obj($value, $obj->$key); - } - else - { - $obj->$key = $value; - } - } - return $obj; - } - - public function fetch($mode = SQLSRV_FETCH_BOTH, $object_class = 'stdClass') - { - if ($this->m_cursor >= $this->m_row_count || $this->m_row_count == 0) - { - return false; - } - - $ret = false; - $arr_num = array(); - - if ($mode == SQLSRV_FETCH_NUMERIC || $mode == SQLSRV_FETCH_BOTH) - { - foreach($this->m_rows[$this->m_cursor] as $key => $value) - { - $arr_num[] = $value; - } - } - - switch ($mode) - { - case SQLSRV_FETCH_ASSOC: - $ret = $this->m_rows[$this->m_cursor]; - break; - case SQLSRV_FETCH_NUMERIC: - $ret = $arr_num; - break; - case 'OBJECT': - $ret = $this->array_to_obj($this->m_rows[$this->m_cursor], $o = new $object_class); - break; - case SQLSRV_FETCH_BOTH: - default: - $ret = $this->m_rows[$this->m_cursor] + $arr_num; - break; - } - $this->m_cursor++; - return $ret; - } - - public function get($pos, $fld) - { - return $this->m_rows[$pos][$fld]; - } - - public function num_rows() - { - return $this->m_row_count; - } - - public function seek($iRow) - { - $this->m_cursor = min($iRow, $this->m_row_count); - } - - public function num_fields() - { - return $this->m_num_fields; - } - - public function field_name($nr) - { - $arr_keys = array_keys($this->m_rows[0]); - return $arr_keys[$nr]; - } - - public function field_type($nr) - { - $i = 0; - $int_type = -1; - $str_type = ''; - - foreach ($this->m_field_meta as $meta) - { - if ($nr == $i) - { - $int_type = $meta['Type']; - break; - } - $i++; - } - - //http://msdn.microsoft.com/en-us/library/cc296183.aspx contains type table - switch ($int_type) - { - case SQLSRV_SQLTYPE_BIGINT: $str_type = 'bigint'; break; - case SQLSRV_SQLTYPE_BINARY: $str_type = 'binary'; break; - case SQLSRV_SQLTYPE_BIT: $str_type = 'bit'; break; - case SQLSRV_SQLTYPE_CHAR: $str_type = 'char'; break; - case SQLSRV_SQLTYPE_DATETIME: $str_type = 'datetime'; break; - case SQLSRV_SQLTYPE_DECIMAL/*($precision, $scale)*/: $str_type = 'decimal'; break; - case SQLSRV_SQLTYPE_FLOAT: $str_type = 'float'; break; - case SQLSRV_SQLTYPE_IMAGE: $str_type = 'image'; break; - case SQLSRV_SQLTYPE_INT: $str_type = 'int'; break; - case SQLSRV_SQLTYPE_MONEY: $str_type = 'money'; break; - case SQLSRV_SQLTYPE_NCHAR/*($charCount)*/: $str_type = 'nchar'; break; - case SQLSRV_SQLTYPE_NUMERIC/*($precision, $scale)*/: $str_type = 'numeric'; break; - case SQLSRV_SQLTYPE_NVARCHAR/*($charCount)*/: $str_type = 'nvarchar'; break; - case SQLSRV_SQLTYPE_NTEXT: $str_type = 'ntext'; break; - case SQLSRV_SQLTYPE_REAL: $str_type = 'real'; break; - case SQLSRV_SQLTYPE_SMALLDATETIME: $str_type = 'smalldatetime'; break; - case SQLSRV_SQLTYPE_SMALLINT: $str_type = 'smallint'; break; - case SQLSRV_SQLTYPE_SMALLMONEY: $str_type = 'smallmoney'; break; - case SQLSRV_SQLTYPE_TEXT: $str_type = 'text'; break; - case SQLSRV_SQLTYPE_TIMESTAMP: $str_type = 'timestamp'; break; - case SQLSRV_SQLTYPE_TINYINT: $str_type = 'tinyint'; break; - case SQLSRV_SQLTYPE_UNIQUEIDENTIFIER: $str_type = 'uniqueidentifier'; break; - case SQLSRV_SQLTYPE_UDT: $str_type = 'UDT'; break; - case SQLSRV_SQLTYPE_VARBINARY/*($byteCount)*/: $str_type = 'varbinary'; break; - case SQLSRV_SQLTYPE_VARCHAR/*($charCount)*/: $str_type = 'varchar'; break; - case SQLSRV_SQLTYPE_XML: $str_type = 'xml'; break; - default: $str_type = $int_type; - } - return $str_type; - } - - public function free() - { - unset($this->m_rows); - return; - } -} - -/** -* @package dbal -*/ -class dbal_mssqlnative extends dbal -{ - var $m_insert_id = NULL; - var $last_query_text = ''; - var $query_options = array(); - - /** - * Connect to server - */ - function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false) - { - # Test for driver support, to avoid suppressed fatal error - if (!function_exists('sqlsrv_connect')) - { - trigger_error('Native MS SQL Server driver for PHP is missing or needs to be updated. Version 1.1 or later is required to install phpBB3. You can download the driver from: http://www.microsoft.com/sqlserver/2005/en/us/PHP-Driver.aspx\n', E_USER_ERROR); - } - - //set up connection variables - $this->persistency = $persistency; - $this->user = $sqluser; - $this->dbname = $database; - $port_delimiter = (defined('PHP_OS') && substr(PHP_OS, 0, 3) === 'WIN') ? ',' : ':'; - $this->server = $sqlserver . (($port) ? $port_delimiter . $port : ''); - - //connect to database - error_reporting(E_ALL); - $this->db_connect_id = sqlsrv_connect($this->server, array( - 'Database' => $this->dbname, - 'UID' => $this->user, - 'PWD' => $sqlpassword - )); - - return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); - } - - /** - * Version information about used database - * @param bool $raw if true, only return the fetched sql_server_version - * @param bool $use_cache If true, it is safe to retrieve the value from the cache - * @return string sql server version - */ - function sql_server_info($raw = false, $use_cache = true) - { - global $cache; - - if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mssql_version')) === false) - { - $arr_server_info = sqlsrv_server_info($this->db_connect_id); - $this->sql_server_version = $arr_server_info['SQLServerVersion']; - - if (!empty($cache) && $use_cache) - { - $cache->put('mssql_version', $this->sql_server_version); - } - } - - if ($raw) - { - return $this->sql_server_version; - } - - return ($this->sql_server_version) ? 'MSSQL
' . $this->sql_server_version : 'MSSQL'; - } - - /** - * {@inheritDoc} - */ - public function sql_concatenate($expr1, $expr2) - { - return $expr1 . ' + ' . $expr2; - } - - /** - * {@inheritDoc} - */ - function sql_buffer_nested_transactions() - { - return true; - } - - /** - * SQL Transaction - * @access private - */ - function _sql_transaction($status = 'begin') - { - switch ($status) - { - case 'begin': - return sqlsrv_begin_transaction($this->db_connect_id); - break; - - case 'commit': - return sqlsrv_commit($this->db_connect_id); - break; - - case 'rollback': - return sqlsrv_rollback($this->db_connect_id); - break; - } - return true; - } - - /** - * Base query method - * - * @param string $query Contains the SQL query which shall be executed - * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache - * @return mixed When casted to bool the returned value returns true on success and false on failure - * - * @access public - */ - function sql_query($query = '', $cache_ttl = 0) - { - if ($query != '') - { - global $cache; - - // EXPLAIN only in extra debug mode - if (defined('DEBUG_EXTRA')) - { - $this->sql_report('start', $query); - } - - $this->last_query_text = $query; - $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; - $this->sql_add_num_queries($this->query_result); - - if ($this->query_result === false) - { - if (($this->query_result = @sqlsrv_query($this->db_connect_id, $query, array(), $this->query_options)) === false) - { - $this->sql_error($query); - } - // reset options for next query - $this->query_options = array(); - - if (defined('DEBUG_EXTRA')) - { - $this->sql_report('stop', $query); - } - - if ($cache_ttl && method_exists($cache, 'sql_save')) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - $cache->sql_save($query, $this->query_result, $cache_ttl); - } - else if (strpos($query, 'SELECT') === 0 && $this->query_result) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - } - } - else if (defined('DEBUG_EXTRA')) - { - $this->sql_report('fromcache', $query); - } - } - else - { - return false; - } - return $this->query_result; - } - - /** - * Build LIMIT query - */ - function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) - { - $this->query_result = false; - - // total == 0 means all results - not zero results - if ($offset == 0 && $total !== 0) - { - if (strpos($query, "SELECT") === false) - { - $query = "TOP {$total} " . $query; - } - else - { - $query = preg_replace('/SELECT(\s*DISTINCT)?/Dsi', 'SELECT$1 TOP '.$total, $query); - } - } - else if ($offset > 0) - { - $query = preg_replace('/SELECT(\s*DISTINCT)?/Dsi', 'SELECT$1 TOP(10000000) ', $query); - $query = 'SELECT * - FROM (SELECT sub2.*, ROW_NUMBER() OVER(ORDER BY sub2.line2) AS line3 - FROM (SELECT 1 AS line2, sub1.* FROM (' . $query . ') AS sub1) as sub2) AS sub3'; - - if ($total > 0) - { - $query .= ' WHERE line3 BETWEEN ' . ($offset+1) . ' AND ' . ($offset + $total); - } - else - { - $query .= ' WHERE line3 > ' . $offset; - } - } - - $result = $this->sql_query($query, $cache_ttl); - - return $result; - } - - /** - * Return number of affected rows - */ - function sql_affectedrows() - { - return (!empty($this->query_result)) ? @sqlsrv_rows_affected($this->query_result) : false; - } - - /** - * Fetch current row - */ - function sql_fetchrow($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_fetchrow($query_id); - } - - if ($query_id === false) - { - return false; - } - - $row = @sqlsrv_fetch_array($query_id, SQLSRV_FETCH_ASSOC); - - if ($row) - { - foreach ($row as $key => $value) - { - $row[$key] = ($value === ' ' || $value === NULL) ? '' : $value; - } - - // remove helper values from LIMIT queries - if (isset($row['line2'])) - { - unset($row['line2'], $row['line3']); - } - } - return (sizeof($row)) ? $row : false; - } - - /** - * Get last inserted id after insert statement - */ - function sql_nextid() - { - $result_id = @sqlsrv_query($this->db_connect_id, 'SELECT @@IDENTITY'); - - if ($result_id !== false) - { - $row = @sqlsrv_fetch_array($result_id); - $id = $row[0]; - @sqlsrv_free_stmt($result_id); - return $id; - } - else - { - return false; - } - } - - /** - * Free sql result - */ - function sql_freeresult($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_freeresult($query_id); - } - - if (isset($this->open_queries[$query_id])) - { - unset($this->open_queries[$query_id]); - return @sqlsrv_free_stmt($query_id); - } - return false; - } - - /** - * Escape string used in sql query - */ - function sql_escape($msg) - { - return str_replace(array("'", "\0"), array("''", ''), $msg); - } - - /** - * {@inheritDoc} - */ - function sql_lower_text($column_name) - { - return "LOWER(SUBSTRING($column_name, 1, DATALENGTH($column_name)))"; - } - - /** - * Build LIKE expression - * @access private - */ - function _sql_like_expression($expression) - { - return $expression . " ESCAPE '\\'"; - } - - /** - * return sql error array - * @access private - */ - function _sql_error() - { - $errors = @sqlsrv_errors(SQLSRV_ERR_ERRORS); - $error_message = ''; - $code = 0; - - if ($errors != null) - { - foreach ($errors as $error) - { - $error_message .= "SQLSTATE: ".$error[ 'SQLSTATE']."\n"; - $error_message .= "code: ".$error[ 'code']."\n"; - $code = $error['code']; - $error_message .= "message: ".$error[ 'message']."\n"; - } - $this->last_error_result = $error_message; - $error = $this->last_error_result; - } - else - { - $error = (isset($this->last_error_result) && $this->last_error_result) ? $this->last_error_result : array(); - } - - return array( - 'message' => $error, - 'code' => $code, - ); - } - - /** - * Build db-specific query data - * @access private - */ - function _sql_custom_build($stage, $data) - { - return $data; - } - - /** - * Close sql connection - * @access private - */ - function _sql_close() - { - return @sqlsrv_close($this->db_connect_id); - } - - /** - * Build db-specific report - * @access private - */ - function _sql_report($mode, $query = '') - { - switch ($mode) - { - case 'start': - $html_table = false; - @sqlsrv_query($this->db_connect_id, 'SET SHOWPLAN_TEXT ON;'); - if ($result = @sqlsrv_query($this->db_connect_id, $query)) - { - @sqlsrv_next_result($result); - while ($row = @sqlsrv_fetch_array($result)) - { - $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); - } - } - @sqlsrv_query($this->db_connect_id, 'SET SHOWPLAN_TEXT OFF;'); - @sqlsrv_free_stmt($result); - - if ($html_table) - { - $this->html_hold .= ''; - } - break; - - case 'fromcache': - $endtime = explode(' ', microtime()); - $endtime = $endtime[0] + $endtime[1]; - - $result = @sqlsrv_query($this->db_connect_id, $query); - while ($void = @sqlsrv_fetch_array($result)) - { - // Take the time spent on parsing rows into account - } - @sqlsrv_free_stmt($result); - - $splittime = explode(' ', microtime()); - $splittime = $splittime[0] + $splittime[1]; - - $this->sql_report('record_fromcache', $query, $endtime, $splittime); - - break; - } - } - - /** - * Utility method used to retrieve number of rows - * Emulates mysql_num_rows - * Used in acp_database.php -> write_data_mssqlnative() - * Requires a static or keyset cursor to be definde via - * mssqlnative_set_query_options() - */ - function mssqlnative_num_rows($res) - { - if ($res !== false) - { - return sqlsrv_num_rows($res); - } - else - { - return false; - } - } - - /** - * Allows setting mssqlnative specific query options passed to sqlsrv_query as 4th parameter. - */ - function mssqlnative_set_query_options($options) - { - $this->query_options = $options; - } -} diff --git a/phpBB/includes/db/mysql.php b/phpBB/includes/db/mysql.php deleted file mode 100644 index 8d1f805870..0000000000 --- a/phpBB/includes/db/mysql.php +++ /dev/null @@ -1,567 +0,0 @@ -persistency = $persistency; - $this->user = $sqluser; - $this->server = $sqlserver . (($port) ? ':' . $port : ''); - $this->dbname = $database; - - $this->sql_layer = 'mysql4'; - - $this->db_connect_id = ($this->persistency) ? @mysql_pconnect($this->server, $this->user, $sqlpassword) : @mysql_connect($this->server, $this->user, $sqlpassword, $new_link); - - if ($this->db_connect_id && $this->dbname != '') - { - if (@mysql_select_db($this->dbname, $this->db_connect_id)) - { - // Determine what version we are using and if it natively supports UNICODE - if (version_compare($this->sql_server_info(true), '4.1.0', '>=')) - { - @mysql_query("SET NAMES 'utf8'", $this->db_connect_id); - - // enforce strict mode on databases that support it - if (version_compare($this->sql_server_info(true), '5.0.2', '>=')) - { - $result = @mysql_query('SELECT @@session.sql_mode AS sql_mode', $this->db_connect_id); - $row = @mysql_fetch_assoc($result); - @mysql_free_result($result); - $modes = array_map('trim', explode(',', $row['sql_mode'])); - - // TRADITIONAL includes STRICT_ALL_TABLES and STRICT_TRANS_TABLES - if (!in_array('TRADITIONAL', $modes)) - { - if (!in_array('STRICT_ALL_TABLES', $modes)) - { - $modes[] = 'STRICT_ALL_TABLES'; - } - - if (!in_array('STRICT_TRANS_TABLES', $modes)) - { - $modes[] = 'STRICT_TRANS_TABLES'; - } - } - - $mode = implode(',', $modes); - @mysql_query("SET SESSION sql_mode='{$mode}'", $this->db_connect_id); - } - } - else if (version_compare($this->sql_server_info(true), '4.0.0', '<')) - { - $this->sql_layer = 'mysql'; - } - - return $this->db_connect_id; - } - } - - return $this->sql_error(''); - } - - /** - * Version information about used database - * @param bool $raw if true, only return the fetched sql_server_version - * @param bool $use_cache If true, it is safe to retrieve the value from the cache - * @return string sql server version - */ - function sql_server_info($raw = false, $use_cache = true) - { - global $cache; - - if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mysql_version')) === false) - { - $result = @mysql_query('SELECT VERSION() AS version', $this->db_connect_id); - $row = @mysql_fetch_assoc($result); - @mysql_free_result($result); - - $this->sql_server_version = $row['version']; - - if (!empty($cache) && $use_cache) - { - $cache->put('mysql_version', $this->sql_server_version); - } - } - - return ($raw) ? $this->sql_server_version : 'MySQL ' . $this->sql_server_version; - } - - /** - * {@inheritDoc} - */ - public function sql_concatenate($expr1, $expr2) - { - return 'CONCAT(' . $expr1 . ', ' . $expr2 . ')'; - } - - /** - * SQL Transaction - * @access private - */ - function _sql_transaction($status = 'begin') - { - switch ($status) - { - case 'begin': - return @mysql_query('BEGIN', $this->db_connect_id); - break; - - case 'commit': - return @mysql_query('COMMIT', $this->db_connect_id); - break; - - case 'rollback': - return @mysql_query('ROLLBACK', $this->db_connect_id); - break; - } - - return true; - } - - /** - * Base query method - * - * @param string $query Contains the SQL query which shall be executed - * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache - * @return mixed When casted to bool the returned value returns true on success and false on failure - * - * @access public - */ - function sql_query($query = '', $cache_ttl = 0) - { - if ($query != '') - { - global $cache; - - // EXPLAIN only in extra debug mode - if (defined('DEBUG_EXTRA')) - { - $this->sql_report('start', $query); - } - - $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; - $this->sql_add_num_queries($this->query_result); - - if ($this->query_result === false) - { - if (($this->query_result = @mysql_query($query, $this->db_connect_id)) === false) - { - $this->sql_error($query); - } - - if (defined('DEBUG_EXTRA')) - { - $this->sql_report('stop', $query); - } - - if ($cache_ttl && method_exists($cache, 'sql_save')) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - $cache->sql_save($query, $this->query_result, $cache_ttl); - } - else if (strpos($query, 'SELECT') === 0 && $this->query_result) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - } - } - else if (defined('DEBUG_EXTRA')) - { - $this->sql_report('fromcache', $query); - } - } - else - { - return false; - } - - return $this->query_result; - } - - /** - * Build LIMIT query - */ - function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) - { - $this->query_result = false; - - // if $total is set to 0 we do not want to limit the number of rows - if ($total == 0) - { - // Having a value of -1 was always a bug - $total = '18446744073709551615'; - } - - $query .= "\n LIMIT " . ((!empty($offset)) ? $offset . ', ' . $total : $total); - - return $this->sql_query($query, $cache_ttl); - } - - /** - * Return number of affected rows - */ - function sql_affectedrows() - { - return ($this->db_connect_id) ? @mysql_affected_rows($this->db_connect_id) : false; - } - - /** - * Fetch current row - */ - function sql_fetchrow($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_fetchrow($query_id); - } - - return ($query_id !== false) ? @mysql_fetch_assoc($query_id) : false; - } - - /** - * Seek to given row number - * rownum is zero-based - */ - function sql_rowseek($rownum, &$query_id) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_rowseek($rownum, $query_id); - } - - return ($query_id !== false) ? @mysql_data_seek($query_id, $rownum) : false; - } - - /** - * Get last inserted id after insert statement - */ - function sql_nextid() - { - return ($this->db_connect_id) ? @mysql_insert_id($this->db_connect_id) : false; - } - - /** - * Free sql result - */ - function sql_freeresult($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_freeresult($query_id); - } - - if (isset($this->open_queries[(int) $query_id])) - { - unset($this->open_queries[(int) $query_id]); - return @mysql_free_result($query_id); - } - - return false; - } - - /** - * Escape string used in sql query - */ - function sql_escape($msg) - { - if (!$this->db_connect_id) - { - return @mysql_real_escape_string($msg); - } - - return @mysql_real_escape_string($msg, $this->db_connect_id); - } - - /** - * Gets the estimated number of rows in a specified table. - * - * @param string $table_name Table name - * - * @return string Number of rows in $table_name. - * Prefixed with ~ if estimated (otherwise exact). - * - * @access public - */ - function get_estimated_row_count($table_name) - { - $table_status = $this->get_table_status($table_name); - - if (isset($table_status['Engine'])) - { - if ($table_status['Engine'] === 'MyISAM') - { - return $table_status['Rows']; - } - else if ($table_status['Engine'] === 'InnoDB' && $table_status['Rows'] > 100000) - { - return '~' . $table_status['Rows']; - } - } - - return parent::get_row_count($table_name); - } - - /** - * Gets the exact number of rows in a specified table. - * - * @param string $table_name Table name - * - * @return string Exact number of rows in $table_name. - * - * @access public - */ - function get_row_count($table_name) - { - $table_status = $this->get_table_status($table_name); - - if (isset($table_status['Engine']) && $table_status['Engine'] === 'MyISAM') - { - return $table_status['Rows']; - } - - return parent::get_row_count($table_name); - } - - /** - * Gets some information about the specified table. - * - * @param string $table_name Table name - * - * @return array - * - * @access protected - */ - function get_table_status($table_name) - { - $sql = "SHOW TABLE STATUS - LIKE '" . $this->sql_escape($table_name) . "'"; - $result = $this->sql_query($sql); - $table_status = $this->sql_fetchrow($result); - $this->sql_freeresult($result); - - return $table_status; - } - - /** - * Build LIKE expression - * @access private - */ - function _sql_like_expression($expression) - { - return $expression; - } - - /** - * Build db-specific query data - * @access private - */ - function _sql_custom_build($stage, $data) - { - switch ($stage) - { - case 'FROM': - $data = '(' . $data . ')'; - break; - } - - return $data; - } - - /** - * return sql error array - * @access private - */ - function _sql_error() - { - if (!$this->db_connect_id) - { - return array( - 'message' => @mysql_error(), - 'code' => @mysql_errno() - ); - } - - return array( - 'message' => @mysql_error($this->db_connect_id), - 'code' => @mysql_errno($this->db_connect_id) - ); - } - - /** - * Close sql connection - * @access private - */ - function _sql_close() - { - return @mysql_close($this->db_connect_id); - } - - /** - * Build db-specific report - * @access private - */ - function _sql_report($mode, $query = '') - { - static $test_prof; - - // current detection method, might just switch to see the existance of INFORMATION_SCHEMA.PROFILING - if ($test_prof === null) - { - $test_prof = false; - if (version_compare($this->sql_server_info(true), '5.0.37', '>=') && version_compare($this->sql_server_info(true), '5.1', '<')) - { - $test_prof = true; - } - } - - switch ($mode) - { - case 'start': - - $explain_query = $query; - if (preg_match('/UPDATE ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) - { - $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; - } - else if (preg_match('/DELETE FROM ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) - { - $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; - } - - if (preg_match('/^SELECT/', $explain_query)) - { - $html_table = false; - - // begin profiling - if ($test_prof) - { - @mysql_query('SET profiling = 1;', $this->db_connect_id); - } - - if ($result = @mysql_query("EXPLAIN $explain_query", $this->db_connect_id)) - { - while ($row = @mysql_fetch_assoc($result)) - { - $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); - } - } - @mysql_free_result($result); - - if ($html_table) - { - $this->html_hold .= ''; - } - - if ($test_prof) - { - $html_table = false; - - // get the last profile - if ($result = @mysql_query('SHOW PROFILE ALL;', $this->db_connect_id)) - { - $this->html_hold .= '
'; - while ($row = @mysql_fetch_assoc($result)) - { - // make HTML safe - if (!empty($row['Source_function'])) - { - $row['Source_function'] = str_replace(array('<', '>'), array('<', '>'), $row['Source_function']); - } - - // remove unsupported features - foreach ($row as $key => $val) - { - if ($val === null) - { - unset($row[$key]); - } - } - $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); - } - } - @mysql_free_result($result); - - if ($html_table) - { - $this->html_hold .= ''; - } - - @mysql_query('SET profiling = 0;', $this->db_connect_id); - } - } - - break; - - case 'fromcache': - $endtime = explode(' ', microtime()); - $endtime = $endtime[0] + $endtime[1]; - - $result = @mysql_query($query, $this->db_connect_id); - while ($void = @mysql_fetch_assoc($result)) - { - // Take the time spent on parsing rows into account - } - @mysql_free_result($result); - - $splittime = explode(' ', microtime()); - $splittime = $splittime[0] + $splittime[1]; - - $this->sql_report('record_fromcache', $query, $endtime, $splittime); - - break; - } - } -} diff --git a/phpBB/includes/db/mysqli.php b/phpBB/includes/db/mysqli.php deleted file mode 100644 index e07cd35e24..0000000000 --- a/phpBB/includes/db/mysqli.php +++ /dev/null @@ -1,568 +0,0 @@ -persistency = (version_compare(PHP_VERSION, '5.3.0', '>=')) ? $persistency : false; - $this->user = $sqluser; - - // If persistent connection, set dbhost to localhost when empty and prepend it with 'p:' prefix - $this->server = ($this->persistency) ? 'p:' . (($sqlserver) ? $sqlserver : 'localhost') : $sqlserver; - - $this->dbname = $database; - $port = (!$port) ? NULL : $port; - - // If port is set and it is not numeric, most likely mysqli socket is set. - // Try to map it to the $socket parameter. - $socket = NULL; - if ($port) - { - if (is_numeric($port)) - { - $port = (int) $port; - } - else - { - $socket = $port; - $port = NULL; - } - } - - $this->db_connect_id = @mysqli_connect($this->server, $this->user, $sqlpassword, $this->dbname, $port, $socket); - - if ($this->db_connect_id && $this->dbname != '') - { - @mysqli_query($this->db_connect_id, "SET NAMES 'utf8'"); - - // enforce strict mode on databases that support it - if (version_compare($this->sql_server_info(true), '5.0.2', '>=')) - { - $result = @mysqli_query($this->db_connect_id, 'SELECT @@session.sql_mode AS sql_mode'); - $row = @mysqli_fetch_assoc($result); - @mysqli_free_result($result); - - $modes = array_map('trim', explode(',', $row['sql_mode'])); - - // TRADITIONAL includes STRICT_ALL_TABLES and STRICT_TRANS_TABLES - if (!in_array('TRADITIONAL', $modes)) - { - if (!in_array('STRICT_ALL_TABLES', $modes)) - { - $modes[] = 'STRICT_ALL_TABLES'; - } - - if (!in_array('STRICT_TRANS_TABLES', $modes)) - { - $modes[] = 'STRICT_TRANS_TABLES'; - } - } - - $mode = implode(',', $modes); - @mysqli_query($this->db_connect_id, "SET SESSION sql_mode='{$mode}'"); - } - return $this->db_connect_id; - } - - return $this->sql_error(''); - } - - /** - * Version information about used database - * @param bool $use_cache If true, it is safe to retrieve the value from the cache - * @return string sql server version - */ - function sql_server_info($raw = false, $use_cache = true) - { - global $cache; - - if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mysqli_version')) === false) - { - $result = @mysqli_query($this->db_connect_id, 'SELECT VERSION() AS version'); - $row = @mysqli_fetch_assoc($result); - @mysqli_free_result($result); - - $this->sql_server_version = $row['version']; - - if (!empty($cache) && $use_cache) - { - $cache->put('mysqli_version', $this->sql_server_version); - } - } - - return ($raw) ? $this->sql_server_version : 'MySQL(i) ' . $this->sql_server_version; - } - - /** - * {@inheritDoc} - */ - public function sql_concatenate($expr1, $expr2) - { - return 'CONCAT(' . $expr1 . ', ' . $expr2 . ')'; - } - - /** - * SQL Transaction - * @access private - */ - function _sql_transaction($status = 'begin') - { - switch ($status) - { - case 'begin': - return @mysqli_autocommit($this->db_connect_id, false); - break; - - case 'commit': - $result = @mysqli_commit($this->db_connect_id); - @mysqli_autocommit($this->db_connect_id, true); - return $result; - break; - - case 'rollback': - $result = @mysqli_rollback($this->db_connect_id); - @mysqli_autocommit($this->db_connect_id, true); - return $result; - break; - } - - return true; - } - - /** - * Base query method - * - * @param string $query Contains the SQL query which shall be executed - * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache - * @return mixed When casted to bool the returned value returns true on success and false on failure - * - * @access public - */ - function sql_query($query = '', $cache_ttl = 0) - { - if ($query != '') - { - global $cache; - - // EXPLAIN only in extra debug mode - if (defined('DEBUG_EXTRA')) - { - $this->sql_report('start', $query); - } - - $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; - $this->sql_add_num_queries($this->query_result); - - if ($this->query_result === false) - { - if (($this->query_result = @mysqli_query($this->db_connect_id, $query)) === false) - { - $this->sql_error($query); - } - - if (defined('DEBUG_EXTRA')) - { - $this->sql_report('stop', $query); - } - - if ($cache_ttl && method_exists($cache, 'sql_save')) - { - $cache->sql_save($query, $this->query_result, $cache_ttl); - } - } - else if (defined('DEBUG_EXTRA')) - { - $this->sql_report('fromcache', $query); - } - } - else - { - return false; - } - - return $this->query_result; - } - - /** - * Build LIMIT query - */ - function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) - { - $this->query_result = false; - - // if $total is set to 0 we do not want to limit the number of rows - if ($total == 0) - { - // MySQL 4.1+ no longer supports -1 in limit queries - $total = '18446744073709551615'; - } - - $query .= "\n LIMIT " . ((!empty($offset)) ? $offset . ', ' . $total : $total); - - return $this->sql_query($query, $cache_ttl); - } - - /** - * Return number of affected rows - */ - function sql_affectedrows() - { - return ($this->db_connect_id) ? @mysqli_affected_rows($this->db_connect_id) : false; - } - - /** - * Fetch current row - */ - function sql_fetchrow($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (!is_object($query_id) && isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_fetchrow($query_id); - } - - if ($query_id !== false) - { - $result = @mysqli_fetch_assoc($query_id); - return $result !== null ? $result : false; - } - - return false; - } - - /** - * Seek to given row number - * rownum is zero-based - */ - function sql_rowseek($rownum, &$query_id) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (!is_object($query_id) && isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_rowseek($rownum, $query_id); - } - - return ($query_id !== false) ? @mysqli_data_seek($query_id, $rownum) : false; - } - - /** - * Get last inserted id after insert statement - */ - function sql_nextid() - { - return ($this->db_connect_id) ? @mysqli_insert_id($this->db_connect_id) : false; - } - - /** - * Free sql result - */ - function sql_freeresult($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (!is_object($query_id) && isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_freeresult($query_id); - } - - return @mysqli_free_result($query_id); - } - - /** - * Escape string used in sql query - */ - function sql_escape($msg) - { - return @mysqli_real_escape_string($this->db_connect_id, $msg); - } - - /** - * Gets the estimated number of rows in a specified table. - * - * @param string $table_name Table name - * - * @return string Number of rows in $table_name. - * Prefixed with ~ if estimated (otherwise exact). - * - * @access public - */ - function get_estimated_row_count($table_name) - { - $table_status = $this->get_table_status($table_name); - - if (isset($table_status['Engine'])) - { - if ($table_status['Engine'] === 'MyISAM') - { - return $table_status['Rows']; - } - else if ($table_status['Engine'] === 'InnoDB' && $table_status['Rows'] > 100000) - { - return '~' . $table_status['Rows']; - } - } - - return parent::get_row_count($table_name); - } - - /** - * Gets the exact number of rows in a specified table. - * - * @param string $table_name Table name - * - * @return string Exact number of rows in $table_name. - * - * @access public - */ - function get_row_count($table_name) - { - $table_status = $this->get_table_status($table_name); - - if (isset($table_status['Engine']) && $table_status['Engine'] === 'MyISAM') - { - return $table_status['Rows']; - } - - return parent::get_row_count($table_name); - } - - /** - * Gets some information about the specified table. - * - * @param string $table_name Table name - * - * @return array - * - * @access protected - */ - function get_table_status($table_name) - { - $sql = "SHOW TABLE STATUS - LIKE '" . $this->sql_escape($table_name) . "'"; - $result = $this->sql_query($sql); - $table_status = $this->sql_fetchrow($result); - $this->sql_freeresult($result); - - return $table_status; - } - - /** - * Build LIKE expression - * @access private - */ - function _sql_like_expression($expression) - { - return $expression; - } - - /** - * Build db-specific query data - * @access private - */ - function _sql_custom_build($stage, $data) - { - switch ($stage) - { - case 'FROM': - $data = '(' . $data . ')'; - break; - } - - return $data; - } - - /** - * return sql error array - * @access private - */ - function _sql_error() - { - if (!$this->db_connect_id) - { - return array( - 'message' => @mysqli_connect_error(), - 'code' => @mysqli_connect_errno() - ); - } - - return array( - 'message' => @mysqli_error($this->db_connect_id), - 'code' => @mysqli_errno($this->db_connect_id) - ); - } - - /** - * Close sql connection - * @access private - */ - function _sql_close() - { - return @mysqli_close($this->db_connect_id); - } - - /** - * Build db-specific report - * @access private - */ - function _sql_report($mode, $query = '') - { - static $test_prof; - - // current detection method, might just switch to see the existance of INFORMATION_SCHEMA.PROFILING - if ($test_prof === null) - { - $test_prof = false; - if (strpos(mysqli_get_server_info($this->db_connect_id), 'community') !== false) - { - $ver = mysqli_get_server_version($this->db_connect_id); - if ($ver >= 50037 && $ver < 50100) - { - $test_prof = true; - } - } - } - - switch ($mode) - { - case 'start': - - $explain_query = $query; - if (preg_match('/UPDATE ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) - { - $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; - } - else if (preg_match('/DELETE FROM ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) - { - $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; - } - - if (preg_match('/^SELECT/', $explain_query)) - { - $html_table = false; - - // begin profiling - if ($test_prof) - { - @mysqli_query($this->db_connect_id, 'SET profiling = 1;'); - } - - if ($result = @mysqli_query($this->db_connect_id, "EXPLAIN $explain_query")) - { - while ($row = @mysqli_fetch_assoc($result)) - { - $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); - } - } - @mysqli_free_result($result); - - if ($html_table) - { - $this->html_hold .= ''; - } - - if ($test_prof) - { - $html_table = false; - - // get the last profile - if ($result = @mysqli_query($this->db_connect_id, 'SHOW PROFILE ALL;')) - { - $this->html_hold .= '
'; - while ($row = @mysqli_fetch_assoc($result)) - { - // make HTML safe - if (!empty($row['Source_function'])) - { - $row['Source_function'] = str_replace(array('<', '>'), array('<', '>'), $row['Source_function']); - } - - // remove unsupported features - foreach ($row as $key => $val) - { - if ($val === null) - { - unset($row[$key]); - } - } - $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); - } - } - @mysqli_free_result($result); - - if ($html_table) - { - $this->html_hold .= ''; - } - - @mysqli_query($this->db_connect_id, 'SET profiling = 0;'); - } - } - - break; - - case 'fromcache': - $endtime = explode(' ', microtime()); - $endtime = $endtime[0] + $endtime[1]; - - $result = @mysqli_query($this->db_connect_id, $query); - while ($void = @mysqli_fetch_assoc($result)) - { - // Take the time spent on parsing rows into account - } - @mysqli_free_result($result); - - $splittime = explode(' ', microtime()); - $splittime = $splittime[0] + $splittime[1]; - - $this->sql_report('record_fromcache', $query, $endtime, $splittime); - - break; - } - } -} diff --git a/phpBB/includes/db/oracle.php b/phpBB/includes/db/oracle.php deleted file mode 100644 index 2e801532f0..0000000000 --- a/phpBB/includes/db/oracle.php +++ /dev/null @@ -1,768 +0,0 @@ -persistency = $persistency; - $this->user = $sqluser; - $this->server = $sqlserver . (($port) ? ':' . $port : ''); - $this->dbname = $database; - - $connect = $database; - - // support for "easy connect naming" - if ($sqlserver !== '' && $sqlserver !== '/') - { - if (substr($sqlserver, -1, 1) == '/') - { - $sqlserver == substr($sqlserver, 0, -1); - } - $connect = $sqlserver . (($port) ? ':' . $port : '') . '/' . $database; - } - - $this->db_connect_id = ($new_link) ? @ocinlogon($this->user, $sqlpassword, $connect, 'UTF8') : (($this->persistency) ? @ociplogon($this->user, $sqlpassword, $connect, 'UTF8') : @ocilogon($this->user, $sqlpassword, $connect, 'UTF8')); - - return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); - } - - /** - * Version information about used database - * @param bool $raw if true, only return the fetched sql_server_version - * @param bool $use_cache forced to false for Oracle - * @return string sql server version - */ - function sql_server_info($raw = false, $use_cache = true) - { - /** - * force $use_cache false. I didn't research why the caching code below is commented out - * but I assume its because the Oracle extension provides a direct method to access it - * without a query. - */ - - $use_cache = false; -/* - global $cache; - - if (empty($cache) || ($this->sql_server_version = $cache->get('oracle_version')) === false) - { - $result = @ociparse($this->db_connect_id, 'SELECT * FROM v$version WHERE banner LIKE \'Oracle%\''); - @ociexecute($result, OCI_DEFAULT); - @ocicommit($this->db_connect_id); - - $row = array(); - @ocifetchinto($result, $row, OCI_ASSOC + OCI_RETURN_NULLS); - @ocifreestatement($result); - $this->sql_server_version = trim($row['BANNER']); - - $cache->put('oracle_version', $this->sql_server_version); - } -*/ - $this->sql_server_version = @ociserverversion($this->db_connect_id); - - return $this->sql_server_version; - } - - /** - * SQL Transaction - * @access private - */ - function _sql_transaction($status = 'begin') - { - switch ($status) - { - case 'begin': - return true; - break; - - case 'commit': - return @ocicommit($this->db_connect_id); - break; - - case 'rollback': - return @ocirollback($this->db_connect_id); - break; - } - - return true; - } - - /** - * Oracle specific code to handle the fact that it does not compare columns properly - * @access private - */ - function _rewrite_col_compare($args) - { - if (sizeof($args) == 4) - { - if ($args[2] == '=') - { - return '(' . $args[0] . ' OR (' . $args[1] . ' is NULL AND ' . $args[3] . ' is NULL))'; - } - else if ($args[2] == '<>') - { - // really just a fancy way of saying foo <> bar or (foo is NULL XOR bar is NULL) but SQL has no XOR :P - return '(' . $args[0] . ' OR ((' . $args[1] . ' is NULL AND ' . $args[3] . ' is NOT NULL) OR (' . $args[1] . ' is NOT NULL AND ' . $args[3] . ' is NULL)))'; - } - } - else - { - return $this->_rewrite_where($args[0]); - } - } - - /** - * Oracle specific code to handle it's lack of sanity - * @access private - */ - function _rewrite_where($where_clause) - { - preg_match_all('/\s*(AND|OR)?\s*([\w_.()]++)\s*(?:(=|<[=>]?|>=?|LIKE)\s*((?>\'(?>[^\']++|\'\')*+\'|[\d-.()]+))|((NOT )?IN\s*\((?>\'(?>[^\']++|\'\')*+\',? ?|[\d-.]+,? ?)*+\)))/', $where_clause, $result, PREG_SET_ORDER); - $out = ''; - foreach ($result as $val) - { - if (!isset($val[5])) - { - if ($val[4] !== "''") - { - $out .= $val[0]; - } - else - { - $out .= ' ' . $val[1] . ' ' . $val[2]; - if ($val[3] == '=') - { - $out .= ' is NULL'; - } - else if ($val[3] == '<>') - { - $out .= ' is NOT NULL'; - } - } - } - else - { - $in_clause = array(); - $sub_exp = substr($val[5], strpos($val[5], '(') + 1, -1); - $extra = false; - preg_match_all('/\'(?>[^\']++|\'\')*+\'|[\d-.]++/', $sub_exp, $sub_vals, PREG_PATTERN_ORDER); - $i = 0; - foreach ($sub_vals[0] as $sub_val) - { - // two things: - // 1) This determines if an empty string was in the IN clausing, making us turn it into a NULL comparison - // 2) This fixes the 1000 list limit that Oracle has (ORA-01795) - if ($sub_val !== "''") - { - $in_clause[(int) $i++/1000][] = $sub_val; - } - else - { - $extra = true; - } - } - if (!$extra && $i < 1000) - { - $out .= $val[0]; - } - else - { - $out .= ' ' . $val[1] . '('; - $in_array = array(); - - // constuct each IN() clause - foreach ($in_clause as $in_values) - { - $in_array[] = $val[2] . ' ' . (isset($val[6]) ? $val[6] : '') . 'IN(' . implode(', ', $in_values) . ')'; - } - - // Join the IN() clauses against a few ORs (IN is just a nicer OR anyway) - $out .= implode(' OR ', $in_array); - - // handle the empty string case - if ($extra) - { - $out .= ' OR ' . $val[2] . ' is ' . (isset($val[6]) ? $val[6] : '') . 'NULL'; - } - $out .= ')'; - - unset($in_array, $in_clause); - } - } - } - - return $out; - } - - /** - * Base query method - * - * @param string $query Contains the SQL query which shall be executed - * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache - * @return mixed When casted to bool the returned value returns true on success and false on failure - * - * @access public - */ - function sql_query($query = '', $cache_ttl = 0) - { - if ($query != '') - { - global $cache; - - // EXPLAIN only in extra debug mode - if (defined('DEBUG_EXTRA')) - { - $this->sql_report('start', $query); - } - - $this->last_query_text = $query; - $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; - $this->sql_add_num_queries($this->query_result); - - if ($this->query_result === false) - { - $in_transaction = false; - if (!$this->transaction) - { - $this->sql_transaction('begin'); - } - else - { - $in_transaction = true; - } - - $array = array(); - - // We overcome Oracle's 4000 char limit by binding vars - if (strlen($query) > 4000) - { - if (preg_match('/^(INSERT INTO[^(]++)\\(([^()]+)\\) VALUES[^(]++\\((.*?)\\)$/sU', $query, $regs)) - { - if (strlen($regs[3]) > 4000) - { - $cols = explode(', ', $regs[2]); - - preg_match_all('/\'(?:[^\']++|\'\')*+\'|[\d-.]+/', $regs[3], $vals, PREG_PATTERN_ORDER); - -/* The code inside this comment block breaks clob handling, but does allow the - database restore script to work. If you want to allow no posts longer than 4KB - and/or need the db restore script, uncomment this. - - - if (sizeof($cols) !== sizeof($vals)) - { - // Try to replace some common data we know is from our restore script or from other sources - $regs[3] = str_replace("'||chr(47)||'", '/', $regs[3]); - $_vals = explode(', ', $regs[3]); - - $vals = array(); - $is_in_val = false; - $i = 0; - $string = ''; - - foreach ($_vals as $value) - { - if (strpos($value, "'") === false && !$is_in_val) - { - $vals[$i++] = $value; - continue; - } - - if (substr($value, -1) === "'") - { - $vals[$i] = $string . (($is_in_val) ? ', ' : '') . $value; - $string = ''; - $is_in_val = false; - - if ($vals[$i][0] !== "'") - { - $vals[$i] = "''" . $vals[$i]; - } - $i++; - continue; - } - else - { - $string .= (($is_in_val) ? ', ' : '') . $value; - $is_in_val = true; - } - } - - if ($string) - { - // New value if cols != value - $vals[(sizeof($cols) !== sizeof($vals)) ? $i : $i - 1] .= $string; - } - - $vals = array(0 => $vals); - } -*/ - - $inserts = $vals[0]; - unset($vals); - - foreach ($inserts as $key => $value) - { - if (!empty($value) && $value[0] === "'" && strlen($value) > 4002) // check to see if this thing is greater than the max + 'x2 - { - $inserts[$key] = ':' . strtoupper($cols[$key]); - $array[$inserts[$key]] = str_replace("''", "'", substr($value, 1, -1)); - } - } - - $query = $regs[1] . '(' . $regs[2] . ') VALUES (' . implode(', ', $inserts) . ')'; - } - } - else if (preg_match_all('/^(UPDATE [\\w_]++\\s+SET )([\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+)(?:,\\s*[\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+))*+)\\s+(WHERE.*)$/s', $query, $data, PREG_SET_ORDER)) - { - if (strlen($data[0][2]) > 4000) - { - $update = $data[0][1]; - $where = $data[0][3]; - preg_match_all('/([\\w_]++)\\s*=\\s*(\'(?:[^\']++|\'\')*+\'|[\d-.]++)/', $data[0][2], $temp, PREG_SET_ORDER); - unset($data); - - $cols = array(); - foreach ($temp as $value) - { - if (!empty($value[2]) && $value[2][0] === "'" && strlen($value[2]) > 4002) // check to see if this thing is greater than the max + 'x2 - { - $cols[] = $value[1] . '=:' . strtoupper($value[1]); - $array[$value[1]] = str_replace("''", "'", substr($value[2], 1, -1)); - } - else - { - $cols[] = $value[1] . '=' . $value[2]; - } - } - - $query = $update . implode(', ', $cols) . ' ' . $where; - unset($cols); - } - } - } - - switch (substr($query, 0, 6)) - { - case 'DELETE': - if (preg_match('/^(DELETE FROM [\w_]++ WHERE)((?:\s*(?:AND|OR)?\s*[\w_]+\s*(?:(?:=|<>)\s*(?>\'(?>[^\']++|\'\')*+\'|[\d-.]+)|(?:NOT )?IN\s*\((?>\'(?>[^\']++|\'\')*+\',? ?|[\d-.]+,? ?)*+\)))*+)$/', $query, $regs)) - { - $query = $regs[1] . $this->_rewrite_where($regs[2]); - unset($regs); - } - break; - - case 'UPDATE': - if (preg_match('/^(UPDATE [\\w_]++\\s+SET [\\w_]+\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]++|:\w++)(?:, [\\w_]+\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]++|:\w++))*+\\s+WHERE)(.*)$/s', $query, $regs)) - { - $query = $regs[1] . $this->_rewrite_where($regs[2]); - unset($regs); - } - break; - - case 'SELECT': - $query = preg_replace_callback('/([\w_.]++)\s*(?:(=|<>)\s*(?>\'(?>[^\']++|\'\')*+\'|[\d-.]++|([\w_.]++))|(?:NOT )?IN\s*\((?>\'(?>[^\']++|\'\')*+\',? ?|[\d-.]++,? ?)*+\))/', array($this, '_rewrite_col_compare'), $query); - break; - } - - $this->query_result = @ociparse($this->db_connect_id, $query); - - foreach ($array as $key => $value) - { - @ocibindbyname($this->query_result, $key, $array[$key], -1); - } - - $success = @ociexecute($this->query_result, OCI_DEFAULT); - - if (!$success) - { - $this->sql_error($query); - $this->query_result = false; - } - else - { - if (!$in_transaction) - { - $this->sql_transaction('commit'); - } - } - - if (defined('DEBUG_EXTRA')) - { - $this->sql_report('stop', $query); - } - - if ($cache_ttl && method_exists($cache, 'sql_save')) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - $cache->sql_save($query, $this->query_result, $cache_ttl); - } - else if (strpos($query, 'SELECT') === 0 && $this->query_result) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - } - } - else if (defined('DEBUG_EXTRA')) - { - $this->sql_report('fromcache', $query); - } - } - else - { - return false; - } - - return $this->query_result; - } - - /** - * Build LIMIT query - */ - function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) - { - $this->query_result = false; - - $query = 'SELECT * FROM (SELECT /*+ FIRST_ROWS */ rownum AS xrownum, a.* FROM (' . $query . ') a WHERE rownum <= ' . ($offset + $total) . ') WHERE xrownum >= ' . $offset; - - return $this->sql_query($query, $cache_ttl); - } - - /** - * Return number of affected rows - */ - function sql_affectedrows() - { - return ($this->query_result) ? @ocirowcount($this->query_result) : false; - } - - /** - * Fetch current row - */ - function sql_fetchrow($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_fetchrow($query_id); - } - - if ($query_id !== false) - { - $row = array(); - $result = @ocifetchinto($query_id, $row, OCI_ASSOC + OCI_RETURN_NULLS); - - if (!$result || !$row) - { - return false; - } - - $result_row = array(); - foreach ($row as $key => $value) - { - // Oracle treats empty strings as null - if (is_null($value)) - { - $value = ''; - } - - // OCI->CLOB? - if (is_object($value)) - { - $value = $value->load(); - } - - $result_row[strtolower($key)] = $value; - } - - return $result_row; - } - - return false; - } - - /** - * Seek to given row number - * rownum is zero-based - */ - function sql_rowseek($rownum, &$query_id) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_rowseek($rownum, $query_id); - } - - if ($query_id === false) - { - return false; - } - - // Reset internal pointer - @ociexecute($query_id, OCI_DEFAULT); - - // We do not fetch the row for rownum == 0 because then the next resultset would be the second row - for ($i = 0; $i < $rownum; $i++) - { - if (!$this->sql_fetchrow($query_id)) - { - return false; - } - } - - return true; - } - - /** - * Get last inserted id after insert statement - */ - function sql_nextid() - { - $query_id = $this->query_result; - - if ($query_id !== false && $this->last_query_text != '') - { - if (preg_match('#^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)#is', $this->last_query_text, $tablename)) - { - $query = 'SELECT ' . $tablename[1] . '_seq.currval FROM DUAL'; - $stmt = @ociparse($this->db_connect_id, $query); - @ociexecute($stmt, OCI_DEFAULT); - - $temp_result = @ocifetchinto($stmt, $temp_array, OCI_ASSOC + OCI_RETURN_NULLS); - @ocifreestatement($stmt); - - if ($temp_result) - { - return $temp_array['CURRVAL']; - } - else - { - return false; - } - } - } - - return false; - } - - /** - * Free sql result - */ - function sql_freeresult($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_freeresult($query_id); - } - - if (isset($this->open_queries[(int) $query_id])) - { - unset($this->open_queries[(int) $query_id]); - return @ocifreestatement($query_id); - } - - return false; - } - - /** - * Escape string used in sql query - */ - function sql_escape($msg) - { - return str_replace(array("'", "\0"), array("''", ''), $msg); - } - - /** - * Build LIKE expression - * @access private - */ - function _sql_like_expression($expression) - { - return $expression . " ESCAPE '\\'"; - } - - function _sql_custom_build($stage, $data) - { - return $data; - } - - function _sql_bit_and($column_name, $bit, $compare = '') - { - return 'BITAND(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : ''); - } - - function _sql_bit_or($column_name, $bit, $compare = '') - { - return 'BITOR(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : ''); - } - - /** - * return sql error array - * @access private - */ - function _sql_error() - { - $error = @ocierror(); - $error = (!$error) ? @ocierror($this->query_result) : $error; - $error = (!$error) ? @ocierror($this->db_connect_id) : $error; - - if ($error) - { - $this->last_error_result = $error; - } - else - { - $error = (isset($this->last_error_result) && $this->last_error_result) ? $this->last_error_result : array(); - } - - return $error; - } - - /** - * Close sql connection - * @access private - */ - function _sql_close() - { - return @ocilogoff($this->db_connect_id); - } - - /** - * Build db-specific report - * @access private - */ - function _sql_report($mode, $query = '') - { - switch ($mode) - { - case 'start': - - $html_table = false; - - // Grab a plan table, any will do - $sql = "SELECT table_name - FROM USER_TABLES - WHERE table_name LIKE '%PLAN_TABLE%'"; - $stmt = ociparse($this->db_connect_id, $sql); - ociexecute($stmt); - $result = array(); - - if (ocifetchinto($stmt, $result, OCI_ASSOC + OCI_RETURN_NULLS)) - { - $table = $result['TABLE_NAME']; - - // This is the statement_id that will allow us to track the plan - $statement_id = substr(md5($query), 0, 30); - - // Remove any stale plans - $stmt2 = ociparse($this->db_connect_id, "DELETE FROM $table WHERE statement_id='$statement_id'"); - ociexecute($stmt2); - ocifreestatement($stmt2); - - // Explain the plan - $sql = "EXPLAIN PLAN - SET STATEMENT_ID = '$statement_id' - FOR $query"; - $stmt2 = ociparse($this->db_connect_id, $sql); - ociexecute($stmt2); - ocifreestatement($stmt2); - - // Get the data from the plan - $sql = "SELECT operation, options, object_name, object_type, cardinality, cost - FROM plan_table - START WITH id = 0 AND statement_id = '$statement_id' - CONNECT BY PRIOR id = parent_id - AND statement_id = '$statement_id'"; - $stmt2 = ociparse($this->db_connect_id, $sql); - ociexecute($stmt2); - - $row = array(); - while (ocifetchinto($stmt2, $row, OCI_ASSOC + OCI_RETURN_NULLS)) - { - $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); - } - - ocifreestatement($stmt2); - - // Remove the plan we just made, we delete them on request anyway - $stmt2 = ociparse($this->db_connect_id, "DELETE FROM $table WHERE statement_id='$statement_id'"); - ociexecute($stmt2); - ocifreestatement($stmt2); - } - - ocifreestatement($stmt); - - if ($html_table) - { - $this->html_hold .= ''; - } - - break; - - case 'fromcache': - $endtime = explode(' ', microtime()); - $endtime = $endtime[0] + $endtime[1]; - - $result = @ociparse($this->db_connect_id, $query); - $success = @ociexecute($result, OCI_DEFAULT); - $row = array(); - - while (@ocifetchinto($result, $row, OCI_ASSOC + OCI_RETURN_NULLS)) - { - // Take the time spent on parsing rows into account - } - @ocifreestatement($result); - - $splittime = explode(' ', microtime()); - $splittime = $splittime[0] + $splittime[1]; - - $this->sql_report('record_fromcache', $query, $endtime, $splittime); - - break; - } - } -} diff --git a/phpBB/includes/db/postgres.php b/phpBB/includes/db/postgres.php deleted file mode 100644 index bf22cffafa..0000000000 --- a/phpBB/includes/db/postgres.php +++ /dev/null @@ -1,498 +0,0 @@ -dbname = $database; - if (strpos($database, '.') !== false) - { - list($database, $schema) = explode('.', $database); - } - $connect_string .= "dbname=$database"; - } - - $this->persistency = $persistency; - - if ($this->persistency) - { - if (!function_exists('pg_pconnect')) - { - $this->connect_error = 'pg_pconnect function does not exist, is pgsql extension installed?'; - return $this->sql_error(''); - } - $collector = new phpbb_error_collector; - $collector->install(); - $this->db_connect_id = (!$new_link) ? @pg_pconnect($connect_string) : @pg_pconnect($connect_string, PGSQL_CONNECT_FORCE_NEW); - } - else - { - if (!function_exists('pg_connect')) - { - $this->connect_error = 'pg_connect function does not exist, is pgsql extension installed?'; - return $this->sql_error(''); - } - $collector = new phpbb_error_collector; - $collector->install(); - $this->db_connect_id = (!$new_link) ? @pg_connect($connect_string) : @pg_connect($connect_string, PGSQL_CONNECT_FORCE_NEW); - } - - $collector->uninstall(); - - if ($this->db_connect_id) - { - if (version_compare($this->sql_server_info(true), '8.2', '>=')) - { - $this->multi_insert = true; - } - - if ($schema !== '') - { - @pg_query($this->db_connect_id, 'SET search_path TO ' . $schema); - } - return $this->db_connect_id; - } - - $this->connect_error = $collector->format_errors(); - return $this->sql_error(''); - } - - /** - * Version information about used database - * @param bool $raw if true, only return the fetched sql_server_version - * @param bool $use_cache If true, it is safe to retrieve the value from the cache - * @return string sql server version - */ - function sql_server_info($raw = false, $use_cache = true) - { - global $cache; - - if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('pgsql_version')) === false) - { - $query_id = @pg_query($this->db_connect_id, 'SELECT VERSION() AS version'); - $row = @pg_fetch_assoc($query_id, null); - @pg_free_result($query_id); - - $this->sql_server_version = (!empty($row['version'])) ? trim(substr($row['version'], 10)) : 0; - - if (!empty($cache) && $use_cache) - { - $cache->put('pgsql_version', $this->sql_server_version); - } - } - - return ($raw) ? $this->sql_server_version : 'PostgreSQL ' . $this->sql_server_version; - } - - /** - * SQL Transaction - * @access private - */ - function _sql_transaction($status = 'begin') - { - switch ($status) - { - case 'begin': - return @pg_query($this->db_connect_id, 'BEGIN'); - break; - - case 'commit': - return @pg_query($this->db_connect_id, 'COMMIT'); - break; - - case 'rollback': - return @pg_query($this->db_connect_id, 'ROLLBACK'); - break; - } - - return true; - } - - /** - * Base query method - * - * @param string $query Contains the SQL query which shall be executed - * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache - * @return mixed When casted to bool the returned value returns true on success and false on failure - * - * @access public - */ - function sql_query($query = '', $cache_ttl = 0) - { - if ($query != '') - { - global $cache; - - // EXPLAIN only in extra debug mode - if (defined('DEBUG_EXTRA')) - { - $this->sql_report('start', $query); - } - - $this->last_query_text = $query; - $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; - $this->sql_add_num_queries($this->query_result); - - if ($this->query_result === false) - { - if (($this->query_result = @pg_query($this->db_connect_id, $query)) === false) - { - $this->sql_error($query); - } - - if (defined('DEBUG_EXTRA')) - { - $this->sql_report('stop', $query); - } - - if ($cache_ttl && method_exists($cache, 'sql_save')) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - $cache->sql_save($query, $this->query_result, $cache_ttl); - } - else if (strpos($query, 'SELECT') === 0 && $this->query_result) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - } - } - else if (defined('DEBUG_EXTRA')) - { - $this->sql_report('fromcache', $query); - } - } - else - { - return false; - } - - return $this->query_result; - } - - /** - * Build db-specific query data - * @access private - */ - function _sql_custom_build($stage, $data) - { - return $data; - } - - /** - * Build LIMIT query - */ - function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) - { - $this->query_result = false; - - // if $total is set to 0 we do not want to limit the number of rows - if ($total == 0) - { - $total = 'ALL'; - } - - $query .= "\n LIMIT $total OFFSET $offset"; - - return $this->sql_query($query, $cache_ttl); - } - - /** - * Return number of affected rows - */ - function sql_affectedrows() - { - return ($this->query_result) ? @pg_affected_rows($this->query_result) : false; - } - - /** - * Fetch current row - */ - function sql_fetchrow($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_fetchrow($query_id); - } - - return ($query_id !== false) ? @pg_fetch_assoc($query_id, null) : false; - } - - /** - * Seek to given row number - * rownum is zero-based - */ - function sql_rowseek($rownum, &$query_id) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_rowseek($rownum, $query_id); - } - - return ($query_id !== false) ? @pg_result_seek($query_id, $rownum) : false; - } - - /** - * Get last inserted id after insert statement - */ - function sql_nextid() - { - $query_id = $this->query_result; - - if ($query_id !== false && $this->last_query_text != '') - { - if (preg_match("/^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)/is", $this->last_query_text, $tablename)) - { - $query = "SELECT currval('" . $tablename[1] . "_seq') AS last_value"; - $temp_q_id = @pg_query($this->db_connect_id, $query); - - if (!$temp_q_id) - { - return false; - } - - $temp_result = @pg_fetch_assoc($temp_q_id, NULL); - @pg_free_result($query_id); - - return ($temp_result) ? $temp_result['last_value'] : false; - } - } - - return false; - } - - /** - * Free sql result - */ - function sql_freeresult($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_freeresult($query_id); - } - - if (isset($this->open_queries[(int) $query_id])) - { - unset($this->open_queries[(int) $query_id]); - return @pg_free_result($query_id); - } - - return false; - } - - /** - * Escape string used in sql query - * Note: Do not use for bytea values if we may use them at a later stage - */ - function sql_escape($msg) - { - return @pg_escape_string($msg); - } - - /** - * Build LIKE expression - * @access private - */ - function _sql_like_expression($expression) - { - return $expression; - } - - /** - * @inheritdoc - */ - function cast_expr_to_bigint($expression) - { - return 'CAST(' . $expression . ' as DECIMAL(255, 0))'; - } - - /** - * @inheritdoc - */ - function cast_expr_to_string($expression) - { - return 'CAST(' . $expression . ' as VARCHAR(255))'; - } - - /** - * return sql error array - * @access private - */ - function _sql_error() - { - // pg_last_error only works when there is an established connection. - // Connection errors have to be tracked by us manually. - if ($this->db_connect_id) - { - $message = @pg_last_error($this->db_connect_id); - } - else - { - $message = $this->connect_error; - } - - return array( - 'message' => $message, - 'code' => '' - ); - } - - /** - * Close sql connection - * @access private - */ - function _sql_close() - { - return @pg_close($this->db_connect_id); - } - - /** - * Build db-specific report - * @access private - */ - function _sql_report($mode, $query = '') - { - switch ($mode) - { - case 'start': - - $explain_query = $query; - if (preg_match('/UPDATE ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) - { - $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; - } - else if (preg_match('/DELETE FROM ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) - { - $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; - } - - if (preg_match('/^SELECT/', $explain_query)) - { - $html_table = false; - - if ($result = @pg_query($this->db_connect_id, "EXPLAIN $explain_query")) - { - while ($row = @pg_fetch_assoc($result, NULL)) - { - $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); - } - } - @pg_free_result($result); - - if ($html_table) - { - $this->html_hold .= ''; - } - } - - break; - - case 'fromcache': - $endtime = explode(' ', microtime()); - $endtime = $endtime[0] + $endtime[1]; - - $result = @pg_query($this->db_connect_id, $query); - while ($void = @pg_fetch_assoc($result, NULL)) - { - // Take the time spent on parsing rows into account - } - @pg_free_result($result); - - $splittime = explode(' ', microtime()); - $splittime = $splittime[0] + $splittime[1]; - - $this->sql_report('record_fromcache', $query, $endtime, $splittime); - - break; - } - } -} diff --git a/phpBB/includes/db/sqlite.php b/phpBB/includes/db/sqlite.php deleted file mode 100644 index 86bfa75a13..0000000000 --- a/phpBB/includes/db/sqlite.php +++ /dev/null @@ -1,336 +0,0 @@ -persistency = $persistency; - $this->user = $sqluser; - $this->server = $sqlserver . (($port) ? ':' . $port : ''); - $this->dbname = $database; - - $error = ''; - $this->db_connect_id = ($this->persistency) ? @sqlite_popen($this->server, 0666, $error) : @sqlite_open($this->server, 0666, $error); - - if ($this->db_connect_id) - { - @sqlite_query('PRAGMA short_column_names = 1', $this->db_connect_id); -// @sqlite_query('PRAGMA encoding = "UTF-8"', $this->db_connect_id); - } - - return ($this->db_connect_id) ? true : array('message' => $error); - } - - /** - * Version information about used database - * @param bool $raw if true, only return the fetched sql_server_version - * @param bool $use_cache if true, it is safe to retrieve the stored value from the cache - * @return string sql server version - */ - function sql_server_info($raw = false, $use_cache = true) - { - global $cache; - - if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('sqlite_version')) === false) - { - $result = @sqlite_query('SELECT sqlite_version() AS version', $this->db_connect_id); - $row = @sqlite_fetch_array($result, SQLITE_ASSOC); - - $this->sql_server_version = (!empty($row['version'])) ? $row['version'] : 0; - - if (!empty($cache) && $use_cache) - { - $cache->put('sqlite_version', $this->sql_server_version); - } - } - - return ($raw) ? $this->sql_server_version : 'SQLite ' . $this->sql_server_version; - } - - /** - * SQL Transaction - * @access private - */ - function _sql_transaction($status = 'begin') - { - switch ($status) - { - case 'begin': - return @sqlite_query('BEGIN', $this->db_connect_id); - break; - - case 'commit': - return @sqlite_query('COMMIT', $this->db_connect_id); - break; - - case 'rollback': - return @sqlite_query('ROLLBACK', $this->db_connect_id); - break; - } - - return true; - } - - /** - * Base query method - * - * @param string $query Contains the SQL query which shall be executed - * @param int $cache_ttl Either 0 to avoid caching or the time in seconds which the result shall be kept in cache - * @return mixed When casted to bool the returned value returns true on success and false on failure - * - * @access public - */ - function sql_query($query = '', $cache_ttl = 0) - { - if ($query != '') - { - global $cache; - - // EXPLAIN only in extra debug mode - if (defined('DEBUG_EXTRA')) - { - $this->sql_report('start', $query); - } - - $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; - $this->sql_add_num_queries($this->query_result); - - if ($this->query_result === false) - { - if (($this->query_result = @sqlite_query($query, $this->db_connect_id)) === false) - { - $this->sql_error($query); - } - - if (defined('DEBUG_EXTRA')) - { - $this->sql_report('stop', $query); - } - - if ($cache_ttl && method_exists($cache, 'sql_save')) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - $cache->sql_save($query, $this->query_result, $cache_ttl); - } - else if (strpos($query, 'SELECT') === 0 && $this->query_result) - { - $this->open_queries[(int) $this->query_result] = $this->query_result; - } - } - else if (defined('DEBUG_EXTRA')) - { - $this->sql_report('fromcache', $query); - } - } - else - { - return false; - } - - return $this->query_result; - } - - /** - * Build LIMIT query - */ - function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) - { - $this->query_result = false; - - // if $total is set to 0 we do not want to limit the number of rows - if ($total == 0) - { - $total = -1; - } - - $query .= "\n LIMIT " . ((!empty($offset)) ? $offset . ', ' . $total : $total); - - return $this->sql_query($query, $cache_ttl); - } - - /** - * Return number of affected rows - */ - function sql_affectedrows() - { - return ($this->db_connect_id) ? @sqlite_changes($this->db_connect_id) : false; - } - - /** - * Fetch current row - */ - function sql_fetchrow($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_fetchrow($query_id); - } - - return ($query_id !== false) ? @sqlite_fetch_array($query_id, SQLITE_ASSOC) : false; - } - - /** - * Seek to given row number - * rownum is zero-based - */ - function sql_rowseek($rownum, &$query_id) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_rowseek($rownum, $query_id); - } - - return ($query_id !== false) ? @sqlite_seek($query_id, $rownum) : false; - } - - /** - * Get last inserted id after insert statement - */ - function sql_nextid() - { - return ($this->db_connect_id) ? @sqlite_last_insert_rowid($this->db_connect_id) : false; - } - - /** - * Free sql result - */ - function sql_freeresult($query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if (isset($cache->sql_rowset[$query_id])) - { - return $cache->sql_freeresult($query_id); - } - - return true; - } - - /** - * Escape string used in sql query - */ - function sql_escape($msg) - { - return @sqlite_escape_string($msg); - } - - /** - * Correctly adjust LIKE expression for special characters - * For SQLite an underscore is a not-known character... this may change with SQLite3 - */ - function sql_like_expression($expression) - { - // Unlike LIKE, GLOB is case sensitive (unfortunatly). SQLite users need to live with it! - // We only catch * and ? here, not the character map possible on file globbing. - $expression = str_replace(array(chr(0) . '_', chr(0) . '%'), array(chr(0) . '?', chr(0) . '*'), $expression); - - $expression = str_replace(array('?', '*'), array("\?", "\*"), $expression); - $expression = str_replace(array(chr(0) . "\?", chr(0) . "\*"), array('?', '*'), $expression); - - return 'GLOB \'' . $this->sql_escape($expression) . '\''; - } - - /** - * return sql error array - * @access private - */ - function _sql_error() - { - return array( - 'message' => @sqlite_error_string(@sqlite_last_error($this->db_connect_id)), - 'code' => @sqlite_last_error($this->db_connect_id) - ); - } - - /** - * Build db-specific query data - * @access private - */ - function _sql_custom_build($stage, $data) - { - return $data; - } - - /** - * Close sql connection - * @access private - */ - function _sql_close() - { - return @sqlite_close($this->db_connect_id); - } - - /** - * Build db-specific report - * @access private - */ - function _sql_report($mode, $query = '') - { - switch ($mode) - { - case 'start': - break; - - case 'fromcache': - $endtime = explode(' ', microtime()); - $endtime = $endtime[0] + $endtime[1]; - - $result = @sqlite_query($query, $this->db_connect_id); - while ($void = @sqlite_fetch_array($result, SQLITE_ASSOC)) - { - // Take the time spent on parsing rows into account - } - - $splittime = explode(' ', microtime()); - $splittime = $splittime[0] + $splittime[1]; - - $this->sql_report('record_fromcache', $query, $endtime, $splittime); - - break; - } - } -} diff --git a/phpBB/includes/extension/manager.php b/phpBB/includes/extension/manager.php index 537c19aff8..c3d1f58893 100644 --- a/phpBB/includes/extension/manager.php +++ b/phpBB/includes/extension/manager.php @@ -39,7 +39,7 @@ class phpbb_extension_manager * @param phpbb_cache_driver_interface $cache A cache instance or null * @param string $cache_name The name of the cache variable, defaults to _ext */ - public function __construct(dbal $db, $extension_table, $phpbb_root_path, $phpEx = '.php', phpbb_cache_driver_interface $cache = null, $cache_name = '_ext') + public function __construct(phpbb_db_driver $db, $extension_table, $phpbb_root_path, $phpEx = '.php', phpbb_cache_driver_interface $cache = null, $cache_name = '_ext') { $this->phpbb_root_path = $phpbb_root_path; $this->db = $db; @@ -432,7 +432,7 @@ class phpbb_extension_manager } return $disabled; } - + /** * Check to see if a given extension is available on the filesystem * diff --git a/phpBB/includes/functions_install.php b/phpBB/includes/functions_install.php index 46541acd44..413d8481b0 100644 --- a/phpBB/includes/functions_install.php +++ b/phpBB/includes/functions_install.php @@ -49,7 +49,7 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20 'SCHEMA' => 'firebird', 'MODULE' => 'interbase', 'DELIM' => ';;', - 'DRIVER' => 'firebird', + 'DRIVER' => 'phpbb_db_driver_firebird', 'AVAILABLE' => true, '2.0.x' => false, ), @@ -58,7 +58,7 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20 'SCHEMA' => 'mysql_41', 'MODULE' => 'mysqli', 'DELIM' => ';', - 'DRIVER' => 'mysqli', + 'DRIVER' => 'phpbb_db_driver_mysqli', 'AVAILABLE' => true, '2.0.x' => true, ), @@ -67,7 +67,7 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20 'SCHEMA' => 'mysql', 'MODULE' => 'mysql', 'DELIM' => ';', - 'DRIVER' => 'mysql', + 'DRIVER' => 'phpbb_db_driver_mysql', 'AVAILABLE' => true, '2.0.x' => true, ), @@ -76,7 +76,7 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20 'SCHEMA' => 'mssql', 'MODULE' => 'mssql', 'DELIM' => 'GO', - 'DRIVER' => 'mssql', + 'DRIVER' => 'phpbb_db_driver_mssql', 'AVAILABLE' => true, '2.0.x' => true, ), @@ -85,7 +85,7 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20 'SCHEMA' => 'mssql', 'MODULE' => 'odbc', 'DELIM' => 'GO', - 'DRIVER' => 'mssql_odbc', + 'DRIVER' => 'phpbb_db_driver_mssql_odbc', 'AVAILABLE' => true, '2.0.x' => true, ), @@ -94,7 +94,7 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20 'SCHEMA' => 'mssql', 'MODULE' => 'sqlsrv', 'DELIM' => 'GO', - 'DRIVER' => 'mssqlnative', + 'DRIVER' => 'phpbb_db_driver_mssqlnative', 'AVAILABLE' => true, '2.0.x' => false, ), @@ -103,7 +103,7 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20 'SCHEMA' => 'oracle', 'MODULE' => 'oci8', 'DELIM' => '/', - 'DRIVER' => 'oracle', + 'DRIVER' => 'phpbb_db_driver_oracle', 'AVAILABLE' => true, '2.0.x' => false, ), @@ -112,7 +112,7 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20 'SCHEMA' => 'postgres', 'MODULE' => 'pgsql', 'DELIM' => ';', - 'DRIVER' => 'postgres', + 'DRIVER' => 'phpbb_db_driver_postgres', 'AVAILABLE' => true, '2.0.x' => true, ), @@ -121,7 +121,7 @@ function get_available_dbms($dbms = false, $return_unavailable = false, $only_20 'SCHEMA' => 'sqlite', 'MODULE' => 'sqlite', 'DELIM' => ';', - 'DRIVER' => 'sqlite', + 'DRIVER' => 'phpbb_db_driver_sqlite', 'AVAILABLE' => true, '2.0.x' => false, ), @@ -229,26 +229,19 @@ function connect_check_db($error_connect, &$error, $dbms_details, $table_prefix, $dbms = $dbms_details['DRIVER']; - if ($load_dbal) - { - // Include the DB layer - include($phpbb_root_path . 'includes/db/' . $dbms . '.' . $phpEx); - } - // Instantiate it and set return on error true - $sql_db = 'dbal_' . $dbms; - $db = new $sql_db(); + $db = new $dbms(); $db->sql_return_on_error(true); // Check that we actually have a database name before going any further..... - if ($dbms_details['DRIVER'] != 'sqlite' && $dbms_details['DRIVER'] != 'oracle' && $dbname === '') + if ($dbms_details['DRIVER'] != 'phpbb_db_driver_sqlite' && $dbms_details['DRIVER'] != 'phpbb_db_driver_oracle' && $dbname === '') { $error[] = $lang['INST_ERR_DB_NO_NAME']; return false; } // Make sure we don't have a daft user who thinks having the SQLite database in the forum directory is a good idea - if ($dbms_details['DRIVER'] == 'sqlite' && stripos(phpbb_realpath($dbhost), phpbb_realpath('../')) === 0) + if ($dbms_details['DRIVER'] == 'phpbb_db_driver_sqlite' && stripos(phpbb_realpath($dbhost), phpbb_realpath('../')) === 0) { $error[] = $lang['INST_ERR_DB_FORUM_PATH']; return false; @@ -257,8 +250,8 @@ function connect_check_db($error_connect, &$error, $dbms_details, $table_prefix, // Check the prefix length to ensure that index names are not too long and does not contain invalid characters switch ($dbms_details['DRIVER']) { - case 'mysql': - case 'mysqli': + case 'phpbb_db_driver_mysql': + case 'phpbb_db_driver_mysqli': if (strspn($table_prefix, '-./\\') !== 0) { $error[] = $lang['INST_ERR_PREFIX_INVALID']; @@ -267,22 +260,22 @@ function connect_check_db($error_connect, &$error, $dbms_details, $table_prefix, // no break; - case 'postgres': + case 'phpbb_db_driver_postgres': $prefix_length = 36; break; - case 'mssql': - case 'mssql_odbc': - case 'mssqlnative': + case 'phpbb_db_driver_mssql': + case 'phpbb_db_driver_mssql_odbc': + case 'phpbb_db_driver_mssqlnative': $prefix_length = 90; break; - case 'sqlite': + case 'phpbb_db_driver_sqlite': $prefix_length = 200; break; - case 'firebird': - case 'oracle': + case 'phpbb_db_driver_firebird': + case 'phpbb_db_driver_oracle': $prefix_length = 6; break; } @@ -320,21 +313,21 @@ function connect_check_db($error_connect, &$error, $dbms_details, $table_prefix, // Make sure that the user has selected a sensible DBAL for the DBMS actually installed switch ($dbms_details['DRIVER']) { - case 'mysqli': + case 'phpbb_db_driver_mysqli': if (version_compare(mysqli_get_server_info($db->db_connect_id), '4.1.3', '<')) { $error[] = $lang['INST_ERR_DB_NO_MYSQLI']; } break; - case 'sqlite': + case 'phpbb_db_driver_sqlite': if (version_compare(sqlite_libversion(), '2.8.2', '<')) { $error[] = $lang['INST_ERR_DB_NO_SQLITE']; } break; - case 'firebird': + case 'phpbb_db_driver_firebird': // check the version of FB, use some hackery if we can't get access to the server info if ($db->service_handle !== false && function_exists('ibase_server_info')) { @@ -415,7 +408,7 @@ function connect_check_db($error_connect, &$error, $dbms_details, $table_prefix, } break; - case 'oracle': + case 'phpbb_db_driver_oracle': if ($unicode_check) { $sql = "SELECT * @@ -437,7 +430,7 @@ function connect_check_db($error_connect, &$error, $dbms_details, $table_prefix, } break; - case 'postgres': + case 'phpbb_db_driver_postgres': if ($unicode_check) { $sql = "SHOW server_encoding;"; -- cgit v1.2.1 From 65bafb22810038fd51e22fd8168775afd86c2e74 Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sat, 21 Jul 2012 18:08:40 +0200 Subject: [ticket/11015] Add BC files for the drivers PHPBB3-11015 --- phpBB/includes/db/firebird.php | 19 +++++++++++++++++++ phpBB/includes/db/mssql.php | 19 +++++++++++++++++++ phpBB/includes/db/mssql_odbc.php | 19 +++++++++++++++++++ phpBB/includes/db/mssqlnative.php | 19 +++++++++++++++++++ phpBB/includes/db/mysql.php | 19 +++++++++++++++++++ phpBB/includes/db/mysqli.php | 19 +++++++++++++++++++ phpBB/includes/db/oracle.php | 19 +++++++++++++++++++ phpBB/includes/db/postgres.php | 19 +++++++++++++++++++ phpBB/includes/db/sqlite.php | 19 +++++++++++++++++++ 9 files changed, 171 insertions(+) create mode 100644 phpBB/includes/db/firebird.php create mode 100644 phpBB/includes/db/mssql.php create mode 100644 phpBB/includes/db/mssql_odbc.php create mode 100644 phpBB/includes/db/mssqlnative.php create mode 100644 phpBB/includes/db/mysql.php create mode 100644 phpBB/includes/db/mysqli.php create mode 100644 phpBB/includes/db/oracle.php create mode 100644 phpBB/includes/db/postgres.php create mode 100644 phpBB/includes/db/sqlite.php (limited to 'phpBB/includes') diff --git a/phpBB/includes/db/firebird.php b/phpBB/includes/db/firebird.php new file mode 100644 index 0000000000..33f4e2ea48 --- /dev/null +++ b/phpBB/includes/db/firebird.php @@ -0,0 +1,19 @@ + Date: Sat, 21 Jul 2012 18:24:24 +0200 Subject: [ticket/11015] Correctly set sql_layer in driver base class PHPBB3-11015 --- phpBB/includes/db/driver/driver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/db/driver/driver.php b/phpBB/includes/db/driver/driver.php index 39211a5af4..9692ee71b9 100644 --- a/phpBB/includes/db/driver/driver.php +++ b/phpBB/includes/db/driver/driver.php @@ -82,7 +82,7 @@ class phpbb_db_driver // Fill default sql layer based on the class being called. // This can be changed by the specified layer itself later if needed. - $this->sql_layer = substr(get_class($this), 5); + $this->sql_layer = substr(get_class($this), strlen('phpbb_db_driver_')); // Do not change this please! This variable is used to easy the use of it - and is hardcoded. $this->any_char = chr(0) . '%'; -- cgit v1.2.1 From c34ca32795f29c944c840f8282c025e4b322c891 Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sat, 21 Jul 2012 19:35:45 +0200 Subject: [ticket/11015] Fix db connection type hint in lock_db PHPBB3-11015 --- phpBB/includes/lock/db.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/lock/db.php b/phpBB/includes/lock/db.php index fa559d6887..40690144b1 100644 --- a/phpBB/includes/lock/db.php +++ b/phpBB/includes/lock/db.php @@ -61,7 +61,7 @@ class phpbb_lock_db * @param array $config The phpBB configuration * @param dbal $db A database connection */ - public function __construct($config_name, phpbb_config $config, dbal $db) + public function __construct($config_name, phpbb_config $config, phpbb_db_driver $db) { $this->config_name = $config_name; $this->config = $config; -- cgit v1.2.1 From fd6ee50e06cb48c9e3a476bf23285875484ff5f7 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Fri, 2 Nov 2012 16:05:53 -0400 Subject: [ticket/11162] Extract existing behavior into a function and add a test. PHPBB3-11162 --- phpBB/includes/functions.php | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 65d8be32ad..8608c6ca4f 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -1297,6 +1297,28 @@ function tz_select($default = '', $truncate = false) return $tz_select; } +/** +* Updates rows in given table from a set of values to a new value. +* If this results in rows violating uniqueness constraints, the duplicate +* rows are eliminated. +* +* @param dbal $db Database object +* @param string $table Table on which to perform the update +* @param string $column Column whose values to change +* @param array $from_values An array of values that should be changed +* @param int $to_value The new value +* @return null +*/ +function phpbb_update_rows_avoiding_duplicates($db, $table, $column, $from_values, $to_value) +{ + $db->sql_return_on_error(true); + $condition = $db->sql_in_set($column, $from_values); + $db->sql_query('UPDATE ' . $table . ' SET ' . $column . ' = ' . (int) $to_value. ' WHERE ' . $condition); + $db->sql_return_on_error(false); + + $db->sql_query('DELETE FROM ' . $table . ' WHERE ' . $condition); +} + // Functions handling topic/post tracking/marking /** @@ -3355,7 +3377,7 @@ function parse_cfg_file($filename, $lines = false) $parsed_items[$key] = $value; } - + if (isset($parsed_items['inherit_from']) && isset($parsed_items['name']) && $parsed_items['inherit_from'] == $parsed_items['name']) { unset($parsed_items['inherit_from']); -- cgit v1.2.1 From 9bc9ac281af9f194d73160ae3545105f24db5395 Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Mon, 12 Nov 2012 10:38:28 +0100 Subject: [ticket/11015] Remove old dbal classes PHPBB3-11015 --- phpBB/includes/db/firebird.php | 19 ------------------- phpBB/includes/db/mssql.php | 19 ------------------- phpBB/includes/db/mssql_odbc.php | 19 ------------------- phpBB/includes/db/mssqlnative.php | 19 ------------------- phpBB/includes/db/mysql.php | 19 ------------------- phpBB/includes/db/mysqli.php | 19 ------------------- phpBB/includes/db/oracle.php | 19 ------------------- phpBB/includes/db/postgres.php | 19 ------------------- phpBB/includes/db/sqlite.php | 19 ------------------- 9 files changed, 171 deletions(-) delete mode 100644 phpBB/includes/db/firebird.php delete mode 100644 phpBB/includes/db/mssql.php delete mode 100644 phpBB/includes/db/mssql_odbc.php delete mode 100644 phpBB/includes/db/mssqlnative.php delete mode 100644 phpBB/includes/db/mysql.php delete mode 100644 phpBB/includes/db/mysqli.php delete mode 100644 phpBB/includes/db/oracle.php delete mode 100644 phpBB/includes/db/postgres.php delete mode 100644 phpBB/includes/db/sqlite.php (limited to 'phpBB/includes') diff --git a/phpBB/includes/db/firebird.php b/phpBB/includes/db/firebird.php deleted file mode 100644 index 33f4e2ea48..0000000000 --- a/phpBB/includes/db/firebird.php +++ /dev/null @@ -1,19 +0,0 @@ - Date: Mon, 12 Nov 2012 11:10:25 +0100 Subject: [ticket/11015] Fixup some things from the big merge PHPBB3-11015 --- phpBB/includes/cache/driver/file.php | 2 +- phpBB/includes/cache/driver/interface.php | 2 +- phpBB/includes/cache/driver/memory.php | 2 +- phpBB/includes/cache/driver/null.php | 2 +- phpBB/includes/cache/service.php | 40 -- phpBB/includes/db/dbal.php | 1049 ----------------------------- phpBB/includes/db/driver/driver.php | 8 +- phpBB/includes/db/driver/firebird.php | 16 +- phpBB/includes/db/driver/mssql.php | 16 +- phpBB/includes/db/driver/mssql_odbc.php | 16 +- phpBB/includes/db/driver/mssqlnative.php | 17 +- phpBB/includes/db/driver/mysql.php | 18 +- phpBB/includes/db/driver/mysqli.php | 18 +- phpBB/includes/db/driver/oracle.php | 18 +- phpBB/includes/db/driver/postgres.php | 18 +- phpBB/includes/db/driver/sqlite.php | 18 +- 16 files changed, 85 insertions(+), 1175 deletions(-) delete mode 100644 phpBB/includes/db/dbal.php (limited to 'phpBB/includes') diff --git a/phpBB/includes/cache/driver/file.php b/phpBB/includes/cache/driver/file.php index 23063a1a28..b20c0064ea 100644 --- a/phpBB/includes/cache/driver/file.php +++ b/phpBB/includes/cache/driver/file.php @@ -368,7 +368,7 @@ class phpbb_cache_driver_file extends phpbb_cache_driver_base /** * Save sql query */ - function sql_save($query, &$query_result, $ttl) + function sql_save($query, $query_result, $ttl) { global $db; diff --git a/phpBB/includes/cache/driver/interface.php b/phpBB/includes/cache/driver/interface.php index 313a2d4b31..847ba97262 100644 --- a/phpBB/includes/cache/driver/interface.php +++ b/phpBB/includes/cache/driver/interface.php @@ -75,7 +75,7 @@ interface phpbb_cache_driver_interface /** * Save sql query */ - public function sql_save($query, &$query_result, $ttl); + public function sql_save($query, $query_result, $ttl); /** * Ceck if a given sql query exist in cache diff --git a/phpBB/includes/cache/driver/memory.php b/phpBB/includes/cache/driver/memory.php index 623ae44144..98ac02b161 100644 --- a/phpBB/includes/cache/driver/memory.php +++ b/phpBB/includes/cache/driver/memory.php @@ -284,7 +284,7 @@ abstract class phpbb_cache_driver_memory extends phpbb_cache_driver_base /** * Save sql query */ - function sql_save($query, &$query_result, $ttl) + function sql_save($query, $query_result, $ttl) { global $db; diff --git a/phpBB/includes/cache/driver/null.php b/phpBB/includes/cache/driver/null.php index c143803d0e..df2c6c026f 100644 --- a/phpBB/includes/cache/driver/null.php +++ b/phpBB/includes/cache/driver/null.php @@ -107,7 +107,7 @@ class phpbb_cache_driver_null extends phpbb_cache_driver_base /** * Save sql query */ - function sql_save($query, &$query_result, $ttl) + function sql_save($query, $query_result, $ttl) { } diff --git a/phpBB/includes/cache/service.php b/phpBB/includes/cache/service.php index 7858e27a5c..e63ec6e33a 100644 --- a/phpBB/includes/cache/service.php +++ b/phpBB/includes/cache/service.php @@ -58,11 +58,6 @@ class phpbb_cache_service return call_user_func_array(array($this->driver, $method), $arguments); } - public function __get($var) - { - return $this->driver->$var; - } - /** * Obtain list of naughty words and build preg style replacement arrays for use by the * calling script @@ -413,39 +408,4 @@ class phpbb_cache_service return $hook_files; } - - public function sql_load() - { - return call_user_func_array(array($this->driver, __FUNCTION__), func_get_args()); - } - - public function sql_save($query, &$query_result, $ttl) - { - return call_user_func_array(array($this->driver, __FUNCTION__), array($query, &$query_result, $ttl)); - } - - public function sql_exists() - { - return call_user_func_array(array($this->driver, __FUNCTION__), func_get_args()); - } - - public function sql_fetchrow() - { - return call_user_func_array(array($this->driver, __FUNCTION__), func_get_args()); - } - - public function sql_fetchfield() - { - return call_user_func_array(array($this->driver, __FUNCTION__), func_get_args()); - } - - public function sql_rowseek() - { - return call_user_func_array(array($this->driver, __FUNCTION__), func_get_args()); - } - - public function sql_freeresult() - { - return call_user_func_array(array($this->driver, __FUNCTION__), func_get_args()); - } } diff --git a/phpBB/includes/db/dbal.php b/phpBB/includes/db/dbal.php deleted file mode 100644 index ef1dd7d14d..0000000000 --- a/phpBB/includes/db/dbal.php +++ /dev/null @@ -1,1049 +0,0 @@ -num_queries = array( - 'cached' => 0, - 'normal' => 0, - 'total' => 0, - ); - - // Fill default sql layer based on the class being called. - // This can be changed by the specified layer itself later if needed. - $this->sql_layer = substr(get_class($this), 5); - - // Do not change this please! This variable is used to easy the use of it - and is hardcoded. - $this->any_char = chr(0) . '%'; - $this->one_char = chr(0) . '_'; - } - - /** - * return on error or display error message - */ - function sql_return_on_error($fail = false) - { - $this->sql_error_triggered = false; - $this->sql_error_sql = ''; - - $this->return_on_error = $fail; - } - - /** - * Return number of sql queries and cached sql queries used - */ - function sql_num_queries($cached = false) - { - return ($cached) ? $this->num_queries['cached'] : $this->num_queries['normal']; - } - - /** - * Add to query count - */ - function sql_add_num_queries($cached = false) - { - $this->num_queries['cached'] += ($cached !== false) ? 1 : 0; - $this->num_queries['normal'] += ($cached !== false) ? 0 : 1; - $this->num_queries['total'] += 1; - } - - /** - * DBAL garbage collection, close sql connection - */ - function sql_close() - { - if (!$this->db_connect_id) - { - return false; - } - - if ($this->transaction) - { - do - { - $this->sql_transaction('commit'); - } - while ($this->transaction); - } - - foreach ($this->open_queries as $query_id) - { - $this->sql_freeresult($query_id); - } - - // Connection closed correctly. Set db_connect_id to false to prevent errors - if ($result = $this->_sql_close()) - { - $this->db_connect_id = false; - } - - return $result; - } - - /** - * Build LIMIT query - * Doing some validation here. - */ - function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) - { - if (empty($query)) - { - return false; - } - - // Never use a negative total or offset - $total = ($total < 0) ? 0 : $total; - $offset = ($offset < 0) ? 0 : $offset; - - return $this->_sql_query_limit($query, $total, $offset, $cache_ttl); - } - - /** - * Fetch all rows - */ - function sql_fetchrowset($query_id = false) - { - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($query_id !== false) - { - $result = array(); - while ($row = $this->sql_fetchrow($query_id)) - { - $result[] = $row; - } - - return $result; - } - - return false; - } - - /** - * Seek to given row number - * rownum is zero-based - */ - function sql_rowseek($rownum, &$query_id) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache->sql_exists($query_id)) - { - return $cache->sql_rowseek($rownum, $query_id); - } - - if ($query_id === false) - { - return false; - } - - $this->sql_freeresult($query_id); - $query_id = $this->sql_query($this->last_query_text); - - if ($query_id === false) - { - return false; - } - - // We do not fetch the row for rownum == 0 because then the next resultset would be the second row - for ($i = 0; $i < $rownum; $i++) - { - if (!$this->sql_fetchrow($query_id)) - { - return false; - } - } - - return true; - } - - /** - * Fetch field - * if rownum is false, the current row is used, else it is pointing to the row (zero-based) - */ - function sql_fetchfield($field, $rownum = false, $query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($query_id !== false) - { - if ($rownum !== false) - { - $this->sql_rowseek($rownum, $query_id); - } - - if (!is_object($query_id) && $cache->sql_exists($query_id)) - { - return $cache->sql_fetchfield($query_id, $field); - } - - $row = $this->sql_fetchrow($query_id); - return (isset($row[$field])) ? $row[$field] : false; - } - - return false; - } - - /** - * Correctly adjust LIKE expression for special characters - * Some DBMS are handling them in a different way - * - * @param string $expression The expression to use. Every wildcard is escaped, except $this->any_char and $this->one_char - * @return string LIKE expression including the keyword! - */ - function sql_like_expression($expression) - { - $expression = utf8_str_replace(array('_', '%'), array("\_", "\%"), $expression); - $expression = utf8_str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression); - - return $this->_sql_like_expression('LIKE \'' . $this->sql_escape($expression) . '\''); - } - - /** - * Build a case expression - * - * Note: The two statements action_true and action_false must have the same data type (int, vchar, ...) in the database! - * - * @param string $condition The condition which must be true, to use action_true rather then action_else - * @param string $action_true SQL expression that is used, if the condition is true - * @param string $action_else SQL expression that is used, if the condition is false, optional - * @return string CASE expression including the condition and statements - */ - public function sql_case($condition, $action_true, $action_false = false) - { - $sql_case = 'CASE WHEN ' . $condition; - $sql_case .= ' THEN ' . $action_true; - $sql_case .= ($action_false !== false) ? ' ELSE ' . $action_false : ''; - $sql_case .= ' END'; - return $sql_case; - } - - /** - * Build a concatenated expression - * - * @param string $expr1 Base SQL expression where we append the second one - * @param string $expr2 SQL expression that is appended to the first expression - * @return string Concatenated string - */ - public function sql_concatenate($expr1, $expr2) - { - return $expr1 . ' || ' . $expr2; - } - - /** - * Returns whether results of a query need to be buffered to run a transaction while iterating over them. - * - * @return bool Whether buffering is required. - */ - function sql_buffer_nested_transactions() - { - return false; - } - - /** - * SQL Transaction - * @access private - */ - function sql_transaction($status = 'begin') - { - switch ($status) - { - case 'begin': - // If we are within a transaction we will not open another one, but enclose the current one to not loose data (prevening auto commit) - if ($this->transaction) - { - $this->transactions++; - return true; - } - - $result = $this->_sql_transaction('begin'); - - if (!$result) - { - $this->sql_error(); - } - - $this->transaction = true; - break; - - case 'commit': - // If there was a previously opened transaction we do not commit yet... but count back the number of inner transactions - if ($this->transaction && $this->transactions) - { - $this->transactions--; - return true; - } - - // Check if there is a transaction (no transaction can happen if there was an error, with a combined rollback and error returning enabled) - // This implies we have transaction always set for autocommit db's - if (!$this->transaction) - { - return false; - } - - $result = $this->_sql_transaction('commit'); - - if (!$result) - { - $this->sql_error(); - } - - $this->transaction = false; - $this->transactions = 0; - break; - - case 'rollback': - $result = $this->_sql_transaction('rollback'); - $this->transaction = false; - $this->transactions = 0; - break; - - default: - $result = $this->_sql_transaction($status); - break; - } - - return $result; - } - - /** - * Build sql statement from array for insert/update/select statements - * - * Idea for this from Ikonboard - * Possible query values: INSERT, INSERT_SELECT, UPDATE, SELECT - * - */ - function sql_build_array($query, $assoc_ary = false) - { - if (!is_array($assoc_ary)) - { - return false; - } - - $fields = $values = array(); - - if ($query == 'INSERT' || $query == 'INSERT_SELECT') - { - foreach ($assoc_ary as $key => $var) - { - $fields[] = $key; - - if (is_array($var) && is_string($var[0])) - { - // This is used for INSERT_SELECT(s) - $values[] = $var[0]; - } - else - { - $values[] = $this->_sql_validate_value($var); - } - } - - $query = ($query == 'INSERT') ? ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')' : ' (' . implode(', ', $fields) . ') SELECT ' . implode(', ', $values) . ' '; - } - else if ($query == 'MULTI_INSERT') - { - trigger_error('The MULTI_INSERT query value is no longer supported. Please use sql_multi_insert() instead.', E_USER_ERROR); - } - else if ($query == 'UPDATE' || $query == 'SELECT') - { - $values = array(); - foreach ($assoc_ary as $key => $var) - { - $values[] = "$key = " . $this->_sql_validate_value($var); - } - $query = implode(($query == 'UPDATE') ? ', ' : ' AND ', $values); - } - - return $query; - } - - /** - * Build IN or NOT IN sql comparison string, uses <> or = on single element - * arrays to improve comparison speed - * - * @access public - * @param string $field name of the sql column that shall be compared - * @param array $array array of values that are allowed (IN) or not allowed (NOT IN) - * @param bool $negate true for NOT IN (), false for IN () (default) - * @param bool $allow_empty_set If true, allow $array to be empty, this function will return 1=1 or 1=0 then. Default to false. - */ - function sql_in_set($field, $array, $negate = false, $allow_empty_set = false) - { - if (!sizeof($array)) - { - if (!$allow_empty_set) - { - // Print the backtrace to help identifying the location of the problematic code - $this->sql_error('No values specified for SQL IN comparison'); - } - else - { - // NOT IN () actually means everything so use a tautology - if ($negate) - { - return '1=1'; - } - // IN () actually means nothing so use a contradiction - else - { - return '1=0'; - } - } - } - - if (!is_array($array)) - { - $array = array($array); - } - - if (sizeof($array) == 1) - { - @reset($array); - $var = current($array); - - return $field . ($negate ? ' <> ' : ' = ') . $this->_sql_validate_value($var); - } - else - { - return $field . ($negate ? ' NOT IN ' : ' IN ') . '(' . implode(', ', array_map(array($this, '_sql_validate_value'), $array)) . ')'; - } - } - - /** - * Run binary AND operator on DB column. - * Results in sql statement: "{$column_name} & (1 << {$bit}) {$compare}" - * - * @param string $column_name The column name to use - * @param int $bit The value to use for the AND operator, will be converted to (1 << $bit). Is used by options, using the number schema... 0, 1, 2...29 - * @param string $compare Any custom SQL code after the check (for example "= 0") - */ - function sql_bit_and($column_name, $bit, $compare = '') - { - if (method_exists($this, '_sql_bit_and')) - { - return $this->_sql_bit_and($column_name, $bit, $compare); - } - - return $column_name . ' & ' . (1 << $bit) . (($compare) ? ' ' . $compare : ''); - } - - /** - * Run binary OR operator on DB column. - * Results in sql statement: "{$column_name} | (1 << {$bit}) {$compare}" - * - * @param string $column_name The column name to use - * @param int $bit The value to use for the OR operator, will be converted to (1 << $bit). Is used by options, using the number schema... 0, 1, 2...29 - * @param string $compare Any custom SQL code after the check (for example "= 0") - */ - function sql_bit_or($column_name, $bit, $compare = '') - { - if (method_exists($this, '_sql_bit_or')) - { - return $this->_sql_bit_or($column_name, $bit, $compare); - } - - return $column_name . ' | ' . (1 << $bit) . (($compare) ? ' ' . $compare : ''); - } - - /** - * Returns SQL string to cast a string expression to an int. - * - * @param string $expression An expression evaluating to string - * @return string Expression returning an int - */ - function cast_expr_to_bigint($expression) - { - return $expression; - } - - /** - * Returns SQL string to cast an integer expression to a string. - * - * @param string $expression An expression evaluating to int - * @return string Expression returning a string - */ - function cast_expr_to_string($expression) - { - return $expression; - } - - /** - * Run LOWER() on DB column of type text (i.e. neither varchar nor char). - * - * @param string $column_name The column name to use - * - * @return string A SQL statement like "LOWER($column_name)" - */ - function sql_lower_text($column_name) - { - return "LOWER($column_name)"; - } - - /** - * Run more than one insert statement. - * - * @param string $table table name to run the statements on - * @param array &$sql_ary multi-dimensional array holding the statement data. - * - * @return bool false if no statements were executed. - * @access public - */ - function sql_multi_insert($table, &$sql_ary) - { - if (!sizeof($sql_ary)) - { - return false; - } - - if ($this->multi_insert) - { - $ary = array(); - foreach ($sql_ary as $id => $_sql_ary) - { - // If by accident the sql array is only one-dimensional we build a normal insert statement - if (!is_array($_sql_ary)) - { - return $this->sql_query('INSERT INTO ' . $table . ' ' . $this->sql_build_array('INSERT', $sql_ary)); - } - - $values = array(); - foreach ($_sql_ary as $key => $var) - { - $values[] = $this->_sql_validate_value($var); - } - $ary[] = '(' . implode(', ', $values) . ')'; - } - - return $this->sql_query('INSERT INTO ' . $table . ' ' . ' (' . implode(', ', array_keys($sql_ary[0])) . ') VALUES ' . implode(', ', $ary)); - } - else - { - foreach ($sql_ary as $ary) - { - if (!is_array($ary)) - { - return false; - } - - $result = $this->sql_query('INSERT INTO ' . $table . ' ' . $this->sql_build_array('INSERT', $ary)); - - if (!$result) - { - return false; - } - } - } - - return true; - } - - /** - * Function for validating values - * @access private - */ - function _sql_validate_value($var) - { - if (is_null($var)) - { - return 'NULL'; - } - else if (is_string($var)) - { - return "'" . $this->sql_escape($var) . "'"; - } - else - { - return (is_bool($var)) ? intval($var) : $var; - } - } - - /** - * Build sql statement from array for select and select distinct statements - * - * Possible query values: SELECT, SELECT_DISTINCT - */ - function sql_build_query($query, $array) - { - $sql = ''; - switch ($query) - { - case 'SELECT': - case 'SELECT_DISTINCT'; - - $sql = str_replace('_', ' ', $query) . ' ' . $array['SELECT'] . ' FROM '; - - // Build table array. We also build an alias array for later checks. - $table_array = $aliases = array(); - $used_multi_alias = false; - - foreach ($array['FROM'] as $table_name => $alias) - { - if (is_array($alias)) - { - $used_multi_alias = true; - - foreach ($alias as $multi_alias) - { - $table_array[] = $table_name . ' ' . $multi_alias; - $aliases[] = $multi_alias; - } - } - else - { - $table_array[] = $table_name . ' ' . $alias; - $aliases[] = $alias; - } - } - - // We run the following code to determine if we need to re-order the table array. ;) - // The reason for this is that for multi-aliased tables (two equal tables) in the FROM statement the last table need to match the first comparison. - // DBMS who rely on this: Oracle, PostgreSQL and MSSQL. For all other DBMS it makes absolutely no difference in which order the table is. - if (!empty($array['LEFT_JOIN']) && sizeof($array['FROM']) > 1 && $used_multi_alias !== false) - { - // Take first LEFT JOIN - $join = current($array['LEFT_JOIN']); - - // Determine the table used there (even if there are more than one used, we only want to have one - preg_match('/(' . implode('|', $aliases) . ')\.[^\s]+/U', str_replace(array('(', ')', 'AND', 'OR', ' '), '', $join['ON']), $matches); - - // If there is a first join match, we need to make sure the table order is correct - if (!empty($matches[1])) - { - $first_join_match = trim($matches[1]); - $table_array = $last = array(); - - foreach ($array['FROM'] as $table_name => $alias) - { - if (is_array($alias)) - { - foreach ($alias as $multi_alias) - { - ($multi_alias === $first_join_match) ? $last[] = $table_name . ' ' . $multi_alias : $table_array[] = $table_name . ' ' . $multi_alias; - } - } - else - { - ($alias === $first_join_match) ? $last[] = $table_name . ' ' . $alias : $table_array[] = $table_name . ' ' . $alias; - } - } - - $table_array = array_merge($table_array, $last); - } - } - - $sql .= $this->_sql_custom_build('FROM', implode(' CROSS JOIN ', $table_array)); - - if (!empty($array['LEFT_JOIN'])) - { - foreach ($array['LEFT_JOIN'] as $join) - { - $sql .= ' LEFT JOIN ' . key($join['FROM']) . ' ' . current($join['FROM']) . ' ON (' . $join['ON'] . ')'; - } - } - - if (!empty($array['WHERE'])) - { - $sql .= ' WHERE ' . $this->_sql_custom_build('WHERE', $array['WHERE']); - } - - if (!empty($array['GROUP_BY'])) - { - $sql .= ' GROUP BY ' . $array['GROUP_BY']; - } - - if (!empty($array['ORDER_BY'])) - { - $sql .= ' ORDER BY ' . $array['ORDER_BY']; - } - - break; - } - - return $sql; - } - - /** - * display sql error page - */ - function sql_error($sql = '') - { - global $auth, $user, $config; - - // Set var to retrieve errored status - $this->sql_error_triggered = true; - $this->sql_error_sql = $sql; - - $this->sql_error_returned = $this->_sql_error(); - - if (!$this->return_on_error) - { - $message = 'SQL ERROR [ ' . $this->sql_layer . ' ]

' . $this->sql_error_returned['message'] . ' [' . $this->sql_error_returned['code'] . ']'; - - // Show complete SQL error and path to administrators only - // Additionally show complete error on installation or if extended debug mode is enabled - // The DEBUG constant is for development only! - if ((isset($auth) && $auth->acl_get('a_')) || defined('IN_INSTALL') || defined('DEBUG')) - { - $message .= ($sql) ? '

SQL

' . htmlspecialchars($sql) : ''; - } - else - { - // If error occurs in initiating the session we need to use a pre-defined language string - // This could happen if the connection could not be established for example (then we are not able to grab the default language) - if (!isset($user->lang['SQL_ERROR_OCCURRED'])) - { - $message .= '

An sql error occurred while fetching this page. Please contact an administrator if this problem persists.'; - } - else - { - if (!empty($config['board_contact'])) - { - $message .= '

' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '', ''); - } - else - { - $message .= '

' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '', ''); - } - } - } - - if ($this->transaction) - { - $this->sql_transaction('rollback'); - } - - if (strlen($message) > 1024) - { - // We need to define $msg_long_text here to circumvent text stripping. - global $msg_long_text; - $msg_long_text = $message; - - trigger_error(false, E_USER_ERROR); - } - - trigger_error($message, E_USER_ERROR); - } - - if ($this->transaction) - { - $this->sql_transaction('rollback'); - } - - return $this->sql_error_returned; - } - - /** - * Explain queries - */ - function sql_report($mode, $query = '') - { - global $cache, $starttime, $phpbb_root_path, $user; - global $request; - - if (is_object($request) && !$request->variable('explain', false)) - { - return false; - } - - if (!$query && $this->query_hold != '') - { - $query = $this->query_hold; - } - - switch ($mode) - { - case 'display': - if (!empty($cache)) - { - $cache->unload(); - } - $this->sql_close(); - - $mtime = explode(' ', microtime()); - $totaltime = $mtime[0] + $mtime[1] - $starttime; - - echo ' - - - - SQL Report - - - -
- -
-
-
- -
-

SQL Report

-
-

Page generated in ' . round($totaltime, 4) . " seconds with {$this->num_queries['normal']} queries" . (($this->num_queries['cached']) ? " + {$this->num_queries['cached']} " . (($this->num_queries['cached'] == 1) ? 'query' : 'queries') . ' returning data from cache' : '') . '

- -

Time spent on ' . $this->sql_layer . ' queries: ' . round($this->sql_time, 5) . 's | Time spent on PHP: ' . round($totaltime - $this->sql_time, 5) . 's

- -

- ' . $this->sql_report . ' -
- -
-
-
- -
- - '; - - exit_handler(); - - break; - - case 'stop': - $endtime = explode(' ', microtime()); - $endtime = $endtime[0] + $endtime[1]; - - $this->sql_report .= ' - - - - - - - - - - - - -
Query #' . $this->num_queries['total'] . '
- - ' . $this->html_hold . ' - -

- '; - - if ($this->query_result) - { - if (preg_match('/^(UPDATE|DELETE|REPLACE)/', $query)) - { - $this->sql_report .= 'Affected rows: ' . $this->sql_affectedrows($this->query_result) . ' | '; - } - $this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed: ' . sprintf('%.5f', $endtime - $this->curtime) . 's'; - } - else - { - $error = $this->sql_error(); - $this->sql_report .= 'FAILED - ' . $this->sql_layer . ' Error ' . $error['code'] . ': ' . htmlspecialchars($error['message']); - } - - $this->sql_report .= '



'; - - $this->sql_time += $endtime - $this->curtime; - break; - - case 'start': - $this->query_hold = $query; - $this->html_hold = ''; - - $this->_sql_report($mode, $query); - - $this->curtime = explode(' ', microtime()); - $this->curtime = $this->curtime[0] + $this->curtime[1]; - - break; - - case 'add_select_row': - - $html_table = func_get_arg(2); - $row = func_get_arg(3); - - if (!$html_table && sizeof($row)) - { - $html_table = true; - $this->html_hold .= ''; - - foreach (array_keys($row) as $val) - { - $this->html_hold .= ''; - } - $this->html_hold .= ''; - } - $this->html_hold .= ''; - - $class = 'row1'; - foreach (array_values($row) as $val) - { - $class = ($class == 'row1') ? 'row2' : 'row1'; - $this->html_hold .= ''; - } - $this->html_hold .= ''; - - return $html_table; - - break; - - case 'fromcache': - - $this->_sql_report($mode, $query); - - break; - - case 'record_fromcache': - - $endtime = func_get_arg(2); - $splittime = func_get_arg(3); - - $time_cache = $endtime - $this->curtime; - $time_db = $splittime - $endtime; - $color = ($time_db > $time_cache) ? 'green' : 'red'; - - $this->sql_report .= '
' . (($val) ? ucwords(str_replace('_', ' ', $val)) : ' ') . '
' . (($val) ? $val : ' ') . '
'; - $this->sql_report .= '
Query results obtained from the cache
'; - $this->sql_report .= '

'; - $this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed [cache]: ' . sprintf('%.5f', ($time_cache)) . 's | Elapsed [db]: ' . sprintf('%.5f', $time_db) . 's



'; - - // Pad the start time to not interfere with page timing - $starttime += $time_db; - - break; - - default: - - $this->_sql_report($mode, $query); - - break; - } - - return true; - } - - /** - * Gets the estimated number of rows in a specified table. - * - * @param string $table_name Table name - * - * @return string Number of rows in $table_name. - * Prefixed with ~ if estimated (otherwise exact). - * - * @access public - */ - function get_estimated_row_count($table_name) - { - return $this->get_row_count($table_name); - } - - /** - * Gets the exact number of rows in a specified table. - * - * @param string $table_name Table name - * - * @return string Exact number of rows in $table_name. - * - * @access public - */ - function get_row_count($table_name) - { - $sql = 'SELECT COUNT(*) AS rows_total - FROM ' . $this->sql_escape($table_name); - $result = $this->sql_query($sql); - $rows_total = $this->sql_fetchfield('rows_total'); - $this->sql_freeresult($result); - - return $rows_total; - } -} - -/** -* This variable holds the class name to use later -*/ -$sql_db = (!empty($dbms)) ? 'dbal_' . basename($dbms) : 'dbal'; diff --git a/phpBB/includes/db/driver/driver.php b/phpBB/includes/db/driver/driver.php index 9692ee71b9..4b831d2f79 100644 --- a/phpBB/includes/db/driver/driver.php +++ b/phpBB/includes/db/driver/driver.php @@ -206,7 +206,7 @@ class phpbb_db_driver $query_id = $this->query_result; } - if (isset($cache->sql_rowset[$query_id])) + if ($cache->sql_exists($query_id)) { return $cache->sql_rowseek($rownum, $query_id); } @@ -256,7 +256,7 @@ class phpbb_db_driver $this->sql_rowseek($rownum, $query_id); } - if (!is_object($query_id) && isset($cache->sql_rowset[$query_id])) + if (!is_object($query_id) && $cache->sql_exists($query_id)) { return $cache->sql_fetchfield($query_id, $field); } @@ -766,8 +766,8 @@ class phpbb_db_driver // Show complete SQL error and path to administrators only // Additionally show complete error on installation or if extended debug mode is enabled - // The DEBUG_EXTRA constant is for development only! - if ((isset($auth) && $auth->acl_get('a_')) || defined('IN_INSTALL') || defined('DEBUG_EXTRA')) + // The DEBUG constant is for development only! + if ((isset($auth) && $auth->acl_get('a_')) || defined('IN_INSTALL') || defined('DEBUG')) { $message .= ($sql) ? '

SQL

' . htmlspecialchars($sql) : ''; } diff --git a/phpBB/includes/db/driver/firebird.php b/phpBB/includes/db/driver/firebird.php index c793e0a51f..a55175c345 100644 --- a/phpBB/includes/db/driver/firebird.php +++ b/phpBB/includes/db/driver/firebird.php @@ -148,13 +148,13 @@ class phpbb_db_driver_firebird extends phpbb_db_driver global $cache; // EXPLAIN only in extra debug mode - if (defined('DEBUG_EXTRA')) + if (defined('DEBUG')) { $this->sql_report('start', $query); } $this->last_query_text = $query; - $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false; $this->sql_add_num_queries($this->query_result); if ($this->query_result === false) @@ -249,7 +249,7 @@ class phpbb_db_driver_firebird extends phpbb_db_driver $this->sql_error($query); } - if (defined('DEBUG_EXTRA')) + if (defined('DEBUG')) { $this->sql_report('stop', $query); } @@ -267,17 +267,17 @@ class phpbb_db_driver_firebird extends phpbb_db_driver } } - if ($cache_ttl && method_exists($cache, 'sql_save')) + if ($cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; - $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); } else if (strpos($query, 'SELECT') === 0 && $this->query_result) { $this->open_queries[(int) $this->query_result] = $this->query_result; } } - else if (defined('DEBUG_EXTRA')) + else if (defined('DEBUG')) { $this->sql_report('fromcache', $query); } @@ -330,7 +330,7 @@ class phpbb_db_driver_firebird extends phpbb_db_driver $query_id = $this->query_result; } - if (isset($cache->sql_rowset[$query_id])) + if ($cache->sql_exists($query_id)) { return $cache->sql_fetchrow($query_id); } @@ -396,7 +396,7 @@ class phpbb_db_driver_firebird extends phpbb_db_driver $query_id = $this->query_result; } - if (isset($cache->sql_rowset[$query_id])) + if ($cache->sql_exists($query_id)) { return $cache->sql_freeresult($query_id); } diff --git a/phpBB/includes/db/driver/mssql.php b/phpBB/includes/db/driver/mssql.php index e68738f918..04bb75f5ce 100644 --- a/phpBB/includes/db/driver/mssql.php +++ b/phpBB/includes/db/driver/mssql.php @@ -137,12 +137,12 @@ class phpbb_db_driver_mssql extends phpbb_db_driver global $cache; // EXPLAIN only in extra debug mode - if (defined('DEBUG_EXTRA')) + if (defined('DEBUG')) { $this->sql_report('start', $query); } - $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false; $this->sql_add_num_queries($this->query_result); if ($this->query_result === false) @@ -152,7 +152,7 @@ class phpbb_db_driver_mssql extends phpbb_db_driver $this->sql_error($query); } - if (defined('DEBUG_EXTRA')) + if (defined('DEBUG')) { $this->sql_report('stop', $query); } @@ -160,14 +160,14 @@ class phpbb_db_driver_mssql extends phpbb_db_driver if ($cache_ttl && method_exists($cache, 'sql_save')) { $this->open_queries[(int) $this->query_result] = $this->query_result; - $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); } else if (strpos($query, 'SELECT') === 0 && $this->query_result) { $this->open_queries[(int) $this->query_result] = $this->query_result; } } - else if (defined('DEBUG_EXTRA')) + else if (defined('DEBUG')) { $this->sql_report('fromcache', $query); } @@ -232,7 +232,7 @@ class phpbb_db_driver_mssql extends phpbb_db_driver $query_id = $this->query_result; } - if (isset($cache->sql_rowset[$query_id])) + if ($cache->sql_exists($query_id)) { return $cache->sql_fetchrow($query_id); } @@ -269,7 +269,7 @@ class phpbb_db_driver_mssql extends phpbb_db_driver $query_id = $this->query_result; } - if (isset($cache->sql_rowset[$query_id])) + if ($cache->sql_exists($query_id)) { return $cache->sql_rowseek($rownum, $query_id); } @@ -308,7 +308,7 @@ class phpbb_db_driver_mssql extends phpbb_db_driver $query_id = $this->query_result; } - if (isset($cache->sql_rowset[$query_id])) + if ($cache->sql_exists($query_id)) { return $cache->sql_freeresult($query_id); } diff --git a/phpBB/includes/db/driver/mssql_odbc.php b/phpBB/includes/db/driver/mssql_odbc.php index 7b35ce3d11..d1f31a6554 100644 --- a/phpBB/includes/db/driver/mssql_odbc.php +++ b/phpBB/includes/db/driver/mssql_odbc.php @@ -155,13 +155,13 @@ class phpbb_db_driver_mssql_odbc extends phpbb_db_driver global $cache; // EXPLAIN only in extra debug mode - if (defined('DEBUG_EXTRA')) + if (defined('DEBUG')) { $this->sql_report('start', $query); } $this->last_query_text = $query; - $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false; $this->sql_add_num_queries($this->query_result); if ($this->query_result === false) @@ -171,22 +171,22 @@ class phpbb_db_driver_mssql_odbc extends phpbb_db_driver $this->sql_error($query); } - if (defined('DEBUG_EXTRA')) + if (defined('DEBUG')) { $this->sql_report('stop', $query); } - if ($cache_ttl && method_exists($cache, 'sql_save')) + if ($cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; - $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); } else if (strpos($query, 'SELECT') === 0 && $this->query_result) { $this->open_queries[(int) $this->query_result] = $this->query_result; } } - else if (defined('DEBUG_EXTRA')) + else if (defined('DEBUG')) { $this->sql_report('fromcache', $query); } @@ -252,7 +252,7 @@ class phpbb_db_driver_mssql_odbc extends phpbb_db_driver $query_id = $this->query_result; } - if (isset($cache->sql_rowset[$query_id])) + if ($cache->sql_exists($query_id)) { return $cache->sql_fetchrow($query_id); } @@ -293,7 +293,7 @@ class phpbb_db_driver_mssql_odbc extends phpbb_db_driver $query_id = $this->query_result; } - if (isset($cache->sql_rowset[$query_id])) + if ($cache->sql_exists($query_id)) { return $cache->sql_freeresult($query_id); } diff --git a/phpBB/includes/db/driver/mssqlnative.php b/phpBB/includes/db/driver/mssqlnative.php index 99b9d7975a..67a019f5a5 100644 --- a/phpBB/includes/db/driver/mssqlnative.php +++ b/phpBB/includes/db/driver/mssqlnative.php @@ -216,7 +216,6 @@ class phpbb_db_driver_mssqlnative extends phpbb_db_driver $this->server = $sqlserver . (($port) ? $port_delimiter . $port : ''); //connect to database - error_reporting(E_ALL); $this->db_connect_id = sqlsrv_connect($this->server, array( 'Database' => $this->dbname, 'UID' => $this->user, @@ -310,13 +309,13 @@ class phpbb_db_driver_mssqlnative extends phpbb_db_driver global $cache; // EXPLAIN only in extra debug mode - if (defined('DEBUG_EXTRA')) + if (defined('DEBUG')) { $this->sql_report('start', $query); } $this->last_query_text = $query; - $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false; $this->sql_add_num_queries($this->query_result); if ($this->query_result === false) @@ -328,22 +327,22 @@ class phpbb_db_driver_mssqlnative extends phpbb_db_driver // reset options for next query $this->query_options = array(); - if (defined('DEBUG_EXTRA')) + if (defined('DEBUG')) { $this->sql_report('stop', $query); } - if ($cache_ttl && method_exists($cache, 'sql_save')) + if ($cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; - $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); } else if (strpos($query, 'SELECT') === 0 && $this->query_result) { $this->open_queries[(int) $this->query_result] = $this->query_result; } } - else if (defined('DEBUG_EXTRA')) + else if (defined('DEBUG')) { $this->sql_report('fromcache', $query); } @@ -416,7 +415,7 @@ class phpbb_db_driver_mssqlnative extends phpbb_db_driver $query_id = $this->query_result; } - if (isset($cache->sql_rowset[$query_id])) + if ($cache->sql_exists($query_id)) { return $cache->sql_fetchrow($query_id); } @@ -476,7 +475,7 @@ class phpbb_db_driver_mssqlnative extends phpbb_db_driver $query_id = $this->query_result; } - if (isset($cache->sql_rowset[$query_id])) + if ($cache->sql_exists($query_id)) { return $cache->sql_freeresult($query_id); } diff --git a/phpBB/includes/db/driver/mysql.php b/phpBB/includes/db/driver/mysql.php index 987691341a..f8c2be2366 100644 --- a/phpBB/includes/db/driver/mysql.php +++ b/phpBB/includes/db/driver/mysql.php @@ -165,12 +165,12 @@ class phpbb_db_driver_mysql extends phpbb_db_driver global $cache; // EXPLAIN only in extra debug mode - if (defined('DEBUG_EXTRA')) + if (defined('DEBUG')) { $this->sql_report('start', $query); } - $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false; $this->sql_add_num_queries($this->query_result); if ($this->query_result === false) @@ -180,22 +180,22 @@ class phpbb_db_driver_mysql extends phpbb_db_driver $this->sql_error($query); } - if (defined('DEBUG_EXTRA')) + if (defined('DEBUG')) { $this->sql_report('stop', $query); } - if ($cache_ttl && method_exists($cache, 'sql_save')) + if ($cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; - $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); } else if (strpos($query, 'SELECT') === 0 && $this->query_result) { $this->open_queries[(int) $this->query_result] = $this->query_result; } } - else if (defined('DEBUG_EXTRA')) + else if (defined('DEBUG')) { $this->sql_report('fromcache', $query); } @@ -247,7 +247,7 @@ class phpbb_db_driver_mysql extends phpbb_db_driver $query_id = $this->query_result; } - if (isset($cache->sql_rowset[$query_id])) + if ($cache->sql_exists($query_id)) { return $cache->sql_fetchrow($query_id); } @@ -268,7 +268,7 @@ class phpbb_db_driver_mysql extends phpbb_db_driver $query_id = $this->query_result; } - if (isset($cache->sql_rowset[$query_id])) + if ($cache->sql_exists($query_id)) { return $cache->sql_rowseek($rownum, $query_id); } @@ -296,7 +296,7 @@ class phpbb_db_driver_mysql extends phpbb_db_driver $query_id = $this->query_result; } - if (isset($cache->sql_rowset[$query_id])) + if ($cache->sql_exists($query_id)) { return $cache->sql_freeresult($query_id); } diff --git a/phpBB/includes/db/driver/mysqli.php b/phpBB/includes/db/driver/mysqli.php index c473c7fe99..0cc3eb359a 100644 --- a/phpBB/includes/db/driver/mysqli.php +++ b/phpBB/includes/db/driver/mysqli.php @@ -172,12 +172,12 @@ class phpbb_db_driver_mysqli extends phpbb_db_driver global $cache; // EXPLAIN only in extra debug mode - if (defined('DEBUG_EXTRA')) + if (defined('DEBUG')) { $this->sql_report('start', $query); } - $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false; $this->sql_add_num_queries($this->query_result); if ($this->query_result === false) @@ -187,17 +187,17 @@ class phpbb_db_driver_mysqli extends phpbb_db_driver $this->sql_error($query); } - if (defined('DEBUG_EXTRA')) + if (defined('DEBUG')) { $this->sql_report('stop', $query); } - if ($cache_ttl && method_exists($cache, 'sql_save')) + if ($cache_ttl) { - $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); } } - else if (defined('DEBUG_EXTRA')) + else if (defined('DEBUG')) { $this->sql_report('fromcache', $query); } @@ -249,7 +249,7 @@ class phpbb_db_driver_mysqli extends phpbb_db_driver $query_id = $this->query_result; } - if (!is_object($query_id) && isset($cache->sql_rowset[$query_id])) + if (!is_object($query_id) && $cache->sql_exists($query_id)) { return $cache->sql_fetchrow($query_id); } @@ -276,7 +276,7 @@ class phpbb_db_driver_mysqli extends phpbb_db_driver $query_id = $this->query_result; } - if (!is_object($query_id) && isset($cache->sql_rowset[$query_id])) + if (!is_object($query_id) && $cache->sql_exists($query_id)) { return $cache->sql_rowseek($rownum, $query_id); } @@ -304,7 +304,7 @@ class phpbb_db_driver_mysqli extends phpbb_db_driver $query_id = $this->query_result; } - if (!is_object($query_id) && isset($cache->sql_rowset[$query_id])) + if (!is_object($query_id) && $cache->sql_exists($query_id)) { return $cache->sql_freeresult($query_id); } diff --git a/phpBB/includes/db/driver/oracle.php b/phpBB/includes/db/driver/oracle.php index 25803e57bd..d8474694e3 100644 --- a/phpBB/includes/db/driver/oracle.php +++ b/phpBB/includes/db/driver/oracle.php @@ -234,13 +234,13 @@ class phpbb_db_driver_oracle extends phpbb_db_driver global $cache; // EXPLAIN only in extra debug mode - if (defined('DEBUG_EXTRA')) + if (defined('DEBUG')) { $this->sql_report('start', $query); } $this->last_query_text = $query; - $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false; $this->sql_add_num_queries($this->query_result); if ($this->query_result === false) @@ -411,22 +411,22 @@ class phpbb_db_driver_oracle extends phpbb_db_driver } } - if (defined('DEBUG_EXTRA')) + if (defined('DEBUG')) { $this->sql_report('stop', $query); } - if ($cache_ttl && method_exists($cache, 'sql_save')) + if ($cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; - $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); } else if (strpos($query, 'SELECT') === 0 && $this->query_result) { $this->open_queries[(int) $this->query_result] = $this->query_result; } } - else if (defined('DEBUG_EXTRA')) + else if (defined('DEBUG')) { $this->sql_report('fromcache', $query); } @@ -471,7 +471,7 @@ class phpbb_db_driver_oracle extends phpbb_db_driver $query_id = $this->query_result; } - if (isset($cache->sql_rowset[$query_id])) + if ($cache->sql_exists($query_id)) { return $cache->sql_fetchrow($query_id); } @@ -523,7 +523,7 @@ class phpbb_db_driver_oracle extends phpbb_db_driver $query_id = $this->query_result; } - if (isset($cache->sql_rowset[$query_id])) + if ($cache->sql_exists($query_id)) { return $cache->sql_rowseek($rownum, $query_id); } @@ -592,7 +592,7 @@ class phpbb_db_driver_oracle extends phpbb_db_driver $query_id = $this->query_result; } - if (isset($cache->sql_rowset[$query_id])) + if ($cache->sql_exists($query_id)) { return $cache->sql_freeresult($query_id); } diff --git a/phpBB/includes/db/driver/postgres.php b/phpBB/includes/db/driver/postgres.php index 3f54936d23..147ecd04d9 100644 --- a/phpBB/includes/db/driver/postgres.php +++ b/phpBB/includes/db/driver/postgres.php @@ -187,13 +187,13 @@ class phpbb_db_driver_postgres extends phpbb_db_driver global $cache; // EXPLAIN only in extra debug mode - if (defined('DEBUG_EXTRA')) + if (defined('DEBUG')) { $this->sql_report('start', $query); } $this->last_query_text = $query; - $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false; $this->sql_add_num_queries($this->query_result); if ($this->query_result === false) @@ -203,22 +203,22 @@ class phpbb_db_driver_postgres extends phpbb_db_driver $this->sql_error($query); } - if (defined('DEBUG_EXTRA')) + if (defined('DEBUG')) { $this->sql_report('stop', $query); } - if ($cache_ttl && method_exists($cache, 'sql_save')) + if ($cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; - $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); } else if (strpos($query, 'SELECT') === 0 && $this->query_result) { $this->open_queries[(int) $this->query_result] = $this->query_result; } } - else if (defined('DEBUG_EXTRA')) + else if (defined('DEBUG')) { $this->sql_report('fromcache', $query); } @@ -278,7 +278,7 @@ class phpbb_db_driver_postgres extends phpbb_db_driver $query_id = $this->query_result; } - if (isset($cache->sql_rowset[$query_id])) + if ($cache->sql_exists($query_id)) { return $cache->sql_fetchrow($query_id); } @@ -299,7 +299,7 @@ class phpbb_db_driver_postgres extends phpbb_db_driver $query_id = $this->query_result; } - if (isset($cache->sql_rowset[$query_id])) + if ($cache->sql_exists($query_id)) { return $cache->sql_rowseek($rownum, $query_id); } @@ -348,7 +348,7 @@ class phpbb_db_driver_postgres extends phpbb_db_driver $query_id = $this->query_result; } - if (isset($cache->sql_rowset[$query_id])) + if ($cache->sql_exists($query_id)) { return $cache->sql_freeresult($query_id); } diff --git a/phpBB/includes/db/driver/sqlite.php b/phpBB/includes/db/driver/sqlite.php index 363f26da2b..0b09fa758d 100644 --- a/phpBB/includes/db/driver/sqlite.php +++ b/phpBB/includes/db/driver/sqlite.php @@ -110,12 +110,12 @@ class phpbb_db_driver_sqlite extends phpbb_db_driver global $cache; // EXPLAIN only in extra debug mode - if (defined('DEBUG_EXTRA')) + if (defined('DEBUG')) { $this->sql_report('start', $query); } - $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + $this->query_result = ($cache_ttl) ? $cache->sql_load($query) : false; $this->sql_add_num_queries($this->query_result); if ($this->query_result === false) @@ -125,22 +125,22 @@ class phpbb_db_driver_sqlite extends phpbb_db_driver $this->sql_error($query); } - if (defined('DEBUG_EXTRA')) + if (defined('DEBUG')) { $this->sql_report('stop', $query); } - if ($cache_ttl && method_exists($cache, 'sql_save')) + if ($cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; - $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); } else if (strpos($query, 'SELECT') === 0 && $this->query_result) { $this->open_queries[(int) $this->query_result] = $this->query_result; } } - else if (defined('DEBUG_EXTRA')) + else if (defined('DEBUG')) { $this->sql_report('fromcache', $query); } @@ -191,7 +191,7 @@ class phpbb_db_driver_sqlite extends phpbb_db_driver $query_id = $this->query_result; } - if (isset($cache->sql_rowset[$query_id])) + if ($cache->sql_exists($query_id)) { return $cache->sql_fetchrow($query_id); } @@ -212,7 +212,7 @@ class phpbb_db_driver_sqlite extends phpbb_db_driver $query_id = $this->query_result; } - if (isset($cache->sql_rowset[$query_id])) + if ($cache->sql_exists($query_id)) { return $cache->sql_rowseek($rownum, $query_id); } @@ -240,7 +240,7 @@ class phpbb_db_driver_sqlite extends phpbb_db_driver $query_id = $this->query_result; } - if (isset($cache->sql_rowset[$query_id])) + if ($cache->sql_exists($query_id)) { return $cache->sql_freeresult($query_id); } -- cgit v1.2.1 From f8841b7f521684eab6fda7fd3487540fa4560249 Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Thu, 15 Nov 2012 03:09:57 +0100 Subject: [ticket/11015] Fix line endings of phpbb_db_driver to LF PHPBB3-11015 --- phpBB/includes/db/driver/driver.php | 2042 +++++++++++++++++------------------ 1 file changed, 1021 insertions(+), 1021 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/db/driver/driver.php b/phpBB/includes/db/driver/driver.php index 4b831d2f79..49bfcb8007 100644 --- a/phpBB/includes/db/driver/driver.php +++ b/phpBB/includes/db/driver/driver.php @@ -12,7 +12,7 @@ */ if (!defined('IN_PHPBB')) { - exit; + exit; } /** @@ -21,1024 +21,1024 @@ if (!defined('IN_PHPBB')) */ class phpbb_db_driver { - var $db_connect_id; - var $query_result; - var $return_on_error = false; - var $transaction = false; - var $sql_time = 0; - var $num_queries = array(); - var $open_queries = array(); - - var $curtime = 0; - var $query_hold = ''; - var $html_hold = ''; - var $sql_report = ''; - - var $persistency = false; - var $user = ''; - var $server = ''; - var $dbname = ''; - - // Set to true if error triggered - var $sql_error_triggered = false; - - // Holding the last sql query on sql error - var $sql_error_sql = ''; - // Holding the error information - only populated if sql_error_triggered is set - var $sql_error_returned = array(); - - // Holding transaction count - var $transactions = 0; - - // Supports multi inserts? - var $multi_insert = false; - - /** - * Current sql layer - */ - var $sql_layer = ''; - - /** - * Wildcards for matching any (%) or exactly one (_) character within LIKE expressions - */ - var $any_char; - var $one_char; - - /** - * Exact version of the DBAL, directly queried - */ - var $sql_server_version = false; - - /** - * Constructor - */ - function __construct() - { - $this->num_queries = array( - 'cached' => 0, - 'normal' => 0, - 'total' => 0, - ); - - // Fill default sql layer based on the class being called. - // This can be changed by the specified layer itself later if needed. - $this->sql_layer = substr(get_class($this), strlen('phpbb_db_driver_')); - - // Do not change this please! This variable is used to easy the use of it - and is hardcoded. - $this->any_char = chr(0) . '%'; - $this->one_char = chr(0) . '_'; - } - - /** - * return on error or display error message - */ - function sql_return_on_error($fail = false) - { - $this->sql_error_triggered = false; - $this->sql_error_sql = ''; - - $this->return_on_error = $fail; - } - - /** - * Return number of sql queries and cached sql queries used - */ - function sql_num_queries($cached = false) - { - return ($cached) ? $this->num_queries['cached'] : $this->num_queries['normal']; - } - - /** - * Add to query count - */ - function sql_add_num_queries($cached = false) - { - $this->num_queries['cached'] += ($cached !== false) ? 1 : 0; - $this->num_queries['normal'] += ($cached !== false) ? 0 : 1; - $this->num_queries['total'] += 1; - } - - /** - * DBAL garbage collection, close sql connection - */ - function sql_close() - { - if (!$this->db_connect_id) - { - return false; - } - - if ($this->transaction) - { - do - { - $this->sql_transaction('commit'); - } - while ($this->transaction); - } - - foreach ($this->open_queries as $query_id) - { - $this->sql_freeresult($query_id); - } - - // Connection closed correctly. Set db_connect_id to false to prevent errors - if ($result = $this->_sql_close()) - { - $this->db_connect_id = false; - } - - return $result; - } - - /** - * Build LIMIT query - * Doing some validation here. - */ - function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) - { - if (empty($query)) - { - return false; - } - - // Never use a negative total or offset - $total = ($total < 0) ? 0 : $total; - $offset = ($offset < 0) ? 0 : $offset; - - return $this->_sql_query_limit($query, $total, $offset, $cache_ttl); - } - - /** - * Fetch all rows - */ - function sql_fetchrowset($query_id = false) - { - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($query_id !== false) - { - $result = array(); - while ($row = $this->sql_fetchrow($query_id)) - { - $result[] = $row; - } - - return $result; - } - - return false; - } - - /** - * Seek to given row number - * rownum is zero-based - */ - function sql_rowseek($rownum, &$query_id) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($cache->sql_exists($query_id)) - { - return $cache->sql_rowseek($rownum, $query_id); - } - - if ($query_id === false) - { - return false; - } - - $this->sql_freeresult($query_id); - $query_id = $this->sql_query($this->last_query_text); - - if ($query_id === false) - { - return false; - } - - // We do not fetch the row for rownum == 0 because then the next resultset would be the second row - for ($i = 0; $i < $rownum; $i++) - { - if (!$this->sql_fetchrow($query_id)) - { - return false; - } - } - - return true; - } - - /** - * Fetch field - * if rownum is false, the current row is used, else it is pointing to the row (zero-based) - */ - function sql_fetchfield($field, $rownum = false, $query_id = false) - { - global $cache; - - if ($query_id === false) - { - $query_id = $this->query_result; - } - - if ($query_id !== false) - { - if ($rownum !== false) - { - $this->sql_rowseek($rownum, $query_id); - } - - if (!is_object($query_id) && $cache->sql_exists($query_id)) - { - return $cache->sql_fetchfield($query_id, $field); - } - - $row = $this->sql_fetchrow($query_id); - return (isset($row[$field])) ? $row[$field] : false; - } - - return false; - } - - /** - * Correctly adjust LIKE expression for special characters - * Some DBMS are handling them in a different way - * - * @param string $expression The expression to use. Every wildcard is escaped, except $this->any_char and $this->one_char - * @return string LIKE expression including the keyword! - */ - function sql_like_expression($expression) - { - $expression = utf8_str_replace(array('_', '%'), array("\_", "\%"), $expression); - $expression = utf8_str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression); - - return $this->_sql_like_expression('LIKE \'' . $this->sql_escape($expression) . '\''); - } - - /** - * Build a case expression - * - * Note: The two statements action_true and action_false must have the same data type (int, vchar, ...) in the database! - * - * @param string $condition The condition which must be true, to use action_true rather then action_else - * @param string $action_true SQL expression that is used, if the condition is true - * @param string $action_else SQL expression that is used, if the condition is false, optional - * @return string CASE expression including the condition and statements - */ - public function sql_case($condition, $action_true, $action_false = false) - { - $sql_case = 'CASE WHEN ' . $condition; - $sql_case .= ' THEN ' . $action_true; - $sql_case .= ($action_false !== false) ? ' ELSE ' . $action_false : ''; - $sql_case .= ' END'; - return $sql_case; - } - - /** - * Build a concatenated expression - * - * @param string $expr1 Base SQL expression where we append the second one - * @param string $expr2 SQL expression that is appended to the first expression - * @return string Concatenated string - */ - public function sql_concatenate($expr1, $expr2) - { - return $expr1 . ' || ' . $expr2; - } - - /** - * Returns whether results of a query need to be buffered to run a transaction while iterating over them. - * - * @return bool Whether buffering is required. - */ - function sql_buffer_nested_transactions() - { - return false; - } - - /** - * SQL Transaction - * @access private - */ - function sql_transaction($status = 'begin') - { - switch ($status) - { - case 'begin': - // If we are within a transaction we will not open another one, but enclose the current one to not loose data (prevening auto commit) - if ($this->transaction) - { - $this->transactions++; - return true; - } - - $result = $this->_sql_transaction('begin'); - - if (!$result) - { - $this->sql_error(); - } - - $this->transaction = true; - break; - - case 'commit': - // If there was a previously opened transaction we do not commit yet... but count back the number of inner transactions - if ($this->transaction && $this->transactions) - { - $this->transactions--; - return true; - } - - // Check if there is a transaction (no transaction can happen if there was an error, with a combined rollback and error returning enabled) - // This implies we have transaction always set for autocommit db's - if (!$this->transaction) - { - return false; - } - - $result = $this->_sql_transaction('commit'); - - if (!$result) - { - $this->sql_error(); - } - - $this->transaction = false; - $this->transactions = 0; - break; - - case 'rollback': - $result = $this->_sql_transaction('rollback'); - $this->transaction = false; - $this->transactions = 0; - break; - - default: - $result = $this->_sql_transaction($status); - break; - } - - return $result; - } - - /** - * Build sql statement from array for insert/update/select statements - * - * Idea for this from Ikonboard - * Possible query values: INSERT, INSERT_SELECT, UPDATE, SELECT - * - */ - function sql_build_array($query, $assoc_ary = false) - { - if (!is_array($assoc_ary)) - { - return false; - } - - $fields = $values = array(); - - if ($query == 'INSERT' || $query == 'INSERT_SELECT') - { - foreach ($assoc_ary as $key => $var) - { - $fields[] = $key; - - if (is_array($var) && is_string($var[0])) - { - // This is used for INSERT_SELECT(s) - $values[] = $var[0]; - } - else - { - $values[] = $this->_sql_validate_value($var); - } - } - - $query = ($query == 'INSERT') ? ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')' : ' (' . implode(', ', $fields) . ') SELECT ' . implode(', ', $values) . ' '; - } - else if ($query == 'MULTI_INSERT') - { - trigger_error('The MULTI_INSERT query value is no longer supported. Please use sql_multi_insert() instead.', E_USER_ERROR); - } - else if ($query == 'UPDATE' || $query == 'SELECT') - { - $values = array(); - foreach ($assoc_ary as $key => $var) - { - $values[] = "$key = " . $this->_sql_validate_value($var); - } - $query = implode(($query == 'UPDATE') ? ', ' : ' AND ', $values); - } - - return $query; - } - - /** - * Build IN or NOT IN sql comparison string, uses <> or = on single element - * arrays to improve comparison speed - * - * @access public - * @param string $field name of the sql column that shall be compared - * @param array $array array of values that are allowed (IN) or not allowed (NOT IN) - * @param bool $negate true for NOT IN (), false for IN () (default) - * @param bool $allow_empty_set If true, allow $array to be empty, this function will return 1=1 or 1=0 then. Default to false. - */ - function sql_in_set($field, $array, $negate = false, $allow_empty_set = false) - { - if (!sizeof($array)) - { - if (!$allow_empty_set) - { - // Print the backtrace to help identifying the location of the problematic code - $this->sql_error('No values specified for SQL IN comparison'); - } - else - { - // NOT IN () actually means everything so use a tautology - if ($negate) - { - return '1=1'; - } - // IN () actually means nothing so use a contradiction - else - { - return '1=0'; - } - } - } - - if (!is_array($array)) - { - $array = array($array); - } - - if (sizeof($array) == 1) - { - @reset($array); - $var = current($array); - - return $field . ($negate ? ' <> ' : ' = ') . $this->_sql_validate_value($var); - } - else - { - return $field . ($negate ? ' NOT IN ' : ' IN ') . '(' . implode(', ', array_map(array($this, '_sql_validate_value'), $array)) . ')'; - } - } - - /** - * Run binary AND operator on DB column. - * Results in sql statement: "{$column_name} & (1 << {$bit}) {$compare}" - * - * @param string $column_name The column name to use - * @param int $bit The value to use for the AND operator, will be converted to (1 << $bit). Is used by options, using the number schema... 0, 1, 2...29 - * @param string $compare Any custom SQL code after the check (for example "= 0") - */ - function sql_bit_and($column_name, $bit, $compare = '') - { - if (method_exists($this, '_sql_bit_and')) - { - return $this->_sql_bit_and($column_name, $bit, $compare); - } - - return $column_name . ' & ' . (1 << $bit) . (($compare) ? ' ' . $compare : ''); - } - - /** - * Run binary OR operator on DB column. - * Results in sql statement: "{$column_name} | (1 << {$bit}) {$compare}" - * - * @param string $column_name The column name to use - * @param int $bit The value to use for the OR operator, will be converted to (1 << $bit). Is used by options, using the number schema... 0, 1, 2...29 - * @param string $compare Any custom SQL code after the check (for example "= 0") - */ - function sql_bit_or($column_name, $bit, $compare = '') - { - if (method_exists($this, '_sql_bit_or')) - { - return $this->_sql_bit_or($column_name, $bit, $compare); - } - - return $column_name . ' | ' . (1 << $bit) . (($compare) ? ' ' . $compare : ''); - } - - /** - * Returns SQL string to cast a string expression to an int. - * - * @param string $expression An expression evaluating to string - * @return string Expression returning an int - */ - function cast_expr_to_bigint($expression) - { - return $expression; - } - - /** - * Returns SQL string to cast an integer expression to a string. - * - * @param string $expression An expression evaluating to int - * @return string Expression returning a string - */ - function cast_expr_to_string($expression) - { - return $expression; - } - - /** - * Run LOWER() on DB column of type text (i.e. neither varchar nor char). - * - * @param string $column_name The column name to use - * - * @return string A SQL statement like "LOWER($column_name)" - */ - function sql_lower_text($column_name) - { - return "LOWER($column_name)"; - } - - /** - * Run more than one insert statement. - * - * @param string $table table name to run the statements on - * @param array &$sql_ary multi-dimensional array holding the statement data. - * - * @return bool false if no statements were executed. - * @access public - */ - function sql_multi_insert($table, &$sql_ary) - { - if (!sizeof($sql_ary)) - { - return false; - } - - if ($this->multi_insert) - { - $ary = array(); - foreach ($sql_ary as $id => $_sql_ary) - { - // If by accident the sql array is only one-dimensional we build a normal insert statement - if (!is_array($_sql_ary)) - { - return $this->sql_query('INSERT INTO ' . $table . ' ' . $this->sql_build_array('INSERT', $sql_ary)); - } - - $values = array(); - foreach ($_sql_ary as $key => $var) - { - $values[] = $this->_sql_validate_value($var); - } - $ary[] = '(' . implode(', ', $values) . ')'; - } - - return $this->sql_query('INSERT INTO ' . $table . ' ' . ' (' . implode(', ', array_keys($sql_ary[0])) . ') VALUES ' . implode(', ', $ary)); - } - else - { - foreach ($sql_ary as $ary) - { - if (!is_array($ary)) - { - return false; - } - - $result = $this->sql_query('INSERT INTO ' . $table . ' ' . $this->sql_build_array('INSERT', $ary)); - - if (!$result) - { - return false; - } - } - } - - return true; - } - - /** - * Function for validating values - * @access private - */ - function _sql_validate_value($var) - { - if (is_null($var)) - { - return 'NULL'; - } - else if (is_string($var)) - { - return "'" . $this->sql_escape($var) . "'"; - } - else - { - return (is_bool($var)) ? intval($var) : $var; - } - } - - /** - * Build sql statement from array for select and select distinct statements - * - * Possible query values: SELECT, SELECT_DISTINCT - */ - function sql_build_query($query, $array) - { - $sql = ''; - switch ($query) - { - case 'SELECT': - case 'SELECT_DISTINCT'; - - $sql = str_replace('_', ' ', $query) . ' ' . $array['SELECT'] . ' FROM '; - - // Build table array. We also build an alias array for later checks. - $table_array = $aliases = array(); - $used_multi_alias = false; - - foreach ($array['FROM'] as $table_name => $alias) - { - if (is_array($alias)) - { - $used_multi_alias = true; - - foreach ($alias as $multi_alias) - { - $table_array[] = $table_name . ' ' . $multi_alias; - $aliases[] = $multi_alias; - } - } - else - { - $table_array[] = $table_name . ' ' . $alias; - $aliases[] = $alias; - } - } - - // We run the following code to determine if we need to re-order the table array. ;) - // The reason for this is that for multi-aliased tables (two equal tables) in the FROM statement the last table need to match the first comparison. - // DBMS who rely on this: Oracle, PostgreSQL and MSSQL. For all other DBMS it makes absolutely no difference in which order the table is. - if (!empty($array['LEFT_JOIN']) && sizeof($array['FROM']) > 1 && $used_multi_alias !== false) - { - // Take first LEFT JOIN - $join = current($array['LEFT_JOIN']); - - // Determine the table used there (even if there are more than one used, we only want to have one - preg_match('/(' . implode('|', $aliases) . ')\.[^\s]+/U', str_replace(array('(', ')', 'AND', 'OR', ' '), '', $join['ON']), $matches); - - // If there is a first join match, we need to make sure the table order is correct - if (!empty($matches[1])) - { - $first_join_match = trim($matches[1]); - $table_array = $last = array(); - - foreach ($array['FROM'] as $table_name => $alias) - { - if (is_array($alias)) - { - foreach ($alias as $multi_alias) - { - ($multi_alias === $first_join_match) ? $last[] = $table_name . ' ' . $multi_alias : $table_array[] = $table_name . ' ' . $multi_alias; - } - } - else - { - ($alias === $first_join_match) ? $last[] = $table_name . ' ' . $alias : $table_array[] = $table_name . ' ' . $alias; - } - } - - $table_array = array_merge($table_array, $last); - } - } - - $sql .= $this->_sql_custom_build('FROM', implode(' CROSS JOIN ', $table_array)); - - if (!empty($array['LEFT_JOIN'])) - { - foreach ($array['LEFT_JOIN'] as $join) - { - $sql .= ' LEFT JOIN ' . key($join['FROM']) . ' ' . current($join['FROM']) . ' ON (' . $join['ON'] . ')'; - } - } - - if (!empty($array['WHERE'])) - { - $sql .= ' WHERE ' . $this->_sql_custom_build('WHERE', $array['WHERE']); - } - - if (!empty($array['GROUP_BY'])) - { - $sql .= ' GROUP BY ' . $array['GROUP_BY']; - } - - if (!empty($array['ORDER_BY'])) - { - $sql .= ' ORDER BY ' . $array['ORDER_BY']; - } - - break; - } - - return $sql; - } - - /** - * display sql error page - */ - function sql_error($sql = '') - { - global $auth, $user, $config; - - // Set var to retrieve errored status - $this->sql_error_triggered = true; - $this->sql_error_sql = $sql; - - $this->sql_error_returned = $this->_sql_error(); - - if (!$this->return_on_error) - { - $message = 'SQL ERROR [ ' . $this->sql_layer . ' ]

' . $this->sql_error_returned['message'] . ' [' . $this->sql_error_returned['code'] . ']'; - - // Show complete SQL error and path to administrators only - // Additionally show complete error on installation or if extended debug mode is enabled - // The DEBUG constant is for development only! - if ((isset($auth) && $auth->acl_get('a_')) || defined('IN_INSTALL') || defined('DEBUG')) - { - $message .= ($sql) ? '

SQL

' . htmlspecialchars($sql) : ''; - } - else - { - // If error occurs in initiating the session we need to use a pre-defined language string - // This could happen if the connection could not be established for example (then we are not able to grab the default language) - if (!isset($user->lang['SQL_ERROR_OCCURRED'])) - { - $message .= '

An sql error occurred while fetching this page. Please contact an administrator if this problem persists.'; - } - else - { - if (!empty($config['board_contact'])) - { - $message .= '

' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '', ''); - } - else - { - $message .= '

' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '', ''); - } - } - } - - if ($this->transaction) - { - $this->sql_transaction('rollback'); - } - - if (strlen($message) > 1024) - { - // We need to define $msg_long_text here to circumvent text stripping. - global $msg_long_text; - $msg_long_text = $message; - - trigger_error(false, E_USER_ERROR); - } - - trigger_error($message, E_USER_ERROR); - } - - if ($this->transaction) - { - $this->sql_transaction('rollback'); - } - - return $this->sql_error_returned; - } - - /** - * Explain queries - */ - function sql_report($mode, $query = '') - { - global $cache, $starttime, $phpbb_root_path, $user; - global $request; - - if (is_object($request) && !$request->variable('explain', false)) - { - return false; - } - - if (!$query && $this->query_hold != '') - { - $query = $this->query_hold; - } - - switch ($mode) - { - case 'display': - if (!empty($cache)) - { - $cache->unload(); - } - $this->sql_close(); - - $mtime = explode(' ', microtime()); - $totaltime = $mtime[0] + $mtime[1] - $starttime; - - echo ' - - - - SQL Report - - - -
- -
-
-
- -
-

SQL Report

-
-

Page generated in ' . round($totaltime, 4) . " seconds with {$this->num_queries['normal']} queries" . (($this->num_queries['cached']) ? " + {$this->num_queries['cached']} " . (($this->num_queries['cached'] == 1) ? 'query' : 'queries') . ' returning data from cache' : '') . '

- -

Time spent on ' . $this->sql_layer . ' queries: ' . round($this->sql_time, 5) . 's | Time spent on PHP: ' . round($totaltime - $this->sql_time, 5) . 's

- -

- ' . $this->sql_report . ' -
- -
-
-
- -
- - '; - - exit_handler(); - - break; - - case 'stop': - $endtime = explode(' ', microtime()); - $endtime = $endtime[0] + $endtime[1]; - - $this->sql_report .= ' - - - - - - - - - - - - -
Query #' . $this->num_queries['total'] . '
- - ' . $this->html_hold . ' - -

- '; - - if ($this->query_result) - { - if (preg_match('/^(UPDATE|DELETE|REPLACE)/', $query)) - { - $this->sql_report .= 'Affected rows: ' . $this->sql_affectedrows($this->query_result) . ' | '; - } - $this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed: ' . sprintf('%.5f', $endtime - $this->curtime) . 's'; - } - else - { - $error = $this->sql_error(); - $this->sql_report .= 'FAILED - ' . $this->sql_layer . ' Error ' . $error['code'] . ': ' . htmlspecialchars($error['message']); - } - - $this->sql_report .= '



'; - - $this->sql_time += $endtime - $this->curtime; - break; - - case 'start': - $this->query_hold = $query; - $this->html_hold = ''; - - $this->_sql_report($mode, $query); - - $this->curtime = explode(' ', microtime()); - $this->curtime = $this->curtime[0] + $this->curtime[1]; - - break; - - case 'add_select_row': - - $html_table = func_get_arg(2); - $row = func_get_arg(3); - - if (!$html_table && sizeof($row)) - { - $html_table = true; - $this->html_hold .= ''; - - foreach (array_keys($row) as $val) - { - $this->html_hold .= ''; - } - $this->html_hold .= ''; - } - $this->html_hold .= ''; - - $class = 'row1'; - foreach (array_values($row) as $val) - { - $class = ($class == 'row1') ? 'row2' : 'row1'; - $this->html_hold .= ''; - } - $this->html_hold .= ''; - - return $html_table; - - break; - - case 'fromcache': - - $this->_sql_report($mode, $query); - - break; - - case 'record_fromcache': - - $endtime = func_get_arg(2); - $splittime = func_get_arg(3); - - $time_cache = $endtime - $this->curtime; - $time_db = $splittime - $endtime; - $color = ($time_db > $time_cache) ? 'green' : 'red'; - - $this->sql_report .= '
' . (($val) ? ucwords(str_replace('_', ' ', $val)) : ' ') . '
' . (($val) ? $val : ' ') . '
'; - $this->sql_report .= '
Query results obtained from the cache
'; - $this->sql_report .= '

'; - $this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed [cache]: ' . sprintf('%.5f', ($time_cache)) . 's | Elapsed [db]: ' . sprintf('%.5f', $time_db) . 's



'; - - // Pad the start time to not interfere with page timing - $starttime += $time_db; - - break; - - default: - - $this->_sql_report($mode, $query); - - break; - } - - return true; - } - - /** - * Gets the estimated number of rows in a specified table. - * - * @param string $table_name Table name - * - * @return string Number of rows in $table_name. - * Prefixed with ~ if estimated (otherwise exact). - * - * @access public - */ - function get_estimated_row_count($table_name) - { - return $this->get_row_count($table_name); - } - - /** - * Gets the exact number of rows in a specified table. - * - * @param string $table_name Table name - * - * @return string Exact number of rows in $table_name. - * - * @access public - */ - function get_row_count($table_name) - { - $sql = 'SELECT COUNT(*) AS rows_total - FROM ' . $this->sql_escape($table_name); - $result = $this->sql_query($sql); - $rows_total = $this->sql_fetchfield('rows_total'); - $this->sql_freeresult($result); - - return $rows_total; - } + var $db_connect_id; + var $query_result; + var $return_on_error = false; + var $transaction = false; + var $sql_time = 0; + var $num_queries = array(); + var $open_queries = array(); + + var $curtime = 0; + var $query_hold = ''; + var $html_hold = ''; + var $sql_report = ''; + + var $persistency = false; + var $user = ''; + var $server = ''; + var $dbname = ''; + + // Set to true if error triggered + var $sql_error_triggered = false; + + // Holding the last sql query on sql error + var $sql_error_sql = ''; + // Holding the error information - only populated if sql_error_triggered is set + var $sql_error_returned = array(); + + // Holding transaction count + var $transactions = 0; + + // Supports multi inserts? + var $multi_insert = false; + + /** + * Current sql layer + */ + var $sql_layer = ''; + + /** + * Wildcards for matching any (%) or exactly one (_) character within LIKE expressions + */ + var $any_char; + var $one_char; + + /** + * Exact version of the DBAL, directly queried + */ + var $sql_server_version = false; + + /** + * Constructor + */ + function __construct() + { + $this->num_queries = array( + 'cached' => 0, + 'normal' => 0, + 'total' => 0, + ); + + // Fill default sql layer based on the class being called. + // This can be changed by the specified layer itself later if needed. + $this->sql_layer = substr(get_class($this), strlen('phpbb_db_driver_')); + + // Do not change this please! This variable is used to easy the use of it - and is hardcoded. + $this->any_char = chr(0) . '%'; + $this->one_char = chr(0) . '_'; + } + + /** + * return on error or display error message + */ + function sql_return_on_error($fail = false) + { + $this->sql_error_triggered = false; + $this->sql_error_sql = ''; + + $this->return_on_error = $fail; + } + + /** + * Return number of sql queries and cached sql queries used + */ + function sql_num_queries($cached = false) + { + return ($cached) ? $this->num_queries['cached'] : $this->num_queries['normal']; + } + + /** + * Add to query count + */ + function sql_add_num_queries($cached = false) + { + $this->num_queries['cached'] += ($cached !== false) ? 1 : 0; + $this->num_queries['normal'] += ($cached !== false) ? 0 : 1; + $this->num_queries['total'] += 1; + } + + /** + * DBAL garbage collection, close sql connection + */ + function sql_close() + { + if (!$this->db_connect_id) + { + return false; + } + + if ($this->transaction) + { + do + { + $this->sql_transaction('commit'); + } + while ($this->transaction); + } + + foreach ($this->open_queries as $query_id) + { + $this->sql_freeresult($query_id); + } + + // Connection closed correctly. Set db_connect_id to false to prevent errors + if ($result = $this->_sql_close()) + { + $this->db_connect_id = false; + } + + return $result; + } + + /** + * Build LIMIT query + * Doing some validation here. + */ + function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + if (empty($query)) + { + return false; + } + + // Never use a negative total or offset + $total = ($total < 0) ? 0 : $total; + $offset = ($offset < 0) ? 0 : $offset; + + return $this->_sql_query_limit($query, $total, $offset, $cache_ttl); + } + + /** + * Fetch all rows + */ + function sql_fetchrowset($query_id = false) + { + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($query_id !== false) + { + $result = array(); + while ($row = $this->sql_fetchrow($query_id)) + { + $result[] = $row; + } + + return $result; + } + + return false; + } + + /** + * Seek to given row number + * rownum is zero-based + */ + function sql_rowseek($rownum, &$query_id) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache->sql_exists($query_id)) + { + return $cache->sql_rowseek($rownum, $query_id); + } + + if ($query_id === false) + { + return false; + } + + $this->sql_freeresult($query_id); + $query_id = $this->sql_query($this->last_query_text); + + if ($query_id === false) + { + return false; + } + + // We do not fetch the row for rownum == 0 because then the next resultset would be the second row + for ($i = 0; $i < $rownum; $i++) + { + if (!$this->sql_fetchrow($query_id)) + { + return false; + } + } + + return true; + } + + /** + * Fetch field + * if rownum is false, the current row is used, else it is pointing to the row (zero-based) + */ + function sql_fetchfield($field, $rownum = false, $query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($query_id !== false) + { + if ($rownum !== false) + { + $this->sql_rowseek($rownum, $query_id); + } + + if (!is_object($query_id) && $cache->sql_exists($query_id)) + { + return $cache->sql_fetchfield($query_id, $field); + } + + $row = $this->sql_fetchrow($query_id); + return (isset($row[$field])) ? $row[$field] : false; + } + + return false; + } + + /** + * Correctly adjust LIKE expression for special characters + * Some DBMS are handling them in a different way + * + * @param string $expression The expression to use. Every wildcard is escaped, except $this->any_char and $this->one_char + * @return string LIKE expression including the keyword! + */ + function sql_like_expression($expression) + { + $expression = utf8_str_replace(array('_', '%'), array("\_", "\%"), $expression); + $expression = utf8_str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression); + + return $this->_sql_like_expression('LIKE \'' . $this->sql_escape($expression) . '\''); + } + + /** + * Build a case expression + * + * Note: The two statements action_true and action_false must have the same data type (int, vchar, ...) in the database! + * + * @param string $condition The condition which must be true, to use action_true rather then action_else + * @param string $action_true SQL expression that is used, if the condition is true + * @param string $action_else SQL expression that is used, if the condition is false, optional + * @return string CASE expression including the condition and statements + */ + public function sql_case($condition, $action_true, $action_false = false) + { + $sql_case = 'CASE WHEN ' . $condition; + $sql_case .= ' THEN ' . $action_true; + $sql_case .= ($action_false !== false) ? ' ELSE ' . $action_false : ''; + $sql_case .= ' END'; + return $sql_case; + } + + /** + * Build a concatenated expression + * + * @param string $expr1 Base SQL expression where we append the second one + * @param string $expr2 SQL expression that is appended to the first expression + * @return string Concatenated string + */ + public function sql_concatenate($expr1, $expr2) + { + return $expr1 . ' || ' . $expr2; + } + + /** + * Returns whether results of a query need to be buffered to run a transaction while iterating over them. + * + * @return bool Whether buffering is required. + */ + function sql_buffer_nested_transactions() + { + return false; + } + + /** + * SQL Transaction + * @access private + */ + function sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + // If we are within a transaction we will not open another one, but enclose the current one to not loose data (prevening auto commit) + if ($this->transaction) + { + $this->transactions++; + return true; + } + + $result = $this->_sql_transaction('begin'); + + if (!$result) + { + $this->sql_error(); + } + + $this->transaction = true; + break; + + case 'commit': + // If there was a previously opened transaction we do not commit yet... but count back the number of inner transactions + if ($this->transaction && $this->transactions) + { + $this->transactions--; + return true; + } + + // Check if there is a transaction (no transaction can happen if there was an error, with a combined rollback and error returning enabled) + // This implies we have transaction always set for autocommit db's + if (!$this->transaction) + { + return false; + } + + $result = $this->_sql_transaction('commit'); + + if (!$result) + { + $this->sql_error(); + } + + $this->transaction = false; + $this->transactions = 0; + break; + + case 'rollback': + $result = $this->_sql_transaction('rollback'); + $this->transaction = false; + $this->transactions = 0; + break; + + default: + $result = $this->_sql_transaction($status); + break; + } + + return $result; + } + + /** + * Build sql statement from array for insert/update/select statements + * + * Idea for this from Ikonboard + * Possible query values: INSERT, INSERT_SELECT, UPDATE, SELECT + * + */ + function sql_build_array($query, $assoc_ary = false) + { + if (!is_array($assoc_ary)) + { + return false; + } + + $fields = $values = array(); + + if ($query == 'INSERT' || $query == 'INSERT_SELECT') + { + foreach ($assoc_ary as $key => $var) + { + $fields[] = $key; + + if (is_array($var) && is_string($var[0])) + { + // This is used for INSERT_SELECT(s) + $values[] = $var[0]; + } + else + { + $values[] = $this->_sql_validate_value($var); + } + } + + $query = ($query == 'INSERT') ? ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')' : ' (' . implode(', ', $fields) . ') SELECT ' . implode(', ', $values) . ' '; + } + else if ($query == 'MULTI_INSERT') + { + trigger_error('The MULTI_INSERT query value is no longer supported. Please use sql_multi_insert() instead.', E_USER_ERROR); + } + else if ($query == 'UPDATE' || $query == 'SELECT') + { + $values = array(); + foreach ($assoc_ary as $key => $var) + { + $values[] = "$key = " . $this->_sql_validate_value($var); + } + $query = implode(($query == 'UPDATE') ? ', ' : ' AND ', $values); + } + + return $query; + } + + /** + * Build IN or NOT IN sql comparison string, uses <> or = on single element + * arrays to improve comparison speed + * + * @access public + * @param string $field name of the sql column that shall be compared + * @param array $array array of values that are allowed (IN) or not allowed (NOT IN) + * @param bool $negate true for NOT IN (), false for IN () (default) + * @param bool $allow_empty_set If true, allow $array to be empty, this function will return 1=1 or 1=0 then. Default to false. + */ + function sql_in_set($field, $array, $negate = false, $allow_empty_set = false) + { + if (!sizeof($array)) + { + if (!$allow_empty_set) + { + // Print the backtrace to help identifying the location of the problematic code + $this->sql_error('No values specified for SQL IN comparison'); + } + else + { + // NOT IN () actually means everything so use a tautology + if ($negate) + { + return '1=1'; + } + // IN () actually means nothing so use a contradiction + else + { + return '1=0'; + } + } + } + + if (!is_array($array)) + { + $array = array($array); + } + + if (sizeof($array) == 1) + { + @reset($array); + $var = current($array); + + return $field . ($negate ? ' <> ' : ' = ') . $this->_sql_validate_value($var); + } + else + { + return $field . ($negate ? ' NOT IN ' : ' IN ') . '(' . implode(', ', array_map(array($this, '_sql_validate_value'), $array)) . ')'; + } + } + + /** + * Run binary AND operator on DB column. + * Results in sql statement: "{$column_name} & (1 << {$bit}) {$compare}" + * + * @param string $column_name The column name to use + * @param int $bit The value to use for the AND operator, will be converted to (1 << $bit). Is used by options, using the number schema... 0, 1, 2...29 + * @param string $compare Any custom SQL code after the check (for example "= 0") + */ + function sql_bit_and($column_name, $bit, $compare = '') + { + if (method_exists($this, '_sql_bit_and')) + { + return $this->_sql_bit_and($column_name, $bit, $compare); + } + + return $column_name . ' & ' . (1 << $bit) . (($compare) ? ' ' . $compare : ''); + } + + /** + * Run binary OR operator on DB column. + * Results in sql statement: "{$column_name} | (1 << {$bit}) {$compare}" + * + * @param string $column_name The column name to use + * @param int $bit The value to use for the OR operator, will be converted to (1 << $bit). Is used by options, using the number schema... 0, 1, 2...29 + * @param string $compare Any custom SQL code after the check (for example "= 0") + */ + function sql_bit_or($column_name, $bit, $compare = '') + { + if (method_exists($this, '_sql_bit_or')) + { + return $this->_sql_bit_or($column_name, $bit, $compare); + } + + return $column_name . ' | ' . (1 << $bit) . (($compare) ? ' ' . $compare : ''); + } + + /** + * Returns SQL string to cast a string expression to an int. + * + * @param string $expression An expression evaluating to string + * @return string Expression returning an int + */ + function cast_expr_to_bigint($expression) + { + return $expression; + } + + /** + * Returns SQL string to cast an integer expression to a string. + * + * @param string $expression An expression evaluating to int + * @return string Expression returning a string + */ + function cast_expr_to_string($expression) + { + return $expression; + } + + /** + * Run LOWER() on DB column of type text (i.e. neither varchar nor char). + * + * @param string $column_name The column name to use + * + * @return string A SQL statement like "LOWER($column_name)" + */ + function sql_lower_text($column_name) + { + return "LOWER($column_name)"; + } + + /** + * Run more than one insert statement. + * + * @param string $table table name to run the statements on + * @param array &$sql_ary multi-dimensional array holding the statement data. + * + * @return bool false if no statements were executed. + * @access public + */ + function sql_multi_insert($table, &$sql_ary) + { + if (!sizeof($sql_ary)) + { + return false; + } + + if ($this->multi_insert) + { + $ary = array(); + foreach ($sql_ary as $id => $_sql_ary) + { + // If by accident the sql array is only one-dimensional we build a normal insert statement + if (!is_array($_sql_ary)) + { + return $this->sql_query('INSERT INTO ' . $table . ' ' . $this->sql_build_array('INSERT', $sql_ary)); + } + + $values = array(); + foreach ($_sql_ary as $key => $var) + { + $values[] = $this->_sql_validate_value($var); + } + $ary[] = '(' . implode(', ', $values) . ')'; + } + + return $this->sql_query('INSERT INTO ' . $table . ' ' . ' (' . implode(', ', array_keys($sql_ary[0])) . ') VALUES ' . implode(', ', $ary)); + } + else + { + foreach ($sql_ary as $ary) + { + if (!is_array($ary)) + { + return false; + } + + $result = $this->sql_query('INSERT INTO ' . $table . ' ' . $this->sql_build_array('INSERT', $ary)); + + if (!$result) + { + return false; + } + } + } + + return true; + } + + /** + * Function for validating values + * @access private + */ + function _sql_validate_value($var) + { + if (is_null($var)) + { + return 'NULL'; + } + else if (is_string($var)) + { + return "'" . $this->sql_escape($var) . "'"; + } + else + { + return (is_bool($var)) ? intval($var) : $var; + } + } + + /** + * Build sql statement from array for select and select distinct statements + * + * Possible query values: SELECT, SELECT_DISTINCT + */ + function sql_build_query($query, $array) + { + $sql = ''; + switch ($query) + { + case 'SELECT': + case 'SELECT_DISTINCT'; + + $sql = str_replace('_', ' ', $query) . ' ' . $array['SELECT'] . ' FROM '; + + // Build table array. We also build an alias array for later checks. + $table_array = $aliases = array(); + $used_multi_alias = false; + + foreach ($array['FROM'] as $table_name => $alias) + { + if (is_array($alias)) + { + $used_multi_alias = true; + + foreach ($alias as $multi_alias) + { + $table_array[] = $table_name . ' ' . $multi_alias; + $aliases[] = $multi_alias; + } + } + else + { + $table_array[] = $table_name . ' ' . $alias; + $aliases[] = $alias; + } + } + + // We run the following code to determine if we need to re-order the table array. ;) + // The reason for this is that for multi-aliased tables (two equal tables) in the FROM statement the last table need to match the first comparison. + // DBMS who rely on this: Oracle, PostgreSQL and MSSQL. For all other DBMS it makes absolutely no difference in which order the table is. + if (!empty($array['LEFT_JOIN']) && sizeof($array['FROM']) > 1 && $used_multi_alias !== false) + { + // Take first LEFT JOIN + $join = current($array['LEFT_JOIN']); + + // Determine the table used there (even if there are more than one used, we only want to have one + preg_match('/(' . implode('|', $aliases) . ')\.[^\s]+/U', str_replace(array('(', ')', 'AND', 'OR', ' '), '', $join['ON']), $matches); + + // If there is a first join match, we need to make sure the table order is correct + if (!empty($matches[1])) + { + $first_join_match = trim($matches[1]); + $table_array = $last = array(); + + foreach ($array['FROM'] as $table_name => $alias) + { + if (is_array($alias)) + { + foreach ($alias as $multi_alias) + { + ($multi_alias === $first_join_match) ? $last[] = $table_name . ' ' . $multi_alias : $table_array[] = $table_name . ' ' . $multi_alias; + } + } + else + { + ($alias === $first_join_match) ? $last[] = $table_name . ' ' . $alias : $table_array[] = $table_name . ' ' . $alias; + } + } + + $table_array = array_merge($table_array, $last); + } + } + + $sql .= $this->_sql_custom_build('FROM', implode(' CROSS JOIN ', $table_array)); + + if (!empty($array['LEFT_JOIN'])) + { + foreach ($array['LEFT_JOIN'] as $join) + { + $sql .= ' LEFT JOIN ' . key($join['FROM']) . ' ' . current($join['FROM']) . ' ON (' . $join['ON'] . ')'; + } + } + + if (!empty($array['WHERE'])) + { + $sql .= ' WHERE ' . $this->_sql_custom_build('WHERE', $array['WHERE']); + } + + if (!empty($array['GROUP_BY'])) + { + $sql .= ' GROUP BY ' . $array['GROUP_BY']; + } + + if (!empty($array['ORDER_BY'])) + { + $sql .= ' ORDER BY ' . $array['ORDER_BY']; + } + + break; + } + + return $sql; + } + + /** + * display sql error page + */ + function sql_error($sql = '') + { + global $auth, $user, $config; + + // Set var to retrieve errored status + $this->sql_error_triggered = true; + $this->sql_error_sql = $sql; + + $this->sql_error_returned = $this->_sql_error(); + + if (!$this->return_on_error) + { + $message = 'SQL ERROR [ ' . $this->sql_layer . ' ]

' . $this->sql_error_returned['message'] . ' [' . $this->sql_error_returned['code'] . ']'; + + // Show complete SQL error and path to administrators only + // Additionally show complete error on installation or if extended debug mode is enabled + // The DEBUG constant is for development only! + if ((isset($auth) && $auth->acl_get('a_')) || defined('IN_INSTALL') || defined('DEBUG')) + { + $message .= ($sql) ? '

SQL

' . htmlspecialchars($sql) : ''; + } + else + { + // If error occurs in initiating the session we need to use a pre-defined language string + // This could happen if the connection could not be established for example (then we are not able to grab the default language) + if (!isset($user->lang['SQL_ERROR_OCCURRED'])) + { + $message .= '

An sql error occurred while fetching this page. Please contact an administrator if this problem persists.'; + } + else + { + if (!empty($config['board_contact'])) + { + $message .= '

' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '', ''); + } + else + { + $message .= '

' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '', ''); + } + } + } + + if ($this->transaction) + { + $this->sql_transaction('rollback'); + } + + if (strlen($message) > 1024) + { + // We need to define $msg_long_text here to circumvent text stripping. + global $msg_long_text; + $msg_long_text = $message; + + trigger_error(false, E_USER_ERROR); + } + + trigger_error($message, E_USER_ERROR); + } + + if ($this->transaction) + { + $this->sql_transaction('rollback'); + } + + return $this->sql_error_returned; + } + + /** + * Explain queries + */ + function sql_report($mode, $query = '') + { + global $cache, $starttime, $phpbb_root_path, $user; + global $request; + + if (is_object($request) && !$request->variable('explain', false)) + { + return false; + } + + if (!$query && $this->query_hold != '') + { + $query = $this->query_hold; + } + + switch ($mode) + { + case 'display': + if (!empty($cache)) + { + $cache->unload(); + } + $this->sql_close(); + + $mtime = explode(' ', microtime()); + $totaltime = $mtime[0] + $mtime[1] - $starttime; + + echo ' + + + + SQL Report + + + +
+ +
+
+
+ +
+

SQL Report

+
+

Page generated in ' . round($totaltime, 4) . " seconds with {$this->num_queries['normal']} queries" . (($this->num_queries['cached']) ? " + {$this->num_queries['cached']} " . (($this->num_queries['cached'] == 1) ? 'query' : 'queries') . ' returning data from cache' : '') . '

+ +

Time spent on ' . $this->sql_layer . ' queries: ' . round($this->sql_time, 5) . 's | Time spent on PHP: ' . round($totaltime - $this->sql_time, 5) . 's

+ +

+ ' . $this->sql_report . ' +
+ +
+
+
+ +
+ + '; + + exit_handler(); + + break; + + case 'stop': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $this->sql_report .= ' + + + + + + + + + + + + +
Query #' . $this->num_queries['total'] . '
+ + ' . $this->html_hold . ' + +

+ '; + + if ($this->query_result) + { + if (preg_match('/^(UPDATE|DELETE|REPLACE)/', $query)) + { + $this->sql_report .= 'Affected rows: ' . $this->sql_affectedrows($this->query_result) . ' | '; + } + $this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed: ' . sprintf('%.5f', $endtime - $this->curtime) . 's'; + } + else + { + $error = $this->sql_error(); + $this->sql_report .= 'FAILED - ' . $this->sql_layer . ' Error ' . $error['code'] . ': ' . htmlspecialchars($error['message']); + } + + $this->sql_report .= '



'; + + $this->sql_time += $endtime - $this->curtime; + break; + + case 'start': + $this->query_hold = $query; + $this->html_hold = ''; + + $this->_sql_report($mode, $query); + + $this->curtime = explode(' ', microtime()); + $this->curtime = $this->curtime[0] + $this->curtime[1]; + + break; + + case 'add_select_row': + + $html_table = func_get_arg(2); + $row = func_get_arg(3); + + if (!$html_table && sizeof($row)) + { + $html_table = true; + $this->html_hold .= ''; + + foreach (array_keys($row) as $val) + { + $this->html_hold .= ''; + } + $this->html_hold .= ''; + } + $this->html_hold .= ''; + + $class = 'row1'; + foreach (array_values($row) as $val) + { + $class = ($class == 'row1') ? 'row2' : 'row1'; + $this->html_hold .= ''; + } + $this->html_hold .= ''; + + return $html_table; + + break; + + case 'fromcache': + + $this->_sql_report($mode, $query); + + break; + + case 'record_fromcache': + + $endtime = func_get_arg(2); + $splittime = func_get_arg(3); + + $time_cache = $endtime - $this->curtime; + $time_db = $splittime - $endtime; + $color = ($time_db > $time_cache) ? 'green' : 'red'; + + $this->sql_report .= '
' . (($val) ? ucwords(str_replace('_', ' ', $val)) : ' ') . '
' . (($val) ? $val : ' ') . '
'; + $this->sql_report .= '
Query results obtained from the cache
'; + $this->sql_report .= '

'; + $this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed [cache]: ' . sprintf('%.5f', ($time_cache)) . 's | Elapsed [db]: ' . sprintf('%.5f', $time_db) . 's



'; + + // Pad the start time to not interfere with page timing + $starttime += $time_db; + + break; + + default: + + $this->_sql_report($mode, $query); + + break; + } + + return true; + } + + /** + * Gets the estimated number of rows in a specified table. + * + * @param string $table_name Table name + * + * @return string Number of rows in $table_name. + * Prefixed with ~ if estimated (otherwise exact). + * + * @access public + */ + function get_estimated_row_count($table_name) + { + return $this->get_row_count($table_name); + } + + /** + * Gets the exact number of rows in a specified table. + * + * @param string $table_name Table name + * + * @return string Exact number of rows in $table_name. + * + * @access public + */ + function get_row_count($table_name) + { + $sql = 'SELECT COUNT(*) AS rows_total + FROM ' . $this->sql_escape($table_name); + $result = $this->sql_query($sql); + $rows_total = $this->sql_fetchfield('rows_total'); + $this->sql_freeresult($result); + + return $rows_total; + } } -- cgit v1.2.1 From 9cc7680965ae8091fd488b8210ae66c790c83978 Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Thu, 15 Nov 2012 03:24:08 +0100 Subject: [ticket/11015] Fix some more whitespace in the driver PHPBB3-11015 --- phpBB/includes/db/driver/driver.php | 42 ++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/db/driver/driver.php b/phpBB/includes/db/driver/driver.php index 49bfcb8007..25daa7243d 100644 --- a/phpBB/includes/db/driver/driver.php +++ b/phpBB/includes/db/driver/driver.php @@ -75,9 +75,9 @@ class phpbb_db_driver function __construct() { $this->num_queries = array( - 'cached' => 0, - 'normal' => 0, - 'total' => 0, + 'cached' => 0, + 'normal' => 0, + 'total' => 0, ); // Fill default sql layer based on the class being called. @@ -288,10 +288,10 @@ class phpbb_db_driver * * Note: The two statements action_true and action_false must have the same data type (int, vchar, ...) in the database! * - * @param string $condition The condition which must be true, to use action_true rather then action_else - * @param string $action_true SQL expression that is used, if the condition is true - * @param string $action_else SQL expression that is used, if the condition is false, optional - * @return string CASE expression including the condition and statements + * @param string $condition The condition which must be true, to use action_true rather then action_else + * @param string $action_true SQL expression that is used, if the condition is true + * @param string $action_else SQL expression that is used, if the condition is false, optional + * @return string CASE expression including the condition and statements */ public function sql_case($condition, $action_true, $action_false = false) { @@ -305,9 +305,9 @@ class phpbb_db_driver /** * Build a concatenated expression * - * @param string $expr1 Base SQL expression where we append the second one - * @param string $expr2 SQL expression that is appended to the first expression - * @return string Concatenated string + * @param string $expr1 Base SQL expression where we append the second one + * @param string $expr2 SQL expression that is appended to the first expression + * @return string Concatenated string */ public function sql_concatenate($expr1, $expr2) { @@ -447,10 +447,10 @@ class phpbb_db_driver * arrays to improve comparison speed * * @access public - * @param string $field name of the sql column that shall be compared - * @param array $array array of values that are allowed (IN) or not allowed (NOT IN) - * @param bool $negate true for NOT IN (), false for IN () (default) - * @param bool $allow_empty_set If true, allow $array to be empty, this function will return 1=1 or 1=0 then. Default to false. + * @param string $field name of the sql column that shall be compared + * @param array $array array of values that are allowed (IN) or not allowed (NOT IN) + * @param bool $negate true for NOT IN (), false for IN () (default) + * @param bool $allow_empty_set If true, allow $array to be empty, this function will return 1=1 or 1=0 then. Default to false. */ function sql_in_set($field, $array, $negate = false, $allow_empty_set = false) { @@ -555,9 +555,9 @@ class phpbb_db_driver /** * Run LOWER() on DB column of type text (i.e. neither varchar nor char). * - * @param string $column_name The column name to use + * @param string $column_name The column name to use * - * @return string A SQL statement like "LOWER($column_name)" + * @return string A SQL statement like "LOWER($column_name)" */ function sql_lower_text($column_name) { @@ -1010,10 +1010,10 @@ class phpbb_db_driver /** * Gets the estimated number of rows in a specified table. * - * @param string $table_name Table name + * @param string $table_name Table name * - * @return string Number of rows in $table_name. - * Prefixed with ~ if estimated (otherwise exact). + * @return string Number of rows in $table_name. + * Prefixed with ~ if estimated (otherwise exact). * * @access public */ @@ -1025,9 +1025,9 @@ class phpbb_db_driver /** * Gets the exact number of rows in a specified table. * - * @param string $table_name Table name + * @param string $table_name Table name * - * @return string Exact number of rows in $table_name. + * @return string Exact number of rows in $table_name. * * @access public */ -- cgit v1.2.1 From 0d4a289778893d44288d9f586852038f6b52d4db Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Fri, 16 Nov 2012 01:26:36 +0100 Subject: [ticket/11015] Remove strange method_exists call PHPBB3-11015 --- phpBB/includes/db/driver/mssql.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/db/driver/mssql.php b/phpBB/includes/db/driver/mssql.php index 04bb75f5ce..2e9debf84f 100644 --- a/phpBB/includes/db/driver/mssql.php +++ b/phpBB/includes/db/driver/mssql.php @@ -157,7 +157,7 @@ class phpbb_db_driver_mssql extends phpbb_db_driver $this->sql_report('stop', $query); } - if ($cache_ttl && method_exists($cache, 'sql_save')) + if ($cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); -- cgit v1.2.1 From fc5385c2a58833af2929c75d132903a3a0918da9 Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Fri, 16 Nov 2012 01:48:03 +0100 Subject: [ticket/11015] Allow full dbms class name in config.php PHPBB3-11015 --- phpBB/includes/di/extension/config.php | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/di/extension/config.php b/phpBB/includes/di/extension/config.php index 5b2df93ff2..85ab7ab28d 100644 --- a/phpBB/includes/di/extension/config.php +++ b/phpBB/includes/di/extension/config.php @@ -43,8 +43,8 @@ class phpbb_di_extension_config extends Extension require($this->config_file); $container->setParameter('core.table_prefix', $table_prefix); - $container->setParameter('cache.driver.class', $this->fix_acm_type($acm_type)); - $container->setParameter('dbal.driver.class', 'phpbb_db_driver_'.$dbms); + $container->setParameter('cache.driver.class', $this->convert_30_acm_type($acm_type)); + $container->setParameter('dbal.driver.class', $this->convert_30_dbms($dbms)); $container->setParameter('dbal.dbhost', $dbhost); $container->setParameter('dbal.dbuser', $dbuser); $container->setParameter('dbal.dbpasswd', $dbpasswd); @@ -66,12 +66,12 @@ class phpbb_di_extension_config extends Extension } /** - * Convert old (3.0) values to 3.1 class names + * Convert 3.0 ACM type to 3.1 cache driver class name * - * @param style $acm_type ACM type - * @return ACM type class + * @param string $acm_type ACM type + * @return cache driver class */ - protected function fix_acm_type($acm_type) + protected function convert_30_acm_type($acm_type) { if (preg_match('#^[a-z]+$#', $acm_type)) { @@ -80,4 +80,20 @@ class phpbb_di_extension_config extends Extension return $acm_type; } + + /** + * Convert 3.0 dbms to 3.1 db driver class name + * + * @param string $dbms dbms parameter + * @return db driver class + */ + protected function convert_30_dbms($dbms) + { + if (!preg_match('#^phpbb_db_driver_#', $dbms)) + { + return 'phpbb_db_driver_'.$dbms; + } + + return $dbms; + } } -- cgit v1.2.1 From 5bc0f4b3d49ed1bea45464beece42906646eb026 Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sat, 17 Nov 2012 00:24:32 +0100 Subject: [ticket/11015] Move db driver class name fixing to function PHPBB3-11015 --- phpBB/includes/di/extension/config.php | 18 +----------------- phpBB/includes/functions.php | 16 ++++++++++++++++ phpBB/includes/questionnaire/questionnaire.php | 2 ++ 3 files changed, 19 insertions(+), 17 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/di/extension/config.php b/phpBB/includes/di/extension/config.php index 85ab7ab28d..97a6290066 100644 --- a/phpBB/includes/di/extension/config.php +++ b/phpBB/includes/di/extension/config.php @@ -44,7 +44,7 @@ class phpbb_di_extension_config extends Extension $container->setParameter('core.table_prefix', $table_prefix); $container->setParameter('cache.driver.class', $this->convert_30_acm_type($acm_type)); - $container->setParameter('dbal.driver.class', $this->convert_30_dbms($dbms)); + $container->setParameter('dbal.driver.class', phpbb_convert_30_dbms_to_31($dbms)); $container->setParameter('dbal.dbhost', $dbhost); $container->setParameter('dbal.dbuser', $dbuser); $container->setParameter('dbal.dbpasswd', $dbpasswd); @@ -80,20 +80,4 @@ class phpbb_di_extension_config extends Extension return $acm_type; } - - /** - * Convert 3.0 dbms to 3.1 db driver class name - * - * @param string $dbms dbms parameter - * @return db driver class - */ - protected function convert_30_dbms($dbms) - { - if (!preg_match('#^phpbb_db_driver_#', $dbms)) - { - return 'phpbb_db_driver_'.$dbms; - } - - return $dbms; - } } diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 804d89d1a2..045a28672b 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -5412,3 +5412,19 @@ function phpbb_to_numeric($input) { return ($input > PHP_INT_MAX) ? (float) $input : (int) $input; } + +/** +* Convert 3.0 dbms to 3.1 db driver class name +* +* @param string $dbms dbms parameter +* @return db driver class +*/ +function phpbb_convert_30_dbms_to_31($dbms) +{ + if (!preg_match('#^phpbb_db_driver_#', $dbms)) + { + return 'phpbb_db_driver_'.$dbms; + } + + return $dbms; +} diff --git a/phpBB/includes/questionnaire/questionnaire.php b/phpBB/includes/questionnaire/questionnaire.php index f0fb8c3c06..6bbedacbe2 100644 --- a/phpBB/includes/questionnaire/questionnaire.php +++ b/phpBB/includes/questionnaire/questionnaire.php @@ -260,6 +260,8 @@ class phpbb_questionnaire_phpbb_data_provider include("{$phpbb_root_path}config.$phpEx"); unset($dbhost, $dbport, $dbname, $dbuser, $dbpasswd); // Just a precaution + $dbms = phpbb_convert_30_dbms_to_31($dbms); + // Only send certain config vars $config_vars = array( 'active_sessions' => true, -- cgit v1.2.1 From 0372ecf14141ba2c174782f29d4fb079b4dd56c3 Mon Sep 17 00:00:00 2001 From: Igor Wiedler Date: Sat, 17 Nov 2012 01:40:32 +0100 Subject: [ticket/11015] Make phpbb_convert_30_dbms_to_31 more future proof It should allow any class name in the future, as long as that class exists. And it should give a useful error message otherwise. PHPBB3-11015 --- phpBB/includes/functions.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 045a28672b..57136a43ff 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -5421,10 +5421,15 @@ function phpbb_to_numeric($input) */ function phpbb_convert_30_dbms_to_31($dbms) { - if (!preg_match('#^phpbb_db_driver_#', $dbms)) + if (class_exists($dbms)) { - return 'phpbb_db_driver_'.$dbms; + return $dbms; } - return $dbms; + if (class_exists('phpbb_db_driver_' . $dbms)) + { + return 'phpbb_db_driver_' . $dbms; + } + + throw new \RuntimeException('You have specified an invalid dbms driver.'); } -- cgit v1.2.1 From ed548ae8ff0e87035c0c173d40212a96fa642135 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sat, 23 Apr 2011 16:43:55 -0400 Subject: [feature/template-events] Outline for RUNHOOKS template tag. Ported to the new develop, hopefully this is still sensible. PHPBB3-9550 --- phpBB/includes/template/filter.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index 66d28242a3..fc99ba5917 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -317,6 +317,12 @@ class phpbb_template_filter extends php_user_filter return ''; break; + case 'RUNHOOKS': + // return value here will be compiled code (html with embedded php). + // we don't want to wrap it in php tags here. + return 'compile_tag_run_hooks($matches[2]) . '?>'; + break; + default: return $matches[0]; break; @@ -835,6 +841,27 @@ class phpbb_template_filter extends php_user_filter return "\$_template->_php_include('$tag_args');"; } + /** + * Compile RUNHOOKS tag. + * + * $tag_args should be a single string identifying hook location. + */ + private function compile_tag_run_hooks($tag_args) + { + if (!preg_match('/^\w+$/', $tag_args)) + { + // do something + var_dump($tag_args); + } + $location = $tag_args; + // 1. find all mods defining hooks for location + // 2. obtain mods' template fragments + // 3. compile template fragments + // 4. return compiled code + // note: need to make sure we get fragments in the right order + return 'echo "test";'; + } + /** * parse expression * This is from Smarty -- cgit v1.2.1 From d6c881d0c67de80f0b60eab6be0c1dda33296657 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sun, 27 Nov 2011 00:46:36 -0500 Subject: [feature/template-events] Inject extension manager into template class. Template class passes extension manager to template compiler. Template compiler passes extension manager to template filter. Template filter will use extension manager to locate hooks as it is compiling templates. All extension manager arguments are optional. If an extension manager is not given, template hooks will not be invoked. PHPBB3-9550 --- phpBB/includes/bbcode.php | 2 +- phpBB/includes/functions_messenger.php | 2 +- phpBB/includes/template/compile.php | 8 +++++--- phpBB/includes/template/filter.php | 8 ++++++++ phpBB/includes/template/template.php | 13 +++++++++++-- 5 files changed, 26 insertions(+), 7 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/bbcode.php b/phpBB/includes/bbcode.php index b9ffa8091c..e8681420d4 100644 --- a/phpBB/includes/bbcode.php +++ b/phpBB/includes/bbcode.php @@ -134,7 +134,7 @@ class bbcode $style_resource_locator = new phpbb_style_resource_locator(); $style_path_provider = new phpbb_style_extension_path_provider($phpbb_extension_manager, new phpbb_style_path_provider()); - $template = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $style_resource_locator, new phpbb_template_context()); + $template = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $style_resource_locator, new phpbb_template_context(), $phpbb_extension_manager); $style = new phpbb_style($phpbb_root_path, $phpEx, $config, $user, $style_resource_locator, $style_path_provider, $template); $style->set_style(); $template->set_filenames(array('bbcode.html' => 'bbcode.html')); diff --git a/phpBB/includes/functions_messenger.php b/phpBB/includes/functions_messenger.php index cf03de08c4..55884caedb 100644 --- a/phpBB/includes/functions_messenger.php +++ b/phpBB/includes/functions_messenger.php @@ -210,7 +210,7 @@ class messenger { $style_resource_locator = new phpbb_style_resource_locator(); $style_path_provider = new phpbb_style_extension_path_provider($phpbb_extension_manager, new phpbb_style_path_provider()); - $tpl = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $style_resource_locator, new phpbb_template_context()); + $tpl = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $style_resource_locator, new phpbb_template_context(), $extension_manager); $style = new phpbb_style($phpbb_root_path, $phpEx, $config, $user, $style_resource_locator, $style_path_provider, $tpl); $this->tpl_msg[$template_lang . $template_file] = $tpl; diff --git a/phpBB/includes/template/compile.php b/phpBB/includes/template/compile.php index 82b301c1a2..fb7d146701 100644 --- a/phpBB/includes/template/compile.php +++ b/phpBB/includes/template/compile.php @@ -35,16 +35,18 @@ class phpbb_template_compile /** * Constructor. * - * @param bool @allow_php Whether PHP code will be allowed in templates (inline PHP code, PHP tag and INCLUDEPHP tag) + * @param bool $allow_php Whether PHP code will be allowed in templates (inline PHP code, PHP tag and INCLUDEPHP tag) * @param phpbb_style_resource_locator $locator Resource locator * @param string $phpbb_root_path Path to phpBB root directory + * @param phpbb_extension_manager $extension_manager Extension manager to use for finding template fragments in extensions; if null, template hooks will not be invoked */ - public function __construct($allow_php, $locator, $phpbb_root_path) + public function __construct($allow_php, $locator, $phpbb_root_path, $extension_manager = null) { $this->filter_params = array( 'allow_php' => $allow_php, 'locator' => $locator, - 'phpbb_root_path' => $phpbb_root_path + 'phpbb_root_path' => $phpbb_root_path, + 'extension_manager' => $extension_manager, ); } diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index fc99ba5917..911f21ef00 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -87,6 +87,13 @@ class phpbb_template_filter extends php_user_filter */ private $phpbb_root_path; + /** + * Extension manager. + * + * @var phpbb_extension_manager + */ + private $extension_manager; + /** * Stream filter * @@ -148,6 +155,7 @@ class phpbb_template_filter extends php_user_filter $this->allow_php = $this->params['allow_php']; $this->locator = $this->params['locator']; $this->phpbb_root_path = $this->params['phpbb_root_path']; + $this->extension_manager = $this->params['extension_manager']; return true; } diff --git a/phpBB/includes/template/template.php b/phpBB/includes/template/template.php index 5396ddbfad..96a16fee77 100644 --- a/phpBB/includes/template/template.php +++ b/phpBB/includes/template/template.php @@ -74,6 +74,13 @@ class phpbb_template */ private $locator; + /** + * Extension manager. + * + * @var phpbb_extension_manager + */ + private $extension_manager; + /** * Constructor. * @@ -81,8 +88,9 @@ class phpbb_template * @param user $user current user * @param phpbb_template_locator $locator template locator * @param phpbb_template_context $context template context + * @param phpbb_extension_manager $extension_manager extension manager, if null then template hooks will not be invoked */ - public function __construct($phpbb_root_path, $php_ext, $config, $user, phpbb_template_locator $locator, phpbb_template_context $context) + public function __construct($phpbb_root_path, $php_ext, $config, $user, phpbb_template_locator $locator, phpbb_template_context $context, phpbb_extension_manager $extension_manager = null) { $this->phpbb_root_path = $phpbb_root_path; $this->php_ext = $php_ext; @@ -90,6 +98,7 @@ class phpbb_template $this->user = $user; $this->locator = $locator; $this->context = $context; + $this->extension_manager = $extension_manager; } /** @@ -282,7 +291,7 @@ class phpbb_template return new phpbb_template_renderer_include($output_file, $this); } - $compile = new phpbb_template_compile($this->config['tpl_allow_php'], $this->locator, $this->phpbb_root_path); + $compile = new phpbb_template_compile($this->config['tpl_allow_php'], $this->locator, $this->phpbb_root_path, $this->extension_manager); if ($compile->compile_file_to_file($source_file, $output_file) !== false) { -- cgit v1.2.1 From 09aae1ea30b199630c3972bdc483db476bda9a7e Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sat, 3 Dec 2011 21:08:31 -0500 Subject: [feature/template-events] Inject template compile into template filter. This is needed for hooks in order for the filter to compile template files from extensions. PHPBB3-9550 --- phpBB/includes/template/filter.php | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index 911f21ef00..3d39b3b4ed 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -94,6 +94,13 @@ class phpbb_template_filter extends php_user_filter */ private $extension_manager; + /** + * Template compiler. + * + * @var phpbb_template_compile + */ + private $template_compile; + /** * Stream filter * @@ -156,6 +163,7 @@ class phpbb_template_filter extends php_user_filter $this->locator = $this->params['locator']; $this->phpbb_root_path = $this->params['phpbb_root_path']; $this->extension_manager = $this->params['extension_manager']; + $this->template_compile = $this->params['template_compile']; return true; } -- cgit v1.2.1 From 66232031e4c69a0f5ba25699ba908946bd445967 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sun, 27 Nov 2011 00:49:39 -0500 Subject: [feature/template-events] Really basic template hook implementation. PHPBB3-9550 --- phpBB/includes/template/filter.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index 3d39b3b4ed..6151983be0 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -870,6 +870,26 @@ class phpbb_template_filter extends php_user_filter var_dump($tag_args); } $location = $tag_args; + + if ($this->phpbb_extension_manager) + { + $finder = $this->phpbb_extension_manager->get_finder(); + + $files = $finder + ->extension_prefix($location) + ->extension_suffix('.html') + ->extension_directory("/styles/universal/template") + ->get_files(); + + $all_compiled = ''; + foreach ($files as $file) + { + $compiled = $this->template_compile->compile_file($file); + $all_compiled .= $compiled; + } + return '?>' . $all_compiled . ' Date: Fri, 3 Feb 2012 02:21:37 -0500 Subject: [feature/template-events] Fix property name for extension manager. PHPBB3-9550 --- phpBB/includes/template/filter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index 6151983be0..ea5b17c11a 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -871,9 +871,9 @@ class phpbb_template_filter extends php_user_filter } $location = $tag_args; - if ($this->phpbb_extension_manager) + if ($this->extension_manager) { - $finder = $this->phpbb_extension_manager->get_finder(); + $finder = $this->extension_manager->get_finder(); $files = $finder ->extension_prefix($location) -- cgit v1.2.1 From ea094dd91af624c72ac41fde6073dd8918620e68 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 6 Mar 2012 19:45:13 -0500 Subject: [feature/template-events] Rename universal to all (for template fragments). PHPBB3-9550 --- phpBB/includes/template/filter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index ea5b17c11a..08eb9bdd97 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -878,7 +878,7 @@ class phpbb_template_filter extends php_user_filter $files = $finder ->extension_prefix($location) ->extension_suffix('.html') - ->extension_directory("/styles/universal/template") + ->extension_directory("/styles/all/template") ->get_files(); $all_compiled = ''; -- cgit v1.2.1 From dd7c5183fbc5401c85f530a304b70fbb0b5d7fbe Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Thu, 19 Apr 2012 00:21:54 -0400 Subject: [feature/template-events] Add template_compile to template filter params. PHPBB3-9550 --- phpBB/includes/template/compile.php | 1 + 1 file changed, 1 insertion(+) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/compile.php b/phpBB/includes/template/compile.php index fb7d146701..b63da05394 100644 --- a/phpBB/includes/template/compile.php +++ b/phpBB/includes/template/compile.php @@ -47,6 +47,7 @@ class phpbb_template_compile 'locator' => $locator, 'phpbb_root_path' => $phpbb_root_path, 'extension_manager' => $extension_manager, + 'template_compile' => $this, ); } -- cgit v1.2.1 From a6c7fbc59d02ed44ef90e340c4f957a8c5ac9ca5 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Fri, 16 Mar 2012 00:22:42 -0400 Subject: [feature/template-events] Pass top-level template name to template filter. This will be used to invoke template-specific hooks. PHPBB3-9550 --- phpBB/includes/template/compile.php | 4 +++- phpBB/includes/template/filter.php | 13 ++++++++++++- phpBB/includes/template/template.php | 12 +++++++++++- 3 files changed, 26 insertions(+), 3 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/compile.php b/phpBB/includes/template/compile.php index b63da05394..06ff60387a 100644 --- a/phpBB/includes/template/compile.php +++ b/phpBB/includes/template/compile.php @@ -36,14 +36,16 @@ class phpbb_template_compile * Constructor. * * @param bool $allow_php Whether PHP code will be allowed in templates (inline PHP code, PHP tag and INCLUDEPHP tag) + * @param string $template_name Name of top-level template being compiled * @param phpbb_style_resource_locator $locator Resource locator * @param string $phpbb_root_path Path to phpBB root directory * @param phpbb_extension_manager $extension_manager Extension manager to use for finding template fragments in extensions; if null, template hooks will not be invoked */ - public function __construct($allow_php, $locator, $phpbb_root_path, $extension_manager = null) + public function __construct($allow_php, $template_name, $locator, $phpbb_root_path, $extension_manager = null) { $this->filter_params = array( 'allow_php' => $allow_php, + 'template_name' => $template_name, 'locator' => $locator, 'phpbb_root_path' => $phpbb_root_path, 'extension_manager' => $extension_manager, diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index 08eb9bdd97..eca9a0d48c 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -87,6 +87,16 @@ class phpbb_template_filter extends php_user_filter */ private $phpbb_root_path; + /** + * Name of the top-level template being compiled and/or rendered. + * + * This is used by hooks implementation to invoke template-specific + * template hooks. + * + * @var string + */ + private $template_name; + /** * Extension manager. * @@ -152,7 +162,7 @@ class phpbb_template_filter extends php_user_filter /** * Initializer, called on creation. * - * Get the allow_php option, root directory and locator from params, + * Get the allow_php option, template_name, root directory and locator from params, * which are passed to stream_filter_append. */ public function onCreate() @@ -162,6 +172,7 @@ class phpbb_template_filter extends php_user_filter $this->allow_php = $this->params['allow_php']; $this->locator = $this->params['locator']; $this->phpbb_root_path = $this->params['phpbb_root_path']; + $this->template_name = $this->params['template_name']; $this->extension_manager = $this->params['extension_manager']; $this->template_compile = $this->params['template_compile']; return true; diff --git a/phpBB/includes/template/template.php b/phpBB/includes/template/template.php index 96a16fee77..c43c1ddf99 100644 --- a/phpBB/includes/template/template.php +++ b/phpBB/includes/template/template.php @@ -81,6 +81,16 @@ class phpbb_template */ private $extension_manager; + /** + * Name of the top-level template being compiled and/or rendered. + * + * This is used by hooks implementation to invoke template-specific + * template hooks. + * + * @var string + */ + private $template_name; + /** * Constructor. * @@ -291,7 +301,7 @@ class phpbb_template return new phpbb_template_renderer_include($output_file, $this); } - $compile = new phpbb_template_compile($this->config['tpl_allow_php'], $this->locator, $this->phpbb_root_path, $this->extension_manager); + $compile = new phpbb_template_compile($this->config['tpl_allow_php'], $this->template_name, $this->locator, $this->phpbb_root_path, $this->extension_manager); if ($compile->compile_file_to_file($source_file, $output_file) !== false) { -- cgit v1.2.1 From bdbb382a26efb4194e2eb1b6ee5f3913829fc9d1 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Fri, 16 Mar 2012 00:54:09 -0400 Subject: [feature/template-events] Invoke template hooks that are template-specific. PHPBB3-9550 --- phpBB/includes/template/filter.php | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index eca9a0d48c..a158dd4074 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -892,6 +892,13 @@ class phpbb_template_filter extends php_user_filter ->extension_directory("/styles/all/template") ->get_files(); + $files = array_merge($files, $finder + ->extension_prefix($location) + ->extension_suffix('.html') + // XXX is this safe? + ->extension_directory("/styles/" . $this->template_name . "/template") + ->get_files()); + $all_compiled = ''; foreach ($files as $file) { -- cgit v1.2.1 From ecdb54fc048a0805f724d7a3931373f99744923a Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Fri, 16 Mar 2012 02:09:46 -0400 Subject: [feature/template-events] PHP does not parse , avoid generating it. PHPBB3-9550 --- phpBB/includes/template/filter.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index a158dd4074..5129618f03 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -905,7 +905,9 @@ class phpbb_template_filter extends php_user_filter $compiled = $this->template_compile->compile_file($file); $all_compiled .= $compiled; } - return '?>' . $all_compiled . ' sans the spaces + return ' ?>' . $all_compiled . ' Date: Tue, 20 Mar 2012 06:53:48 +0000 Subject: [feature/template-events] Rename RUNHOOKS to EVENT Rename the way to add template events PHPBB3-9550 --- phpBB/includes/template/filter.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index 5129618f03..2c0057b64c 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -344,10 +344,10 @@ class phpbb_template_filter extends php_user_filter return ''; break; - case 'RUNHOOKS': + case 'EVENT': // return value here will be compiled code (html with embedded php). // we don't want to wrap it in php tags here. - return 'compile_tag_run_hooks($matches[2]) . '?>'; + return 'compile_tag_event($matches[2]) . '?>'; break; default: @@ -873,7 +873,7 @@ class phpbb_template_filter extends php_user_filter * * $tag_args should be a single string identifying hook location. */ - private function compile_tag_run_hooks($tag_args) + private function compile_tag_event($tag_args) { if (!preg_match('/^\w+$/', $tag_args)) { -- cgit v1.2.1 From 04f55ba3066d4713b6189062f8a61f12ca02ea34 Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Thu, 18 Oct 2012 17:21:08 -0500 Subject: [feature/template-events] Correct indentation PHPBB3-9550 --- phpBB/includes/template/filter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index 2c0057b64c..39317c6bf3 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -348,7 +348,7 @@ class phpbb_template_filter extends php_user_filter // return value here will be compiled code (html with embedded php). // we don't want to wrap it in php tags here. return 'compile_tag_event($matches[2]) . '?>'; - break; + break; default: return $matches[0]; -- cgit v1.2.1 From f83627763839e601db6b077e4f2f76436c263cd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Fr=C3=A8rejean?= Date: Thu, 19 Apr 2012 13:06:28 +0200 Subject: [feature/template-events] RUNHOOKS -> EVENT PHPBB3-9550 --- phpBB/includes/template/filter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index 39317c6bf3..f3e0f6017c 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -869,7 +869,7 @@ class phpbb_template_filter extends php_user_filter } /** - * Compile RUNHOOKS tag. + * Compile EVENT tag. * * $tag_args should be a single string identifying hook location. */ -- cgit v1.2.1 From 132bbede2b5ca5e1bbb371bdaef0bb3041c67316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Fr=C3=A8rejean?= Date: Wed, 18 Apr 2012 15:35:09 +0200 Subject: [feature/template-events] Handle incorrect template event identifiers If the template event identifier is wrongly formatted phpBB will triggern an `E_USER_NOTICE` if the `DEBUG` constant is set, otherwise the location is ignored. PHPBB3-9550 --- phpBB/includes/template/filter.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index f3e0f6017c..50882b2855 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -877,8 +877,16 @@ class phpbb_template_filter extends php_user_filter { if (!preg_match('/^\w+$/', $tag_args)) { - // do something - var_dump($tag_args); + // The hook location is wrongly formatted, + // if the `DEBUG` constant is set then trigger a waring, + // otherwise drop the hook and continue + if (defined('DEBUG')) + { + global $user; + trigger_error($user->lang('ERR_TEMPLATE_EVENT_LOCATION', $tag_args), E_USER_NOTICE); + } + + return; } $location = $tag_args; -- cgit v1.2.1 From 6fc0c889fd714945613be1c51316b1386f3c88f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Fr=C3=A8rejean?= Date: Thu, 19 Apr 2012 13:57:11 +0200 Subject: [feature/template-events] Remove comment Remove comment per Nils in the PR. PHPBB3-9550 --- phpBB/includes/template/filter.php | 1 - 1 file changed, 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index 50882b2855..798cadaa2a 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -903,7 +903,6 @@ class phpbb_template_filter extends php_user_filter $files = array_merge($files, $finder ->extension_prefix($location) ->extension_suffix('.html') - // XXX is this safe? ->extension_directory("/styles/" . $this->template_name . "/template") ->get_files()); -- cgit v1.2.1 From 2add66c0ebd49bb2f8beee538676fe34f969b33e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Fr=C3=A8rejean?= Date: Thu, 19 Apr 2012 13:58:41 +0200 Subject: [feature/template-events] Add additional space for editors Use `' ?'. '>'` rather than `' ?>'` as the latter causes problems in some editors. PHPBB3-9550 --- phpBB/includes/template/filter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index 798cadaa2a..1ec1467c09 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -914,7 +914,7 @@ class phpbb_template_filter extends php_user_filter } // Need spaces inside php tags as php cannot grok // < ?php? > sans the spaces - return ' ?>' . $all_compiled . '' . $all_compiled . ' Date: Thu, 19 Apr 2012 14:00:42 +0200 Subject: [feature/template-events] Fix typo (waring -> warning) PHPBB3-9550 --- phpBB/includes/template/filter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index 1ec1467c09..453e2a05ce 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -878,7 +878,7 @@ class phpbb_template_filter extends php_user_filter if (!preg_match('/^\w+$/', $tag_args)) { // The hook location is wrongly formatted, - // if the `DEBUG` constant is set then trigger a waring, + // if the `DEBUG` constant is set then trigger a warning, // otherwise drop the hook and continue if (defined('DEBUG')) { -- cgit v1.2.1 From f46f48a2cfbc923f4e4734dfd200cc6ddbfd689e Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Thu, 10 May 2012 04:22:39 -0400 Subject: [feature/template-events] Chase template/style renames and changes. PHPBB3-9550 --- phpBB/includes/template/template.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/template.php b/phpBB/includes/template/template.php index c43c1ddf99..19ee59323a 100644 --- a/phpBB/includes/template/template.php +++ b/phpBB/includes/template/template.php @@ -301,7 +301,7 @@ class phpbb_template return new phpbb_template_renderer_include($output_file, $this); } - $compile = new phpbb_template_compile($this->config['tpl_allow_php'], $this->template_name, $this->locator, $this->phpbb_root_path, $this->extension_manager); + $compile = new phpbb_template_compile($this->config['tpl_allow_php'], $this->template_name, $this->locator, $this->phpbb_root_path, $this->extension_manager, $this->user); if ($compile->compile_file_to_file($source_file, $output_file) !== false) { -- cgit v1.2.1 From 45a1219886b039fbb4bda740accbfbbdbf971022 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Thu, 10 May 2012 03:53:30 -0400 Subject: [feature/template-events] Always commit suicide for invalid event names. Note: suicide happens for syntactically invalid event names, e.g. event names containing punctuation. Event names for which there are no events are silently dropped. PHPBB3-9550 --- phpBB/includes/template/filter.php | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index 453e2a05ce..2706eb9040 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -878,15 +878,8 @@ class phpbb_template_filter extends php_user_filter if (!preg_match('/^\w+$/', $tag_args)) { // The hook location is wrongly formatted, - // if the `DEBUG` constant is set then trigger a warning, - // otherwise drop the hook and continue - if (defined('DEBUG')) - { - global $user; - trigger_error($user->lang('ERR_TEMPLATE_EVENT_LOCATION', $tag_args), E_USER_NOTICE); - } - - return; + global $user; + trigger_error($user->lang('ERR_TEMPLATE_EVENT_LOCATION', $tag_args), E_USER_ERROR); } $location = $tag_args; -- cgit v1.2.1 From 1b36fc3a6074a661a1b32c015fb9931f3470c61c Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Thu, 10 May 2012 04:23:18 -0400 Subject: [feature/template-events] Handle user access correctly. Pass through $user from template to filter. Allow $user to be null for standalone usage of the template engine. PHPBB3-9550 --- phpBB/includes/template/compile.php | 4 +++- phpBB/includes/template/filter.php | 20 ++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/compile.php b/phpBB/includes/template/compile.php index 06ff60387a..e2e9a095dc 100644 --- a/phpBB/includes/template/compile.php +++ b/phpBB/includes/template/compile.php @@ -40,8 +40,9 @@ class phpbb_template_compile * @param phpbb_style_resource_locator $locator Resource locator * @param string $phpbb_root_path Path to phpBB root directory * @param phpbb_extension_manager $extension_manager Extension manager to use for finding template fragments in extensions; if null, template hooks will not be invoked + * @param phpbb_user $user Current user */ - public function __construct($allow_php, $template_name, $locator, $phpbb_root_path, $extension_manager = null) + public function __construct($allow_php, $template_name, $locator, $phpbb_root_path, $extension_manager = null, $user = null) { $this->filter_params = array( 'allow_php' => $allow_php, @@ -49,6 +50,7 @@ class phpbb_template_compile 'locator' => $locator, 'phpbb_root_path' => $phpbb_root_path, 'extension_manager' => $extension_manager, + 'user' => $user, 'template_compile' => $this, ); } diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index 2706eb9040..ed81c4cd8a 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -104,6 +104,12 @@ class phpbb_template_filter extends php_user_filter */ private $extension_manager; + /** + * Current user + * @var phpbb_user + */ + private $user; + /** * Template compiler. * @@ -174,6 +180,10 @@ class phpbb_template_filter extends php_user_filter $this->phpbb_root_path = $this->params['phpbb_root_path']; $this->template_name = $this->params['template_name']; $this->extension_manager = $this->params['extension_manager']; + if (isset($this->params['user'])) + { + $this->user = $this->params['user']; + } $this->template_compile = $this->params['template_compile']; return true; } @@ -878,8 +888,14 @@ class phpbb_template_filter extends php_user_filter if (!preg_match('/^\w+$/', $tag_args)) { // The hook location is wrongly formatted, - global $user; - trigger_error($user->lang('ERR_TEMPLATE_EVENT_LOCATION', $tag_args), E_USER_ERROR); + if ($this->user) + { + trigger_error($this->user->lang('ERR_TEMPLATE_EVENT_LOCATION', $tag_args), E_USER_ERROR); + } + else + { + trigger_error(sprintf('The specified template event location [%s] is wrongly formatted.', $tag_args), E_USER_ERROR); + } } $location = $tag_args; -- cgit v1.2.1 From bd63b17d0030706e080188ab6bf3bdec8466ae82 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Thu, 10 May 2012 05:06:00 -0400 Subject: [feature/template-events] Move comment to the function docblock. PHPBB3-9550 --- phpBB/includes/template/filter.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index ed81c4cd8a..9f77c4a1b7 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -266,7 +266,9 @@ class phpbb_template_filter extends php_user_filter } /** - * Callback for replacing matched tokens with PHP code + * Callback for replacing matched tokens with compiled template code. + * + * Compiled template code is an HTML stream with embedded PHP. * * @param array $matches Regular expression matches * @return string compiled template code @@ -355,8 +357,6 @@ class phpbb_template_filter extends php_user_filter break; case 'EVENT': - // return value here will be compiled code (html with embedded php). - // we don't want to wrap it in php tags here. return 'compile_tag_event($matches[2]) . '?>'; break; -- cgit v1.2.1 From faf96a1b4017c2bdceb4458c1d84959cb07da9bb Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Thu, 10 May 2012 05:11:45 -0400 Subject: [feature/template-events] Delete old comments/test code. PHPBB3-9550 --- phpBB/includes/template/filter.php | 7 ------- 1 file changed, 7 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index 9f77c4a1b7..cb0b2b0cd9 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -925,13 +925,6 @@ class phpbb_template_filter extends php_user_filter // < ?php? > sans the spaces return ' ?' . '>' . $all_compiled . ' Date: Thu, 10 May 2012 05:23:54 -0400 Subject: [feature/template-events] Update EVENT tag documentation. It should now fairly closely reflect what actually happens. PHPBB3-9550 --- phpBB/includes/template/filter.php | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index cb0b2b0cd9..9f814f5c5f 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -881,7 +881,27 @@ class phpbb_template_filter extends php_user_filter /** * Compile EVENT tag. * - * $tag_args should be a single string identifying hook location. + * $tag_args should be a single string identifying the event. + * The event name can contain letters, numbers and underscores only. + * If an invalid event name is specified, an E_USER_ERROR will be + * triggered. + * + * Event tags are only functional when the template engine has + * an instance of the extension manager. Extension manager would + * be called upon to find all extensions listening for the specified + * event, and to obtain additional template fragments. All such + * template fragments will be compiled and included in the generated + * compiled template code for the current template being compiled. + * + * The above means that whenever an extension is enabled or disabled, + * template cache should be cleared in order to update the compiled + * template code for the active set of template event listeners. + * + * This also means that extensions cannot return different template + * fragments at different times. Once templates are compiled, changing + * such template fragments would have no effect. + * + * @param string $tag_args EVENT tag arguments, as a string - for EVENT this is the event name */ private function compile_tag_event($tag_args) { -- cgit v1.2.1 From 09b4cf2f4c6df4d967f4df66f2bb29e38ee10a1d Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Fri, 11 May 2012 13:38:10 -0400 Subject: [feature/template-events] Report when templates cannot be compiled. PHPBB3-9550 --- phpBB/includes/template/filter.php | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index 9f814f5c5f..385612b094 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -939,6 +939,10 @@ class phpbb_template_filter extends php_user_filter foreach ($files as $file) { $compiled = $this->template_compile->compile_file($file); + if ($compiled === false) + { + trigger_error(sprintf('The file could not be compiled: %s', phpbb_filter_root_path($file)), E_USER_ERROR); + } $all_compiled .= $compiled; } // Need spaces inside php tags as php cannot grok -- cgit v1.2.1 From 9c31a0ffc785e30c4ff87e8d9d66e7989b55d8ef Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Fri, 11 May 2012 14:10:45 -0400 Subject: [feature/template-events] Rename template_name to style_name. "Style name" makes a lot more sense and should be in line with recent style/template changes. PHPBB3-9550 --- phpBB/includes/template/compile.php | 6 +++--- phpBB/includes/template/filter.php | 20 ++++++++++---------- phpBB/includes/template/template.php | 9 +++++---- 3 files changed, 18 insertions(+), 17 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/compile.php b/phpBB/includes/template/compile.php index e2e9a095dc..c2762bfd59 100644 --- a/phpBB/includes/template/compile.php +++ b/phpBB/includes/template/compile.php @@ -36,17 +36,17 @@ class phpbb_template_compile * Constructor. * * @param bool $allow_php Whether PHP code will be allowed in templates (inline PHP code, PHP tag and INCLUDEPHP tag) - * @param string $template_name Name of top-level template being compiled + * @param string $style_name Name of style to which the template being compiled belongs * @param phpbb_style_resource_locator $locator Resource locator * @param string $phpbb_root_path Path to phpBB root directory * @param phpbb_extension_manager $extension_manager Extension manager to use for finding template fragments in extensions; if null, template hooks will not be invoked * @param phpbb_user $user Current user */ - public function __construct($allow_php, $template_name, $locator, $phpbb_root_path, $extension_manager = null, $user = null) + public function __construct($allow_php, $style_name, $locator, $phpbb_root_path, $extension_manager = null, $user = null) { $this->filter_params = array( 'allow_php' => $allow_php, - 'template_name' => $template_name, + 'style_name' => $style_name, 'locator' => $locator, 'phpbb_root_path' => $phpbb_root_path, 'extension_manager' => $extension_manager, diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index 385612b094..a87bfaef34 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -88,14 +88,15 @@ class phpbb_template_filter extends php_user_filter private $phpbb_root_path; /** - * Name of the top-level template being compiled and/or rendered. + * Name of the style that the template being compiled and/or rendered + * belongs to. * - * This is used by hooks implementation to invoke template-specific + * This is used by hooks implementation to invoke style-specific * template hooks. * * @var string */ - private $template_name; + private $style_name; /** * Extension manager. @@ -168,7 +169,7 @@ class phpbb_template_filter extends php_user_filter /** * Initializer, called on creation. * - * Get the allow_php option, template_name, root directory and locator from params, + * Get the allow_php option, style_name, root directory and locator from params, * which are passed to stream_filter_append. */ public function onCreate() @@ -178,7 +179,7 @@ class phpbb_template_filter extends php_user_filter $this->allow_php = $this->params['allow_php']; $this->locator = $this->params['locator']; $this->phpbb_root_path = $this->params['phpbb_root_path']; - $this->template_name = $this->params['template_name']; + $this->style_name = $this->params['style_name']; $this->extension_manager = $this->params['extension_manager']; if (isset($this->params['user'])) { @@ -932,17 +933,16 @@ class phpbb_template_filter extends php_user_filter $files = array_merge($files, $finder ->extension_prefix($location) ->extension_suffix('.html') - ->extension_directory("/styles/" . $this->template_name . "/template") + ->extension_directory("/styles/" . $this->style_name . "/template") ->get_files()); $all_compiled = ''; foreach ($files as $file) { $compiled = $this->template_compile->compile_file($file); - if ($compiled === false) - { - trigger_error(sprintf('The file could not be compiled: %s', phpbb_filter_root_path($file)), E_USER_ERROR); - } + if ($compiled === false) { + trigger_error(sprintf('The file could not be compiled: %s', phpbb_filter_root_path($file)), E_USER_ERROR); + } $all_compiled .= $compiled; } // Need spaces inside php tags as php cannot grok diff --git a/phpBB/includes/template/template.php b/phpBB/includes/template/template.php index 19ee59323a..3de5cd45a5 100644 --- a/phpBB/includes/template/template.php +++ b/phpBB/includes/template/template.php @@ -82,14 +82,15 @@ class phpbb_template private $extension_manager; /** - * Name of the top-level template being compiled and/or rendered. + * Name of the style that the template being compiled and/or rendered + * belongs to. * - * This is used by hooks implementation to invoke template-specific + * This is used by hooks implementation to invoke style-specific * template hooks. * * @var string */ - private $template_name; + private $style_name; /** * Constructor. @@ -301,7 +302,7 @@ class phpbb_template return new phpbb_template_renderer_include($output_file, $this); } - $compile = new phpbb_template_compile($this->config['tpl_allow_php'], $this->template_name, $this->locator, $this->phpbb_root_path, $this->extension_manager, $this->user); + $compile = new phpbb_template_compile($this->config['tpl_allow_php'], $this->style_name, $this->locator, $this->phpbb_root_path, $this->extension_manager, $this->user); if ($compile->compile_file_to_file($source_file, $output_file) !== false) { -- cgit v1.2.1 From 2fb40060562dc7efcfda06cde05c2abb78a2c3c2 Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Wed, 31 Oct 2012 11:10:40 -0400 Subject: [feature/template-events] Indentation fix. PHPBB3-9550 --- phpBB/includes/template/filter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index a87bfaef34..8c102ea0fe 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -89,7 +89,7 @@ class phpbb_template_filter extends php_user_filter /** * Name of the style that the template being compiled and/or rendered - * belongs to. + * belongs to. * * This is used by hooks implementation to invoke style-specific * template hooks. -- cgit v1.2.1 From 4ed9e4124e592ddb7fe2696e45261e74edb89ddd Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Wed, 31 Oct 2012 11:11:33 -0400 Subject: [feature/template-events] Wording: wrongly -> improperly. PHPBB3-9550 --- phpBB/includes/template/filter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index 8c102ea0fe..8df43e5b01 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -908,14 +908,14 @@ class phpbb_template_filter extends php_user_filter { if (!preg_match('/^\w+$/', $tag_args)) { - // The hook location is wrongly formatted, + // The hook location is improperly formatted, if ($this->user) { trigger_error($this->user->lang('ERR_TEMPLATE_EVENT_LOCATION', $tag_args), E_USER_ERROR); } else { - trigger_error(sprintf('The specified template event location [%s] is wrongly formatted.', $tag_args), E_USER_ERROR); + trigger_error(sprintf('The specified template event location [%s] is improperly formatted.', $tag_args), E_USER_ERROR); } } $location = $tag_args; -- cgit v1.2.1 From 6c7f1f7bdeba88fdef27fab537f973464bba6183 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Fri, 2 Nov 2012 19:58:01 -0400 Subject: [feature/template-events] Cosmetic changes. PHPBB3-9550 --- phpBB/includes/template/locator.php | 4 ++-- phpBB/includes/template/template.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/locator.php b/phpBB/includes/template/locator.php index 42db91efb2..f6fd20bcc2 100644 --- a/phpBB/includes/template/locator.php +++ b/phpBB/includes/template/locator.php @@ -39,7 +39,7 @@ interface phpbb_template_locator * Sets the template filenames for handles. $filename_array * should be a hash of handle => filename pairs. * - * @param array $filname_array Should be a hash of handle => filename pairs. + * @param array $filename_array Should be a hash of handle => filename pairs. */ public function set_filenames(array $filename_array); @@ -66,7 +66,7 @@ interface phpbb_template_locator * returns actually exists, it is faster than get_source_file_for_handle. * * Use get_source_file_for_handle to obtain the actual path that is - * guaranteed to exist (which might come from the parent style + * guaranteed to exist (which might come from the parent style * directory if primary style has parent styles). * * This function will trigger an error if the handle was never diff --git a/phpBB/includes/template/template.php b/phpBB/includes/template/template.php index 3de5cd45a5..52c08326d5 100644 --- a/phpBB/includes/template/template.php +++ b/phpBB/includes/template/template.php @@ -83,7 +83,7 @@ class phpbb_template /** * Name of the style that the template being compiled and/or rendered - * belongs to. + * belongs to. * * This is used by hooks implementation to invoke style-specific * template hooks. @@ -115,7 +115,7 @@ class phpbb_template /** * Sets the template filenames for handles. * - * @param array $filname_array Should be a hash of handle => filename pairs. + * @param array $filename_array Should be a hash of handle => filename pairs. */ public function set_filenames(array $filename_array) { -- cgit v1.2.1 From 0141154ceb58702a308c76b45242cc47f51d3e01 Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Wed, 31 Oct 2012 12:03:22 -0400 Subject: [feature/template-events] Indentation fix. PHPBB3-9550 --- phpBB/includes/template/filter.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index 8df43e5b01..5d9e00553f 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -940,9 +940,12 @@ class phpbb_template_filter extends php_user_filter foreach ($files as $file) { $compiled = $this->template_compile->compile_file($file); - if ($compiled === false) { - trigger_error(sprintf('The file could not be compiled: %s', phpbb_filter_root_path($file)), E_USER_ERROR); - } + + if ($compiled === false) + { + trigger_error(sprintf('The file could not be compiled: %s', phpbb_filter_root_path($file)), E_USER_ERROR); + } + $all_compiled .= $compiled; } // Need spaces inside php tags as php cannot grok -- cgit v1.2.1 From 44d6dc4c4ccf969fd3d84f3b39bfd24ecd3a3f9d Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Thu, 8 Nov 2012 12:21:06 -0500 Subject: [feature/template-events] Convert a single style name to array of them. This allows template code to know the entire style hierarchy for templates being rendered. PHPBB3-9550 --- phpBB/includes/functions_messenger.php | 2 +- phpBB/includes/style/style.php | 11 ++++++++++- phpBB/includes/template/compile.php | 6 +++--- phpBB/includes/template/filter.php | 13 ++++++------- phpBB/includes/template/template.php | 11 +++++------ 5 files changed, 25 insertions(+), 18 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions_messenger.php b/phpBB/includes/functions_messenger.php index 55884caedb..2043e2f7be 100644 --- a/phpBB/includes/functions_messenger.php +++ b/phpBB/includes/functions_messenger.php @@ -231,7 +231,7 @@ class messenger } } - $style->set_custom_style($template_lang . '_email', array($template_path, $fallback_template_path), ''); + $style->set_custom_style($template_lang . '_email', array($template_path, $fallback_template_path), array(), ''); $tpl->set_filenames(array( 'body' => $template_file . '.txt', diff --git a/phpBB/includes/style/style.php b/phpBB/includes/style/style.php index effd496fb9..9ce96c5da5 100644 --- a/phpBB/includes/style/style.php +++ b/phpBB/includes/style/style.php @@ -110,18 +110,27 @@ class phpbb_style * * @param string $name Name of style, used for cache prefix. Examples: "admin", "prosilver" * @param array or string $paths Array of style paths, relative to current root directory + * @param array $names Array of names of templates in inheritance tree order, used by extensions. If empty, $name will be used. * @param string $template_path Path to templates, relative to style directory. False if path should be set to default (templates/). */ - public function set_custom_style($name, $paths, $template_path = false) + public function set_custom_style($name, $paths, $names = array(), $template_path = false) { if (is_string($paths)) { $paths = array($paths); } + if (empty($names)) + { + $names = array($name); + } + $this->names = $names; + $this->provider->set_styles($paths); $this->locator->set_paths($this->provider); + $this->template->style_names = $names; + if ($template_path !== false) { $this->locator->set_template_path($template_path); diff --git a/phpBB/includes/template/compile.php b/phpBB/includes/template/compile.php index c2762bfd59..76ad2317c9 100644 --- a/phpBB/includes/template/compile.php +++ b/phpBB/includes/template/compile.php @@ -36,17 +36,17 @@ class phpbb_template_compile * Constructor. * * @param bool $allow_php Whether PHP code will be allowed in templates (inline PHP code, PHP tag and INCLUDEPHP tag) - * @param string $style_name Name of style to which the template being compiled belongs + * @param array $style_names Name of style to which the template being compiled belongs and parents in style tree order * @param phpbb_style_resource_locator $locator Resource locator * @param string $phpbb_root_path Path to phpBB root directory * @param phpbb_extension_manager $extension_manager Extension manager to use for finding template fragments in extensions; if null, template hooks will not be invoked * @param phpbb_user $user Current user */ - public function __construct($allow_php, $style_name, $locator, $phpbb_root_path, $extension_manager = null, $user = null) + public function __construct($allow_php, $style_names, $locator, $phpbb_root_path, $extension_manager = null, $user = null) { $this->filter_params = array( 'allow_php' => $allow_php, - 'style_name' => $style_name, + 'style_names' => $style_names, 'locator' => $locator, 'phpbb_root_path' => $phpbb_root_path, 'extension_manager' => $extension_manager, diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index 5d9e00553f..a5a0865569 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -89,14 +89,13 @@ class phpbb_template_filter extends php_user_filter /** * Name of the style that the template being compiled and/or rendered - * belongs to. + * belongs to, and its parents, in inheritance tree order. * - * This is used by hooks implementation to invoke style-specific - * template hooks. + * Used to invoke style-specific template events. * - * @var string + * @var array */ - private $style_name; + private $style_names; /** * Extension manager. @@ -169,7 +168,7 @@ class phpbb_template_filter extends php_user_filter /** * Initializer, called on creation. * - * Get the allow_php option, style_name, root directory and locator from params, + * Get the allow_php option, style_names, root directory and locator from params, * which are passed to stream_filter_append. */ public function onCreate() @@ -179,7 +178,7 @@ class phpbb_template_filter extends php_user_filter $this->allow_php = $this->params['allow_php']; $this->locator = $this->params['locator']; $this->phpbb_root_path = $this->params['phpbb_root_path']; - $this->style_name = $this->params['style_name']; + $this->style_names = $this->params['style_names']; $this->extension_manager = $this->params['extension_manager']; if (isset($this->params['user'])) { diff --git a/phpBB/includes/template/template.php b/phpBB/includes/template/template.php index 52c08326d5..4d257d2510 100644 --- a/phpBB/includes/template/template.php +++ b/phpBB/includes/template/template.php @@ -83,14 +83,13 @@ class phpbb_template /** * Name of the style that the template being compiled and/or rendered - * belongs to. + * belongs to, and its parents, in inheritance tree order. * - * This is used by hooks implementation to invoke style-specific - * template hooks. + * Used to invoke style-specific template events. * - * @var string + * @var array */ - private $style_name; + public $style_names; /** * Constructor. @@ -302,7 +301,7 @@ class phpbb_template return new phpbb_template_renderer_include($output_file, $this); } - $compile = new phpbb_template_compile($this->config['tpl_allow_php'], $this->style_name, $this->locator, $this->phpbb_root_path, $this->extension_manager, $this->user); + $compile = new phpbb_template_compile($this->config['tpl_allow_php'], $this->style_names, $this->locator, $this->phpbb_root_path, $this->extension_manager, $this->user); if ($compile->compile_file_to_file($source_file, $output_file) !== false) { -- cgit v1.2.1 From 729eeef2bf4aad74c8170a54098e6679a1e9fadb Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Thu, 8 Nov 2012 12:22:15 -0500 Subject: [feature/template-events] Generate style names array in set_style. PHPBB3-9550 --- phpBB/includes/style/style.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/style/style.php b/phpBB/includes/style/style.php index 9ce96c5da5..6aec5c6ba4 100644 --- a/phpBB/includes/style/style.php +++ b/phpBB/includes/style/style.php @@ -91,16 +91,22 @@ class phpbb_style { $style_path = $this->user->style['style_path']; $style_dirs = ($this->user->style['style_parent_id']) ? array_reverse(explode('/', $this->user->style['style_parent_tree'])) : array(); - $paths = array($this->get_style_path($style_path)); + + $names = array($style_path); foreach ($style_dirs as $dir) { - $paths[] = $this->get_style_path($dir); + $names[] = $dir; } - // Add 'all' path, used as last fallback path by hooks and extensions - $paths[] = $this->get_style_path('all'); + //$names[] = 'all'; + + $paths = array(); + foreach ($names as $name) + { + $paths[] = $this->get_style_path($name); + } - return $this->set_custom_style($style_path, $paths); + return $this->set_custom_style($style_path, $names, $paths); } /** -- cgit v1.2.1 From af47779f51e44d34a78087327a3958fb35c50936 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Thu, 8 Nov 2012 12:22:39 -0500 Subject: [feature/template-events] Use style names array in template filter. This provides a straightforward way of iterating over all styles looking for templates in extensions. PHPBB3-9550 --- phpBB/includes/template/filter.php | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index a5a0865569..fd2ce9d859 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -929,11 +929,19 @@ class phpbb_template_filter extends php_user_filter ->extension_directory("/styles/all/template") ->get_files(); - $files = array_merge($files, $finder - ->extension_prefix($location) - ->extension_suffix('.html') - ->extension_directory("/styles/" . $this->style_name . "/template") - ->get_files()); + foreach ($this->style_names as $style_name) + { + $more_files = $finder + ->extension_prefix($location) + ->extension_suffix('.html') + ->extension_directory("/styles/" . $style_name . "/template") + ->get_files(); + if (!empty($more_files)) + { + $files = array_merge($files, $more_files); + break; + } + } $all_compiled = ''; foreach ($files as $file) -- cgit v1.2.1 From da7d888448cba2160b16010b982f886be9cb1bcb Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Thu, 8 Nov 2012 21:02:02 -0500 Subject: [feature/template-events] Make style names private on template. PHPBB3-9550 --- phpBB/includes/style/style.php | 2 +- phpBB/includes/template/template.php | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/style/style.php b/phpBB/includes/style/style.php index 6aec5c6ba4..7d31df9886 100644 --- a/phpBB/includes/style/style.php +++ b/phpBB/includes/style/style.php @@ -135,7 +135,7 @@ class phpbb_style $this->provider->set_styles($paths); $this->locator->set_paths($this->provider); - $this->template->style_names = $names; + $this->template->set_style_names($names); if ($template_path !== false) { diff --git a/phpBB/includes/template/template.php b/phpBB/includes/template/template.php index 4d257d2510..97e23f34e0 100644 --- a/phpBB/includes/template/template.php +++ b/phpBB/includes/template/template.php @@ -89,7 +89,7 @@ class phpbb_template * * @var array */ - public $style_names; + private $style_names; /** * Constructor. @@ -123,6 +123,18 @@ class phpbb_template return true; } + /** + * Sets the style names corresponding to style hierarchy being compiled + * and/or rendered. + * + * @param array $style_names List of style names in inheritance tree order + * @return null + */ + public function set_style_names(array $style_names) + { + $this->style_names = $style_names; + } + /** * Clears all variables and blocks assigned to this template. */ -- cgit v1.2.1 From 47a90f815d210d84f0c70ae678cb129e69963436 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Wed, 14 Nov 2012 17:31:05 -0500 Subject: [feature/template-events] Changes per imkingdavid's review. PHPBB3-9550 --- phpBB/includes/style/style.php | 2 +- phpBB/includes/template/compile.php | 2 +- phpBB/includes/template/filter.php | 15 +++++++++++++-- phpBB/includes/template/template.php | 2 +- 4 files changed, 16 insertions(+), 5 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/style/style.php b/phpBB/includes/style/style.php index 7d31df9886..7c91edd034 100644 --- a/phpBB/includes/style/style.php +++ b/phpBB/includes/style/style.php @@ -97,7 +97,7 @@ class phpbb_style { $names[] = $dir; } - // Add 'all' path, used as last fallback path by hooks and extensions + // Add 'all' path, used as last fallback path by events and extensions //$names[] = 'all'; $paths = array(); diff --git a/phpBB/includes/template/compile.php b/phpBB/includes/template/compile.php index 76ad2317c9..ba7f45e41d 100644 --- a/phpBB/includes/template/compile.php +++ b/phpBB/includes/template/compile.php @@ -39,7 +39,7 @@ class phpbb_template_compile * @param array $style_names Name of style to which the template being compiled belongs and parents in style tree order * @param phpbb_style_resource_locator $locator Resource locator * @param string $phpbb_root_path Path to phpBB root directory - * @param phpbb_extension_manager $extension_manager Extension manager to use for finding template fragments in extensions; if null, template hooks will not be invoked + * @param phpbb_extension_manager $extension_manager Extension manager to use for finding template fragments in extensions; if null, template events will not be invoked * @param phpbb_user $user Current user */ public function __construct($allow_php, $style_names, $locator, $phpbb_root_path, $extension_manager = null, $user = null) diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php index fd2ce9d859..f73ad28ba1 100644 --- a/phpBB/includes/template/filter.php +++ b/phpBB/includes/template/filter.php @@ -106,6 +106,7 @@ class phpbb_template_filter extends php_user_filter /** * Current user + * * @var phpbb_user */ private $user; @@ -170,6 +171,8 @@ class phpbb_template_filter extends php_user_filter * * Get the allow_php option, style_names, root directory and locator from params, * which are passed to stream_filter_append. + * + * @return boolean Returns true */ public function onCreate() { @@ -902,12 +905,13 @@ class phpbb_template_filter extends php_user_filter * such template fragments would have no effect. * * @param string $tag_args EVENT tag arguments, as a string - for EVENT this is the event name + * @return string compiled template code */ private function compile_tag_event($tag_args) { if (!preg_match('/^\w+$/', $tag_args)) { - // The hook location is improperly formatted, + // The event location is improperly formatted, if ($this->user) { trigger_error($this->user->lang('ERR_TEMPLATE_EVENT_LOCATION', $tag_args), E_USER_ERROR); @@ -950,7 +954,14 @@ class phpbb_template_filter extends php_user_filter if ($compiled === false) { - trigger_error(sprintf('The file could not be compiled: %s', phpbb_filter_root_path($file)), E_USER_ERROR); + if ($this->user) + { + trigger_error($this->user->lang('ERR_TEMPLATE_COMPILATION', phpbb_filter_root_path($file)), E_USER_ERROR); + } + else + { + trigger_error(sprintf('The file could not be compiled: %s', phpbb_filter_root_path($file)), E_USER_ERROR); + } } $all_compiled .= $compiled; diff --git a/phpBB/includes/template/template.php b/phpBB/includes/template/template.php index 97e23f34e0..bbec768613 100644 --- a/phpBB/includes/template/template.php +++ b/phpBB/includes/template/template.php @@ -98,7 +98,7 @@ class phpbb_template * @param user $user current user * @param phpbb_template_locator $locator template locator * @param phpbb_template_context $context template context - * @param phpbb_extension_manager $extension_manager extension manager, if null then template hooks will not be invoked + * @param phpbb_extension_manager $extension_manager extension manager, if null then template events will not be invoked */ public function __construct($phpbb_root_path, $php_ext, $config, $user, phpbb_template_locator $locator, phpbb_template_context $context, phpbb_extension_manager $extension_manager = null) { -- cgit v1.2.1 From d0338531cb2a3a386a3894a1b1b081025f53ea05 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sat, 1 Dec 2012 20:12:31 -0500 Subject: [ticket/11162] An implementation that actually works. PHPBB3-11162 --- phpBB/includes/functions.php | 73 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 5 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 8608c6ca4f..13690e79bb 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -1311,12 +1311,75 @@ function tz_select($default = '', $truncate = false) */ function phpbb_update_rows_avoiding_duplicates($db, $table, $column, $from_values, $to_value) { - $db->sql_return_on_error(true); - $condition = $db->sql_in_set($column, $from_values); - $db->sql_query('UPDATE ' . $table . ' SET ' . $column . ' = ' . (int) $to_value. ' WHERE ' . $condition); - $db->sql_return_on_error(false); + $sql = "SELECT $column, user_id + FROM $table + WHERE " . $db->sql_in_set($column, $from_values); + $result = $db->sql_query($sql); + + $old_user_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $old_user_ids[$row[$column]][] = $row['user_id']; + } + $db->sql_freeresult($result); + + $sql = "SELECT $column, user_id + FROM $table + WHERE $column = '" . (int) $to_value . "'"; + $result = $db->sql_query($sql); + + $new_user_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $new_user_ids[$row[$column]][] = $row['user_id']; + } + $db->sql_freeresult($result); - $db->sql_query('DELETE FROM ' . $table . ' WHERE ' . $condition); + $queries = array(); + $any_found = false; + foreach ($from_values as $from_value) + { + if (!isset($old_user_ids[$from_value])) + { + continue; + } + $any_found = true; + if (empty($new_user_ids)) + { + $sql = "UPDATE $table + SET $column = " . (int) $to_value. " + WHERE $column = '" . $db->sql_escape($from_value) . "'"; + $queries[] = $sql; + } + else + { + $different_user_ids = array_diff($old_user_ids[$from_value], $new_user_ids[$to_value]); + if (!empty($different_user_ids)) + { + $sql = "UPDATE $table + SET $column = " . (int) $to_value. " + WHERE $column = '" . $db->sql_escape($from_value) . "' + AND " . $db->sql_in_set('user_id', $different_user_ids); + $queries[] = $sql; + } + } + } + + if ($any_found) + { + //$db->sql_transaction('begin'); + + foreach ($queries as $sql) + { + $db->sql_query($sql); + } + + $sql = "DELETE FROM $table + WHERE " . $db->sql_in_set($column, $from_values); + $db->sql_query($sql); + + //$db->sql_transaction('commit'); + } } // Functions handling topic/post tracking/marking -- cgit v1.2.1 From 2e947334e563f122c597d7072eddd962453a84ef Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sat, 1 Dec 2012 20:17:01 -0500 Subject: [ticket/11162] Uncomment transactions. PHPBB3-11162 --- phpBB/includes/functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 13690e79bb..a24dcfdf5b 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -1367,7 +1367,7 @@ function phpbb_update_rows_avoiding_duplicates($db, $table, $column, $from_value if ($any_found) { - //$db->sql_transaction('begin'); + $db->sql_transaction('begin'); foreach ($queries as $sql) { @@ -1378,7 +1378,7 @@ function phpbb_update_rows_avoiding_duplicates($db, $table, $column, $from_value WHERE " . $db->sql_in_set($column, $from_values); $db->sql_query($sql); - //$db->sql_transaction('commit'); + $db->sql_transaction('commit'); } } -- cgit v1.2.1 From c2c105df9ff1d8fd48afa1d3abdf23a39584d7ed Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sat, 1 Dec 2012 20:21:29 -0500 Subject: [ticket/11162] Clarify that only the two tables actually work. PHPBB3-11162 --- phpBB/includes/functions.php | 2 ++ 1 file changed, 2 insertions(+) (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index a24dcfdf5b..09487ce10d 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -1302,6 +1302,8 @@ function tz_select($default = '', $truncate = false) * If this results in rows violating uniqueness constraints, the duplicate * rows are eliminated. * +* The only supported tables are bookmarks and topics_watch. +* * @param dbal $db Database object * @param string $table Table on which to perform the update * @param string $column Column whose values to change -- cgit v1.2.1 From 69225bd0a6005e81b7cb58631eb5de7c3d175697 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sat, 1 Dec 2012 20:21:51 -0500 Subject: [ticket/11162] Use phpbb_update_rows_avoiding_duplicates in mcp. PHPBB3-11162 --- phpBB/includes/mcp/mcp_forum.php | 7 +------ phpBB/includes/mcp/mcp_topic.php | 7 +------ 2 files changed, 2 insertions(+), 12 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/mcp/mcp_forum.php b/phpBB/includes/mcp/mcp_forum.php index b70601b479..913c55a9d1 100644 --- a/phpBB/includes/mcp/mcp_forum.php +++ b/phpBB/includes/mcp/mcp_forum.php @@ -415,12 +415,7 @@ function merge_topics($forum_id, $topic_ids, $to_topic_id) $success_msg = 'POSTS_MERGED_SUCCESS'; // If the topic no longer exist, we will update the topic watch table. - // To not let it error out on users watching both topics, we just return on an error... - $db->sql_return_on_error(true); - $db->sql_query('UPDATE ' . TOPICS_WATCH_TABLE . ' SET topic_id = ' . (int) $to_topic_id . ' WHERE ' . $db->sql_in_set('topic_id', $topic_ids)); - $db->sql_return_on_error(false); - - $db->sql_query('DELETE FROM ' . TOPICS_WATCH_TABLE . ' WHERE ' . $db->sql_in_set('topic_id', $topic_ids)); + phpbb_update_rows_avoiding_duplicates($db, TOPICS_WATCH_TABLE, 'topic_id', $topic_ids, $to_topic_id); // Link to the new topic $return_link .= (($return_link) ? '

' : '') . sprintf($user->lang['RETURN_NEW_TOPIC'], '', ''); diff --git a/phpBB/includes/mcp/mcp_topic.php b/phpBB/includes/mcp/mcp_topic.php index 7d4edaf362..f40a4bc044 100644 --- a/phpBB/includes/mcp/mcp_topic.php +++ b/phpBB/includes/mcp/mcp_topic.php @@ -620,12 +620,7 @@ function merge_posts($topic_id, $to_topic_id) else { // If the topic no longer exist, we will update the topic watch table. - // To not let it error out on users watching both topics, we just return on an error... - $db->sql_return_on_error(true); - $db->sql_query('UPDATE ' . TOPICS_WATCH_TABLE . ' SET topic_id = ' . (int) $to_topic_id . ' WHERE topic_id = ' . (int) $topic_id); - $db->sql_return_on_error(false); - - $db->sql_query('DELETE FROM ' . TOPICS_WATCH_TABLE . ' WHERE topic_id = ' . (int) $topic_id); + phpbb_update_rows_avoiding_duplicates($db, TOPICS_WATCH_TABLE, 'topic_id', $topic_ids, $to_topic_id); } // Link to the new topic -- cgit v1.2.1 From 05053dacd350c6bacd529df803bdc9876104a278 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sat, 1 Dec 2012 20:24:25 -0500 Subject: [ticket/11162] Fix inaccurately copy pasted comment. PHPBB3-11162 --- phpBB/includes/mcp/mcp_forum.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/mcp/mcp_forum.php b/phpBB/includes/mcp/mcp_forum.php index 913c55a9d1..e392bef157 100644 --- a/phpBB/includes/mcp/mcp_forum.php +++ b/phpBB/includes/mcp/mcp_forum.php @@ -414,7 +414,7 @@ function merge_topics($forum_id, $topic_ids, $to_topic_id) // Message and return links $success_msg = 'POSTS_MERGED_SUCCESS'; - // If the topic no longer exist, we will update the topic watch table. + // Update the topic watch table. phpbb_update_rows_avoiding_duplicates($db, TOPICS_WATCH_TABLE, 'topic_id', $topic_ids, $to_topic_id); // Link to the new topic -- cgit v1.2.1 From 2fc43e6ed71e4b9abdb76aa179b4e64efaa47ffa Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Sat, 1 Dec 2012 23:11:14 -0500 Subject: [ticket/11162] No whitespace changes in olympus. PHPBB3-11162 --- phpBB/includes/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 09487ce10d..e3feb59d41 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -3442,7 +3442,7 @@ function parse_cfg_file($filename, $lines = false) $parsed_items[$key] = $value; } - + if (isset($parsed_items['inherit_from']) && isset($parsed_items['name']) && $parsed_items['inherit_from'] == $parsed_items['name']) { unset($parsed_items['inherit_from']); -- cgit v1.2.1 From e765ccd075a19be7ec9c60970677d62dc75f0845 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 4 Dec 2012 04:22:10 -0500 Subject: [ticket/11015] Include dbms name in exception message. PHPBB3-11015 --- phpBB/includes/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 57136a43ff..501257956a 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -5431,5 +5431,5 @@ function phpbb_convert_30_dbms_to_31($dbms) return 'phpbb_db_driver_' . $dbms; } - throw new \RuntimeException('You have specified an invalid dbms driver.'); + throw new \RuntimeException("You have specified an invalid dbms driver: $dbms"); } -- cgit v1.2.1 From 3687febdacc9b1629fd6f09e15305d61eacc9b78 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 4 Dec 2012 04:29:31 -0500 Subject: [ticket/11015] Change more docblocks. PHPBB3-11015 --- phpBB/includes/db/db_tools.php | 2 +- phpBB/includes/extension/manager.php | 2 +- phpBB/includes/extension/metadata_manager.php | 2 +- phpBB/includes/functions_download.php | 8 ++++---- phpBB/includes/lock/db.php | 4 ++-- phpBB/includes/search/fulltext_mysql.php | 4 ++-- phpBB/includes/search/fulltext_native.php | 4 ++-- phpBB/includes/search/fulltext_postgres.php | 4 ++-- phpBB/includes/search/fulltext_sphinx.php | 4 ++-- 9 files changed, 17 insertions(+), 17 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/db/db_tools.php b/phpBB/includes/db/db_tools.php index 6df3aac9ce..2bb016cebd 100644 --- a/phpBB/includes/db/db_tools.php +++ b/phpBB/includes/db/db_tools.php @@ -300,7 +300,7 @@ class phpbb_db_tools /** * Constructor. Set DB Object and set {@link $return_statements return_statements}. * - * @param phpbb_dbal $db DBAL object + * @param phpbb_db_driver $db Database connection * @param bool $return_statements True if only statements should be returned and no SQL being executed */ function phpbb_db_tools(&$db, $return_statements = false) diff --git a/phpBB/includes/extension/manager.php b/phpBB/includes/extension/manager.php index 862bca1855..67cc81e407 100644 --- a/phpBB/includes/extension/manager.php +++ b/phpBB/includes/extension/manager.php @@ -34,7 +34,7 @@ class phpbb_extension_manager /** * Creates a manager and loads information from database * - * @param dbal $db A database connection + * @param phpbb_db_driver $db A database connection * @param phpbb_config $config phpbb_config * @param string $extension_table The name of the table holding extensions * @param string $phpbb_root_path Path to the phpbb includes directory. diff --git a/phpBB/includes/extension/metadata_manager.php b/phpBB/includes/extension/metadata_manager.php index 813459eb67..36b0f8b184 100644 --- a/phpBB/includes/extension/metadata_manager.php +++ b/phpBB/includes/extension/metadata_manager.php @@ -34,7 +34,7 @@ class phpbb_extension_metadata_manager /** * Creates the metadata manager * - * @param dbal $db A database connection + * @param phpbb_db_driver $db A database connection * @param string $extension_manager An instance of the phpbb extension manager * @param string $phpbb_root_path Path to the phpbb includes directory. * @param string $phpEx php file extension diff --git a/phpBB/includes/functions_download.php b/phpBB/includes/functions_download.php index b6371dbecc..89bcf7c8d9 100644 --- a/phpBB/includes/functions_download.php +++ b/phpBB/includes/functions_download.php @@ -596,7 +596,7 @@ function phpbb_parse_range_request($request_array, $filesize) /** * Increments the download count of all provided attachments * -* @param dbal $db The database object +* @param phpbb_db_driver $db The database object * @param array|int $ids The attach_id of each attachment * * @return null @@ -617,7 +617,7 @@ function phpbb_increment_downloads($db, $ids) /** * Handles authentication when downloading attachments from a post or topic * -* @param dbal $db The database object +* @param phpbb_db_driver $db The database object * @param phpbb_auth $auth The authentication object * @param int $topic_id The id of the topic that we are downloading from * @@ -651,7 +651,7 @@ function phpbb_download_handle_forum_auth($db, $auth, $topic_id) /** * Handles authentication when downloading attachments from PMs * -* @param dbal $db The database object +* @param phpbb_db_driver $db The database object * @param phpbb_auth $auth The authentication object * @param int $user_id The user id * @param int $msg_id The id of the PM that we are downloading from @@ -678,7 +678,7 @@ function phpbb_download_handle_pm_auth($db, $auth, $user_id, $msg_id) /** * Checks whether a user can download from a particular PM * -* @param dbal $db The database object +* @param phpbb_db_driver $db The database object * @param int $user_id The user id * @param int $msg_id The id of the PM that we are downloading from * diff --git a/phpBB/includes/lock/db.php b/phpBB/includes/lock/db.php index 40690144b1..2bb2e25f97 100644 --- a/phpBB/includes/lock/db.php +++ b/phpBB/includes/lock/db.php @@ -48,7 +48,7 @@ class phpbb_lock_db /** * A database connection - * @var dbal + * @var phpbb_db_driver */ private $db; @@ -59,7 +59,7 @@ class phpbb_lock_db * * @param string $config_name A config variable to be used for locking * @param array $config The phpBB configuration - * @param dbal $db A database connection + * @param phpbb_db_driver $db A database connection */ public function __construct($config_name, phpbb_config $config, phpbb_db_driver $db) { diff --git a/phpBB/includes/search/fulltext_mysql.php b/phpBB/includes/search/fulltext_mysql.php index 58a4dd7d6a..693f663f23 100644 --- a/phpBB/includes/search/fulltext_mysql.php +++ b/phpBB/includes/search/fulltext_mysql.php @@ -41,8 +41,8 @@ class phpbb_search_fulltext_mysql extends phpbb_search_base protected $config; /** - * DBAL object - * @var dbal + * Database connection + * @var phpbb_db_driver */ protected $db; diff --git a/phpBB/includes/search/fulltext_native.php b/phpBB/includes/search/fulltext_native.php index 4623326fc7..53df8348ae 100644 --- a/phpBB/includes/search/fulltext_native.php +++ b/phpBB/includes/search/fulltext_native.php @@ -85,8 +85,8 @@ class phpbb_search_fulltext_native extends phpbb_search_base protected $config; /** - * DBAL object - * @var dbal + * Database connection + * @var phpbb_db_driver */ protected $db; diff --git a/phpBB/includes/search/fulltext_postgres.php b/phpBB/includes/search/fulltext_postgres.php index 08f64735b6..cf613076d0 100644 --- a/phpBB/includes/search/fulltext_postgres.php +++ b/phpBB/includes/search/fulltext_postgres.php @@ -66,8 +66,8 @@ class phpbb_search_fulltext_postgres extends phpbb_search_base protected $config; /** - * DBAL object - * @var dbal + * Database connection + * @var phpbb_db_driver */ protected $db; diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php index dd5634b623..a84a64825c 100644 --- a/phpBB/includes/search/fulltext_sphinx.php +++ b/phpBB/includes/search/fulltext_sphinx.php @@ -84,8 +84,8 @@ class phpbb_search_fulltext_sphinx protected $config; /** - * DBAL object - * @var dbal + * Database connection + * @var phpbb_db_driver */ protected $db; -- cgit v1.2.1 From 0f96b1aad378b1fc40620ea57df5ee89224fd5eb Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 4 Dec 2012 23:35:34 -0500 Subject: [ticket/11162] Move to a separate file to avoid blowing out functions.php. PHPBB3-11162 --- phpBB/includes/functions.php | 87 ------------------------ phpBB/includes/functions_tricky_update.php | 103 +++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 87 deletions(-) create mode 100644 phpBB/includes/functions_tricky_update.php (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index e3feb59d41..65d8be32ad 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -1297,93 +1297,6 @@ function tz_select($default = '', $truncate = false) return $tz_select; } -/** -* Updates rows in given table from a set of values to a new value. -* If this results in rows violating uniqueness constraints, the duplicate -* rows are eliminated. -* -* The only supported tables are bookmarks and topics_watch. -* -* @param dbal $db Database object -* @param string $table Table on which to perform the update -* @param string $column Column whose values to change -* @param array $from_values An array of values that should be changed -* @param int $to_value The new value -* @return null -*/ -function phpbb_update_rows_avoiding_duplicates($db, $table, $column, $from_values, $to_value) -{ - $sql = "SELECT $column, user_id - FROM $table - WHERE " . $db->sql_in_set($column, $from_values); - $result = $db->sql_query($sql); - - $old_user_ids = array(); - while ($row = $db->sql_fetchrow($result)) - { - $old_user_ids[$row[$column]][] = $row['user_id']; - } - $db->sql_freeresult($result); - - $sql = "SELECT $column, user_id - FROM $table - WHERE $column = '" . (int) $to_value . "'"; - $result = $db->sql_query($sql); - - $new_user_ids = array(); - while ($row = $db->sql_fetchrow($result)) - { - $new_user_ids[$row[$column]][] = $row['user_id']; - } - $db->sql_freeresult($result); - - $queries = array(); - $any_found = false; - foreach ($from_values as $from_value) - { - if (!isset($old_user_ids[$from_value])) - { - continue; - } - $any_found = true; - if (empty($new_user_ids)) - { - $sql = "UPDATE $table - SET $column = " . (int) $to_value. " - WHERE $column = '" . $db->sql_escape($from_value) . "'"; - $queries[] = $sql; - } - else - { - $different_user_ids = array_diff($old_user_ids[$from_value], $new_user_ids[$to_value]); - if (!empty($different_user_ids)) - { - $sql = "UPDATE $table - SET $column = " . (int) $to_value. " - WHERE $column = '" . $db->sql_escape($from_value) . "' - AND " . $db->sql_in_set('user_id', $different_user_ids); - $queries[] = $sql; - } - } - } - - if ($any_found) - { - $db->sql_transaction('begin'); - - foreach ($queries as $sql) - { - $db->sql_query($sql); - } - - $sql = "DELETE FROM $table - WHERE " . $db->sql_in_set($column, $from_values); - $db->sql_query($sql); - - $db->sql_transaction('commit'); - } -} - // Functions handling topic/post tracking/marking /** diff --git a/phpBB/includes/functions_tricky_update.php b/phpBB/includes/functions_tricky_update.php new file mode 100644 index 0000000000..a5b9fdacd0 --- /dev/null +++ b/phpBB/includes/functions_tricky_update.php @@ -0,0 +1,103 @@ +sql_in_set($column, $from_values); + $result = $db->sql_query($sql); + + $old_user_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $old_user_ids[$row[$column]][] = $row['user_id']; + } + $db->sql_freeresult($result); + + $sql = "SELECT $column, user_id + FROM $table + WHERE $column = '" . (int) $to_value . "'"; + $result = $db->sql_query($sql); + + $new_user_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $new_user_ids[$row[$column]][] = $row['user_id']; + } + $db->sql_freeresult($result); + + $queries = array(); + $any_found = false; + foreach ($from_values as $from_value) + { + if (!isset($old_user_ids[$from_value])) + { + continue; + } + $any_found = true; + if (empty($new_user_ids)) + { + $sql = "UPDATE $table + SET $column = " . (int) $to_value. " + WHERE $column = '" . $db->sql_escape($from_value) . "'"; + $queries[] = $sql; + } + else + { + $different_user_ids = array_diff($old_user_ids[$from_value], $new_user_ids[$to_value]); + if (!empty($different_user_ids)) + { + $sql = "UPDATE $table + SET $column = " . (int) $to_value. " + WHERE $column = '" . $db->sql_escape($from_value) . "' + AND " . $db->sql_in_set('user_id', $different_user_ids); + $queries[] = $sql; + } + } + } + + if ($any_found) + { + $db->sql_transaction('begin'); + + foreach ($queries as $sql) + { + $db->sql_query($sql); + } + + $sql = "DELETE FROM $table + WHERE " . $db->sql_in_set($column, $from_values); + $db->sql_query($sql); + + $db->sql_transaction('commit'); + } +} -- cgit v1.2.1 From abca64b1dfbe11f73675a3e5457fb9f0038f1cb6 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Tue, 4 Dec 2012 23:37:14 -0500 Subject: [ticket/11162] Add includes. PHPBB3-11162 --- phpBB/includes/mcp/mcp_forum.php | 4 ++++ phpBB/includes/mcp/mcp_topic.php | 4 ++++ 2 files changed, 8 insertions(+) (limited to 'phpBB/includes') diff --git a/phpBB/includes/mcp/mcp_forum.php b/phpBB/includes/mcp/mcp_forum.php index e392bef157..3603224735 100644 --- a/phpBB/includes/mcp/mcp_forum.php +++ b/phpBB/includes/mcp/mcp_forum.php @@ -415,6 +415,10 @@ function merge_topics($forum_id, $topic_ids, $to_topic_id) $success_msg = 'POSTS_MERGED_SUCCESS'; // Update the topic watch table. + if (!function_exists('phpbb_update_rows_avoiding_duplicates')) + { + include($phpbb_root_path . 'includes/functions_tricky_update.' . $phpEx); + } phpbb_update_rows_avoiding_duplicates($db, TOPICS_WATCH_TABLE, 'topic_id', $topic_ids, $to_topic_id); // Link to the new topic diff --git a/phpBB/includes/mcp/mcp_topic.php b/phpBB/includes/mcp/mcp_topic.php index f40a4bc044..1fb3cb9d73 100644 --- a/phpBB/includes/mcp/mcp_topic.php +++ b/phpBB/includes/mcp/mcp_topic.php @@ -620,6 +620,10 @@ function merge_posts($topic_id, $to_topic_id) else { // If the topic no longer exist, we will update the topic watch table. + if (!function_exists('phpbb_update_rows_avoiding_duplicates')) + { + include($phpbb_root_path . 'includes/functions_tricky_update.' . $phpEx); + } phpbb_update_rows_avoiding_duplicates($db, TOPICS_WATCH_TABLE, 'topic_id', $topic_ids, $to_topic_id); } -- cgit v1.2.1 From 6872104aa95a9f2aad565189e25bbf54abc3ca2c Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Wed, 5 Dec 2012 00:07:01 -0500 Subject: [ticket/11162] Account for notify_status. PHPBB3-11162 --- phpBB/includes/functions_tricky_update.php | 111 ++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 3 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions_tricky_update.php b/phpBB/includes/functions_tricky_update.php index a5b9fdacd0..cf922d7014 100644 --- a/phpBB/includes/functions_tricky_update.php +++ b/phpBB/includes/functions_tricky_update.php @@ -20,7 +20,7 @@ if (!defined('IN_PHPBB')) * If this results in rows violating uniqueness constraints, the duplicate * rows are eliminated. * -* The only supported tables are bookmarks and topics_watch. +* The only supported table is bookmarks. * * @param dbal $db Database object * @param string $table Table on which to perform the update @@ -67,7 +67,7 @@ function phpbb_update_rows_avoiding_duplicates($db, $table, $column, $from_value if (empty($new_user_ids)) { $sql = "UPDATE $table - SET $column = " . (int) $to_value. " + SET $column = " . (int) $to_value . " WHERE $column = '" . $db->sql_escape($from_value) . "'"; $queries[] = $sql; } @@ -77,7 +77,7 @@ function phpbb_update_rows_avoiding_duplicates($db, $table, $column, $from_value if (!empty($different_user_ids)) { $sql = "UPDATE $table - SET $column = " . (int) $to_value. " + SET $column = " . (int) $to_value . " WHERE $column = '" . $db->sql_escape($from_value) . "' AND " . $db->sql_in_set('user_id', $different_user_ids); $queries[] = $sql; @@ -101,3 +101,108 @@ function phpbb_update_rows_avoiding_duplicates($db, $table, $column, $from_value $db->sql_transaction('commit'); } } + +/** +* Updates rows in given table from a set of values to a new value. +* If this results in rows violating uniqueness constraints, the duplicate +* rows are merged respecting notify_status (0 takes precedence over 1). +* +* The only supported table is topics_watch. +* +* @param dbal $db Database object +* @param string $table Table on which to perform the update +* @param string $column Column whose values to change +* @param array $from_values An array of values that should be changed +* @param int $to_value The new value +* @return null +*/ +function phpbb_update_rows_avoiding_duplicates_notify_status($db, $table, $column, $from_values, $to_value) +{ + $sql = "SELECT $column, user_id, notify_status + FROM $table + WHERE " . $db->sql_in_set($column, $from_values); + $result = $db->sql_query($sql); + + $old_user_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $old_user_ids[(int) $row['notify_status']][$row[$column]][] = $row['user_id']; + } + $db->sql_freeresult($result); + + $sql = "SELECT $column, user_id + FROM $table + WHERE $column = '" . (int) $to_value . "'"; + $result = $db->sql_query($sql); + + $new_user_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $new_user_ids[$row[$column]][] = $row['user_id']; + } + $db->sql_freeresult($result); + + $queries = array(); + $any_found = false; + $extra_updates = array( + 0 => 'notify_status = 0', + 1 => '', + ); + foreach ($from_values as $from_value) + { + foreach ($extra_updates as $notify_status => $extra_update) { + if (!isset($old_user_ids[$notify_status][$from_value])) + { + continue; + } + $any_found = true; + if (empty($new_user_ids)) + { + $sql = "UPDATE $table + SET $column = " . (int) $to_value . " + WHERE $column = '" . $db->sql_escape($from_value) . "'"; + $queries[] = $sql; + } + else + { + $different_user_ids = array_diff($old_user_ids[$notify_status][$from_value], $new_user_ids[$to_value]); + if (!empty($different_user_ids)) + { + $sql = "UPDATE $table + SET $column = " . (int) $to_value . " + WHERE $column = '" . $db->sql_escape($from_value) . "' + AND " . $db->sql_in_set('user_id', $different_user_ids); + $queries[] = $sql; + } + + if ($extra_update) { + $same_user_ids = array_diff($old_user_ids[$notify_status][$from_value], $different_user_ids); + if (!empty($same_user_ids)) + { + $sql = "UPDATE $table + SET $extra_update + WHERE $column = '" . (int) $to_value . "' + AND " . $db->sql_in_set('user_id', $same_user_ids); + $queries[] = $sql; + } + } + } + } + } + + if ($any_found) + { + $db->sql_transaction('begin'); + + foreach ($queries as $sql) + { + $db->sql_query($sql); + } + + $sql = "DELETE FROM $table + WHERE " . $db->sql_in_set($column, $from_values); + $db->sql_query($sql); + + $db->sql_transaction('commit'); + } +} -- cgit v1.2.1 From 1a411c5658da769e28b03332ed618d5e701dab79 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Wed, 5 Dec 2012 00:10:02 -0500 Subject: [ticket/11162] Use correct functions. PHPBB3-11162 --- phpBB/includes/mcp/mcp_forum.php | 4 ++-- phpBB/includes/mcp/mcp_topic.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/mcp/mcp_forum.php b/phpBB/includes/mcp/mcp_forum.php index 3603224735..cd938e83a0 100644 --- a/phpBB/includes/mcp/mcp_forum.php +++ b/phpBB/includes/mcp/mcp_forum.php @@ -415,11 +415,11 @@ function merge_topics($forum_id, $topic_ids, $to_topic_id) $success_msg = 'POSTS_MERGED_SUCCESS'; // Update the topic watch table. - if (!function_exists('phpbb_update_rows_avoiding_duplicates')) + if (!function_exists('phpbb_update_rows_avoiding_duplicates_notify_status')) { include($phpbb_root_path . 'includes/functions_tricky_update.' . $phpEx); } - phpbb_update_rows_avoiding_duplicates($db, TOPICS_WATCH_TABLE, 'topic_id', $topic_ids, $to_topic_id); + phpbb_update_rows_avoiding_duplicates_notify_status($db, TOPICS_WATCH_TABLE, 'topic_id', $topic_ids, $to_topic_id); // Link to the new topic $return_link .= (($return_link) ? '

' : '') . sprintf($user->lang['RETURN_NEW_TOPIC'], '', ''); diff --git a/phpBB/includes/mcp/mcp_topic.php b/phpBB/includes/mcp/mcp_topic.php index 1fb3cb9d73..29dabdd2a2 100644 --- a/phpBB/includes/mcp/mcp_topic.php +++ b/phpBB/includes/mcp/mcp_topic.php @@ -620,11 +620,11 @@ function merge_posts($topic_id, $to_topic_id) else { // If the topic no longer exist, we will update the topic watch table. - if (!function_exists('phpbb_update_rows_avoiding_duplicates')) + if (!function_exists('phpbb_update_rows_avoiding_duplicates_notify_status')) { include($phpbb_root_path . 'includes/functions_tricky_update.' . $phpEx); } - phpbb_update_rows_avoiding_duplicates($db, TOPICS_WATCH_TABLE, 'topic_id', $topic_ids, $to_topic_id); + phpbb_update_rows_avoiding_duplicates_notify_status($db, TOPICS_WATCH_TABLE, 'topic_id', $topic_ids, $to_topic_id); } // Link to the new topic -- cgit v1.2.1 From 94ebc5707836d931e24e77f5ce1b3a16d0410fb8 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Wed, 5 Dec 2012 10:40:42 -0500 Subject: [ticket/11162] Newlines to LF. PHPBB3-11162 --- phpBB/includes/functions_tricky_update.php | 416 ++++++++++++++--------------- 1 file changed, 208 insertions(+), 208 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions_tricky_update.php b/phpBB/includes/functions_tricky_update.php index cf922d7014..c8fcb9b6f0 100644 --- a/phpBB/includes/functions_tricky_update.php +++ b/phpBB/includes/functions_tricky_update.php @@ -1,208 +1,208 @@ -sql_in_set($column, $from_values); - $result = $db->sql_query($sql); - - $old_user_ids = array(); - while ($row = $db->sql_fetchrow($result)) - { - $old_user_ids[$row[$column]][] = $row['user_id']; - } - $db->sql_freeresult($result); - - $sql = "SELECT $column, user_id - FROM $table - WHERE $column = '" . (int) $to_value . "'"; - $result = $db->sql_query($sql); - - $new_user_ids = array(); - while ($row = $db->sql_fetchrow($result)) - { - $new_user_ids[$row[$column]][] = $row['user_id']; - } - $db->sql_freeresult($result); - - $queries = array(); - $any_found = false; - foreach ($from_values as $from_value) - { - if (!isset($old_user_ids[$from_value])) - { - continue; - } - $any_found = true; - if (empty($new_user_ids)) - { - $sql = "UPDATE $table - SET $column = " . (int) $to_value . " - WHERE $column = '" . $db->sql_escape($from_value) . "'"; - $queries[] = $sql; - } - else - { - $different_user_ids = array_diff($old_user_ids[$from_value], $new_user_ids[$to_value]); - if (!empty($different_user_ids)) - { - $sql = "UPDATE $table - SET $column = " . (int) $to_value . " - WHERE $column = '" . $db->sql_escape($from_value) . "' - AND " . $db->sql_in_set('user_id', $different_user_ids); - $queries[] = $sql; - } - } - } - - if ($any_found) - { - $db->sql_transaction('begin'); - - foreach ($queries as $sql) - { - $db->sql_query($sql); - } - - $sql = "DELETE FROM $table - WHERE " . $db->sql_in_set($column, $from_values); - $db->sql_query($sql); - - $db->sql_transaction('commit'); - } -} - -/** -* Updates rows in given table from a set of values to a new value. -* If this results in rows violating uniqueness constraints, the duplicate -* rows are merged respecting notify_status (0 takes precedence over 1). -* -* The only supported table is topics_watch. -* -* @param dbal $db Database object -* @param string $table Table on which to perform the update -* @param string $column Column whose values to change -* @param array $from_values An array of values that should be changed -* @param int $to_value The new value -* @return null -*/ -function phpbb_update_rows_avoiding_duplicates_notify_status($db, $table, $column, $from_values, $to_value) -{ - $sql = "SELECT $column, user_id, notify_status - FROM $table - WHERE " . $db->sql_in_set($column, $from_values); - $result = $db->sql_query($sql); - - $old_user_ids = array(); - while ($row = $db->sql_fetchrow($result)) - { - $old_user_ids[(int) $row['notify_status']][$row[$column]][] = $row['user_id']; - } - $db->sql_freeresult($result); - - $sql = "SELECT $column, user_id - FROM $table - WHERE $column = '" . (int) $to_value . "'"; - $result = $db->sql_query($sql); - - $new_user_ids = array(); - while ($row = $db->sql_fetchrow($result)) - { - $new_user_ids[$row[$column]][] = $row['user_id']; - } - $db->sql_freeresult($result); - - $queries = array(); - $any_found = false; - $extra_updates = array( - 0 => 'notify_status = 0', - 1 => '', - ); - foreach ($from_values as $from_value) - { - foreach ($extra_updates as $notify_status => $extra_update) { - if (!isset($old_user_ids[$notify_status][$from_value])) - { - continue; - } - $any_found = true; - if (empty($new_user_ids)) - { - $sql = "UPDATE $table - SET $column = " . (int) $to_value . " - WHERE $column = '" . $db->sql_escape($from_value) . "'"; - $queries[] = $sql; - } - else - { - $different_user_ids = array_diff($old_user_ids[$notify_status][$from_value], $new_user_ids[$to_value]); - if (!empty($different_user_ids)) - { - $sql = "UPDATE $table - SET $column = " . (int) $to_value . " - WHERE $column = '" . $db->sql_escape($from_value) . "' - AND " . $db->sql_in_set('user_id', $different_user_ids); - $queries[] = $sql; - } - - if ($extra_update) { - $same_user_ids = array_diff($old_user_ids[$notify_status][$from_value], $different_user_ids); - if (!empty($same_user_ids)) - { - $sql = "UPDATE $table - SET $extra_update - WHERE $column = '" . (int) $to_value . "' - AND " . $db->sql_in_set('user_id', $same_user_ids); - $queries[] = $sql; - } - } - } - } - } - - if ($any_found) - { - $db->sql_transaction('begin'); - - foreach ($queries as $sql) - { - $db->sql_query($sql); - } - - $sql = "DELETE FROM $table - WHERE " . $db->sql_in_set($column, $from_values); - $db->sql_query($sql); - - $db->sql_transaction('commit'); - } -} +sql_in_set($column, $from_values); + $result = $db->sql_query($sql); + + $old_user_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $old_user_ids[$row[$column]][] = $row['user_id']; + } + $db->sql_freeresult($result); + + $sql = "SELECT $column, user_id + FROM $table + WHERE $column = '" . (int) $to_value . "'"; + $result = $db->sql_query($sql); + + $new_user_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $new_user_ids[$row[$column]][] = $row['user_id']; + } + $db->sql_freeresult($result); + + $queries = array(); + $any_found = false; + foreach ($from_values as $from_value) + { + if (!isset($old_user_ids[$from_value])) + { + continue; + } + $any_found = true; + if (empty($new_user_ids)) + { + $sql = "UPDATE $table + SET $column = " . (int) $to_value . " + WHERE $column = '" . $db->sql_escape($from_value) . "'"; + $queries[] = $sql; + } + else + { + $different_user_ids = array_diff($old_user_ids[$from_value], $new_user_ids[$to_value]); + if (!empty($different_user_ids)) + { + $sql = "UPDATE $table + SET $column = " . (int) $to_value . " + WHERE $column = '" . $db->sql_escape($from_value) . "' + AND " . $db->sql_in_set('user_id', $different_user_ids); + $queries[] = $sql; + } + } + } + + if ($any_found) + { + $db->sql_transaction('begin'); + + foreach ($queries as $sql) + { + $db->sql_query($sql); + } + + $sql = "DELETE FROM $table + WHERE " . $db->sql_in_set($column, $from_values); + $db->sql_query($sql); + + $db->sql_transaction('commit'); + } +} + +/** +* Updates rows in given table from a set of values to a new value. +* If this results in rows violating uniqueness constraints, the duplicate +* rows are merged respecting notify_status (0 takes precedence over 1). +* +* The only supported table is topics_watch. +* +* @param dbal $db Database object +* @param string $table Table on which to perform the update +* @param string $column Column whose values to change +* @param array $from_values An array of values that should be changed +* @param int $to_value The new value +* @return null +*/ +function phpbb_update_rows_avoiding_duplicates_notify_status($db, $table, $column, $from_values, $to_value) +{ + $sql = "SELECT $column, user_id, notify_status + FROM $table + WHERE " . $db->sql_in_set($column, $from_values); + $result = $db->sql_query($sql); + + $old_user_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $old_user_ids[(int) $row['notify_status']][$row[$column]][] = $row['user_id']; + } + $db->sql_freeresult($result); + + $sql = "SELECT $column, user_id + FROM $table + WHERE $column = '" . (int) $to_value . "'"; + $result = $db->sql_query($sql); + + $new_user_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $new_user_ids[$row[$column]][] = $row['user_id']; + } + $db->sql_freeresult($result); + + $queries = array(); + $any_found = false; + $extra_updates = array( + 0 => 'notify_status = 0', + 1 => '', + ); + foreach ($from_values as $from_value) + { + foreach ($extra_updates as $notify_status => $extra_update) { + if (!isset($old_user_ids[$notify_status][$from_value])) + { + continue; + } + $any_found = true; + if (empty($new_user_ids)) + { + $sql = "UPDATE $table + SET $column = " . (int) $to_value . " + WHERE $column = '" . $db->sql_escape($from_value) . "'"; + $queries[] = $sql; + } + else + { + $different_user_ids = array_diff($old_user_ids[$notify_status][$from_value], $new_user_ids[$to_value]); + if (!empty($different_user_ids)) + { + $sql = "UPDATE $table + SET $column = " . (int) $to_value . " + WHERE $column = '" . $db->sql_escape($from_value) . "' + AND " . $db->sql_in_set('user_id', $different_user_ids); + $queries[] = $sql; + } + + if ($extra_update) { + $same_user_ids = array_diff($old_user_ids[$notify_status][$from_value], $different_user_ids); + if (!empty($same_user_ids)) + { + $sql = "UPDATE $table + SET $extra_update + WHERE $column = '" . (int) $to_value . "' + AND " . $db->sql_in_set('user_id', $same_user_ids); + $queries[] = $sql; + } + } + } + } + } + + if ($any_found) + { + $db->sql_transaction('begin'); + + foreach ($queries as $sql) + { + $db->sql_query($sql); + } + + $sql = "DELETE FROM $table + WHERE " . $db->sql_in_set($column, $from_values); + $db->sql_query($sql); + + $db->sql_transaction('commit'); + } +} -- cgit v1.2.1 From 16966f52d37e4ebdca4f3846c81dfa85b193501e Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Wed, 5 Dec 2012 10:41:08 -0500 Subject: [ticket/11162] Reformat. PHPBB3-11162 --- phpBB/includes/functions_tricky_update.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions_tricky_update.php b/phpBB/includes/functions_tricky_update.php index c8fcb9b6f0..10321618b2 100644 --- a/phpBB/includes/functions_tricky_update.php +++ b/phpBB/includes/functions_tricky_update.php @@ -150,7 +150,8 @@ function phpbb_update_rows_avoiding_duplicates_notify_status($db, $table, $colum ); foreach ($from_values as $from_value) { - foreach ($extra_updates as $notify_status => $extra_update) { + foreach ($extra_updates as $notify_status => $extra_update) + { if (!isset($old_user_ids[$notify_status][$from_value])) { continue; @@ -174,8 +175,9 @@ function phpbb_update_rows_avoiding_duplicates_notify_status($db, $table, $colum AND " . $db->sql_in_set('user_id', $different_user_ids); $queries[] = $sql; } - - if ($extra_update) { + + if ($extra_update) + { $same_user_ids = array_diff($old_user_ids[$notify_status][$from_value], $different_user_ids); if (!empty($same_user_ids)) { -- cgit v1.2.1 From fe87d441eeb54bf5efb56a0f69f42848d9ef53d5 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Wed, 5 Dec 2012 10:44:36 -0500 Subject: [ticket/11162] Review comments fixed. PHPBB3-11162 --- phpBB/includes/functions_tricky_update.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions_tricky_update.php b/phpBB/includes/functions_tricky_update.php index 10321618b2..05cb65e68d 100644 --- a/phpBB/includes/functions_tricky_update.php +++ b/phpBB/includes/functions_tricky_update.php @@ -39,19 +39,19 @@ function phpbb_update_rows_avoiding_duplicates($db, $table, $column, $from_value $old_user_ids = array(); while ($row = $db->sql_fetchrow($result)) { - $old_user_ids[$row[$column]][] = $row['user_id']; + $old_user_ids[$row[$column]][] = (int) $row['user_id']; } $db->sql_freeresult($result); $sql = "SELECT $column, user_id FROM $table - WHERE $column = '" . (int) $to_value . "'"; + WHERE $column = " . (int) $to_value; $result = $db->sql_query($sql); $new_user_ids = array(); while ($row = $db->sql_fetchrow($result)) { - $new_user_ids[$row[$column]][] = $row['user_id']; + $new_user_ids[$row[$column]][] = (int) $row['user_id']; } $db->sql_freeresult($result); @@ -126,19 +126,19 @@ function phpbb_update_rows_avoiding_duplicates_notify_status($db, $table, $colum $old_user_ids = array(); while ($row = $db->sql_fetchrow($result)) { - $old_user_ids[(int) $row['notify_status']][$row[$column]][] = $row['user_id']; + $old_user_ids[(int) $row['notify_status']][$row[$column]][] = (int) $row['user_id']; } $db->sql_freeresult($result); $sql = "SELECT $column, user_id FROM $table - WHERE $column = '" . (int) $to_value . "'"; + WHERE $column = " . (int) $to_value; $result = $db->sql_query($sql); $new_user_ids = array(); while ($row = $db->sql_fetchrow($result)) { - $new_user_ids[$row[$column]][] = $row['user_id']; + $new_user_ids[$row[$column]][] = (int) $row['user_id']; } $db->sql_freeresult($result); -- cgit v1.2.1 From f5de11438c471a76fc5c5f3a4b8c4c29d07ed734 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Wed, 5 Dec 2012 10:47:26 -0500 Subject: [ticket/11162] Use empty($queries). PHPBB3-11162 --- phpBB/includes/functions_tricky_update.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions_tricky_update.php b/phpBB/includes/functions_tricky_update.php index 05cb65e68d..664c246888 100644 --- a/phpBB/includes/functions_tricky_update.php +++ b/phpBB/includes/functions_tricky_update.php @@ -56,14 +56,12 @@ function phpbb_update_rows_avoiding_duplicates($db, $table, $column, $from_value $db->sql_freeresult($result); $queries = array(); - $any_found = false; foreach ($from_values as $from_value) { if (!isset($old_user_ids[$from_value])) { continue; } - $any_found = true; if (empty($new_user_ids)) { $sql = "UPDATE $table @@ -85,7 +83,7 @@ function phpbb_update_rows_avoiding_duplicates($db, $table, $column, $from_value } } - if ($any_found) + if (!empty($queries)) { $db->sql_transaction('begin'); @@ -143,7 +141,6 @@ function phpbb_update_rows_avoiding_duplicates_notify_status($db, $table, $colum $db->sql_freeresult($result); $queries = array(); - $any_found = false; $extra_updates = array( 0 => 'notify_status = 0', 1 => '', @@ -156,7 +153,6 @@ function phpbb_update_rows_avoiding_duplicates_notify_status($db, $table, $colum { continue; } - $any_found = true; if (empty($new_user_ids)) { $sql = "UPDATE $table @@ -192,7 +188,7 @@ function phpbb_update_rows_avoiding_duplicates_notify_status($db, $table, $colum } } - if ($any_found) + if (!empty($queries)) { $db->sql_transaction('begin'); -- cgit v1.2.1 From 03d2c6413c25b1faf7f37ff20625ce986b19eb88 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Fri, 7 Dec 2012 21:57:33 -0500 Subject: [ticket/11248] Convert line endings to LF - develop edition. PHPBB3-11248 --- phpBB/includes/sphinxapi.php | 3424 +++++++++++++++++++++--------------------- 1 file changed, 1712 insertions(+), 1712 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/sphinxapi.php b/phpBB/includes/sphinxapi.php index bd83b1d2e0..6c3b66710c 100644 --- a/phpBB/includes/sphinxapi.php +++ b/phpBB/includes/sphinxapi.php @@ -1,1712 +1,1712 @@ -=8 ) - { - $v = (int)$v; - return pack ( "NN", $v>>32, $v&0xFFFFFFFF ); - } - - // x32, int - if ( is_int($v) ) - return pack ( "NN", $v < 0 ? -1 : 0, $v ); - - // x32, bcmath - if ( function_exists("bcmul") ) - { - if ( bccomp ( $v, 0 ) == -1 ) - $v = bcadd ( "18446744073709551616", $v ); - $h = bcdiv ( $v, "4294967296", 0 ); - $l = bcmod ( $v, "4294967296" ); - return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit - } - - // x32, no-bcmath - $p = max(0, strlen($v) - 13); - $lo = abs((float)substr($v, $p)); - $hi = abs((float)substr($v, 0, $p)); - - $m = $lo + $hi*1316134912.0; // (10 ^ 13) % (1 << 32) = 1316134912 - $q = floor($m/4294967296.0); - $l = $m - ($q*4294967296.0); - $h = $hi*2328.0 + $q; // (10 ^ 13) / (1 << 32) = 2328 - - if ( $v<0 ) - { - if ( $l==0 ) - $h = 4294967296.0 - $h; - else - { - $h = 4294967295.0 - $h; - $l = 4294967296.0 - $l; - } - } - return pack ( "NN", $h, $l ); -} - -/// pack 64-bit unsigned -function sphPackU64 ( $v ) -{ - assert ( is_numeric($v) ); - - // x64 - if ( PHP_INT_SIZE>=8 ) - { - assert ( $v>=0 ); - - // x64, int - if ( is_int($v) ) - return pack ( "NN", $v>>32, $v&0xFFFFFFFF ); - - // x64, bcmath - if ( function_exists("bcmul") ) - { - $h = bcdiv ( $v, 4294967296, 0 ); - $l = bcmod ( $v, 4294967296 ); - return pack ( "NN", $h, $l ); - } - - // x64, no-bcmath - $p = max ( 0, strlen($v) - 13 ); - $lo = (int)substr ( $v, $p ); - $hi = (int)substr ( $v, 0, $p ); - - $m = $lo + $hi*1316134912; - $l = $m % 4294967296; - $h = $hi*2328 + (int)($m/4294967296); - - return pack ( "NN", $h, $l ); - } - - // x32, int - if ( is_int($v) ) - return pack ( "NN", 0, $v ); - - // x32, bcmath - if ( function_exists("bcmul") ) - { - $h = bcdiv ( $v, "4294967296", 0 ); - $l = bcmod ( $v, "4294967296" ); - return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit - } - - // x32, no-bcmath - $p = max(0, strlen($v) - 13); - $lo = (float)substr($v, $p); - $hi = (float)substr($v, 0, $p); - - $m = $lo + $hi*1316134912.0; - $q = floor($m / 4294967296.0); - $l = $m - ($q * 4294967296.0); - $h = $hi*2328.0 + $q; - - return pack ( "NN", $h, $l ); -} - -// unpack 64-bit unsigned -function sphUnpackU64 ( $v ) -{ - list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) ); - - if ( PHP_INT_SIZE>=8 ) - { - if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again - if ( $lo<0 ) $lo += (1<<32); - - // x64, int - if ( $hi<=2147483647 ) - return ($hi<<32) + $lo; - - // x64, bcmath - if ( function_exists("bcmul") ) - return bcadd ( $lo, bcmul ( $hi, "4294967296" ) ); - - // x64, no-bcmath - $C = 100000; - $h = ((int)($hi / $C) << 32) + (int)($lo / $C); - $l = (($hi % $C) << 32) + ($lo % $C); - if ( $l>$C ) - { - $h += (int)($l / $C); - $l = $l % $C; - } - - if ( $h==0 ) - return $l; - return sprintf ( "%d%05d", $h, $l ); - } - - // x32, int - if ( $hi==0 ) - { - if ( $lo>0 ) - return $lo; - return sprintf ( "%u", $lo ); - } - - $hi = sprintf ( "%u", $hi ); - $lo = sprintf ( "%u", $lo ); - - // x32, bcmath - if ( function_exists("bcmul") ) - return bcadd ( $lo, bcmul ( $hi, "4294967296" ) ); - - // x32, no-bcmath - $hi = (float)$hi; - $lo = (float)$lo; - - $q = floor($hi/10000000.0); - $r = $hi - $q*10000000.0; - $m = $lo + $r*4967296.0; - $mq = floor($m/10000000.0); - $l = $m - $mq*10000000.0; - $h = $q*4294967296.0 + $r*429.0 + $mq; - - $h = sprintf ( "%.0f", $h ); - $l = sprintf ( "%07.0f", $l ); - if ( $h=="0" ) - return sprintf( "%.0f", (float)$l ); - return $h . $l; -} - -// unpack 64-bit signed -function sphUnpackI64 ( $v ) -{ - list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) ); - - // x64 - if ( PHP_INT_SIZE>=8 ) - { - if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again - if ( $lo<0 ) $lo += (1<<32); - - return ($hi<<32) + $lo; - } - - // x32, int - if ( $hi==0 ) - { - if ( $lo>0 ) - return $lo; - return sprintf ( "%u", $lo ); - } - // x32, int - elseif ( $hi==-1 ) - { - if ( $lo<0 ) - return $lo; - return sprintf ( "%.0f", $lo - 4294967296.0 ); - } - - $neg = ""; - $c = 0; - if ( $hi<0 ) - { - $hi = ~$hi; - $lo = ~$lo; - $c = 1; - $neg = "-"; - } - - $hi = sprintf ( "%u", $hi ); - $lo = sprintf ( "%u", $lo ); - - // x32, bcmath - if ( function_exists("bcmul") ) - return $neg . bcadd ( bcadd ( $lo, bcmul ( $hi, "4294967296" ) ), $c ); - - // x32, no-bcmath - $hi = (float)$hi; - $lo = (float)$lo; - - $q = floor($hi/10000000.0); - $r = $hi - $q*10000000.0; - $m = $lo + $r*4967296.0; - $mq = floor($m/10000000.0); - $l = $m - $mq*10000000.0 + $c; - $h = $q*4294967296.0 + $r*429.0 + $mq; - if ( $l==10000000 ) - { - $l = 0; - $h += 1; - } - - $h = sprintf ( "%.0f", $h ); - $l = sprintf ( "%07.0f", $l ); - if ( $h=="0" ) - return $neg . sprintf( "%.0f", (float)$l ); - return $neg . $h . $l; -} - - -function sphFixUint ( $value ) -{ - if ( PHP_INT_SIZE>=8 ) - { - // x64 route, workaround broken unpack() in 5.2.2+ - if ( $value<0 ) $value += (1<<32); - return $value; - } - else - { - // x32 route, workaround php signed/unsigned braindamage - return sprintf ( "%u", $value ); - } -} - - -/// sphinx searchd client class -class SphinxClient -{ - var $_host; ///< searchd host (default is "localhost") - var $_port; ///< searchd port (default is 9312) - var $_offset; ///< how many records to seek from result-set start (default is 0) - var $_limit; ///< how many records to return from result-set starting at offset (default is 20) - var $_mode; ///< query matching mode (default is SPH_MATCH_ALL) - var $_weights; ///< per-field weights (default is 1 for all fields) - var $_sort; ///< match sorting mode (default is SPH_SORT_RELEVANCE) - var $_sortby; ///< attribute to sort by (defualt is "") - var $_min_id; ///< min ID to match (default is 0, which means no limit) - var $_max_id; ///< max ID to match (default is 0, which means no limit) - var $_filters; ///< search filters - var $_groupby; ///< group-by attribute name - var $_groupfunc; ///< group-by function (to pre-process group-by attribute value with) - var $_groupsort; ///< group-by sorting clause (to sort groups in result set with) - var $_groupdistinct;///< group-by count-distinct attribute - var $_maxmatches; ///< max matches to retrieve - var $_cutoff; ///< cutoff to stop searching at (default is 0) - var $_retrycount; ///< distributed retries count - var $_retrydelay; ///< distributed retries delay - var $_anchor; ///< geographical anchor point - var $_indexweights; ///< per-index weights - var $_ranker; ///< ranking mode (default is SPH_RANK_PROXIMITY_BM25) - var $_rankexpr; ///< ranking mode expression (for SPH_RANK_EXPR) - var $_maxquerytime; ///< max query time, milliseconds (default is 0, do not limit) - var $_fieldweights; ///< per-field-name weights - var $_overrides; ///< per-query attribute values overrides - var $_select; ///< select-list (attributes or expressions, with optional aliases) - - var $_error; ///< last error message - var $_warning; ///< last warning message - var $_connerror; ///< connection error vs remote error flag - - var $_reqs; ///< requests array for multi-query - var $_mbenc; ///< stored mbstring encoding - var $_arrayresult; ///< whether $result["matches"] should be a hash or an array - var $_timeout; ///< connect timeout - - ///////////////////////////////////////////////////////////////////////////// - // common stuff - ///////////////////////////////////////////////////////////////////////////// - - /// create a new client object and fill defaults - function SphinxClient () - { - // per-client-object settings - $this->_host = "localhost"; - $this->_port = 9312; - $this->_path = false; - $this->_socket = false; - - // per-query settings - $this->_offset = 0; - $this->_limit = 20; - $this->_mode = SPH_MATCH_ALL; - $this->_weights = array (); - $this->_sort = SPH_SORT_RELEVANCE; - $this->_sortby = ""; - $this->_min_id = 0; - $this->_max_id = 0; - $this->_filters = array (); - $this->_groupby = ""; - $this->_groupfunc = SPH_GROUPBY_DAY; - $this->_groupsort = "@group desc"; - $this->_groupdistinct= ""; - $this->_maxmatches = 1000; - $this->_cutoff = 0; - $this->_retrycount = 0; - $this->_retrydelay = 0; - $this->_anchor = array (); - $this->_indexweights= array (); - $this->_ranker = SPH_RANK_PROXIMITY_BM25; - $this->_rankexpr = ""; - $this->_maxquerytime= 0; - $this->_fieldweights= array(); - $this->_overrides = array(); - $this->_select = "*"; - - $this->_error = ""; // per-reply fields (for single-query case) - $this->_warning = ""; - $this->_connerror = false; - - $this->_reqs = array (); // requests storage (for multi-query case) - $this->_mbenc = ""; - $this->_arrayresult = false; - $this->_timeout = 0; - } - - function __destruct() - { - if ( $this->_socket !== false ) - fclose ( $this->_socket ); - } - - /// get last error message (string) - function GetLastError () - { - return $this->_error; - } - - /// get last warning message (string) - function GetLastWarning () - { - return $this->_warning; - } - - /// get last error flag (to tell network connection errors from searchd errors or broken responses) - function IsConnectError() - { - return $this->_connerror; - } - - /// set searchd host name (string) and port (integer) - function SetServer ( $host, $port = 0 ) - { - assert ( is_string($host) ); - if ( $host[0] == '/') - { - $this->_path = 'unix://' . $host; - return; - } - if ( substr ( $host, 0, 7 )=="unix://" ) - { - $this->_path = $host; - return; - } - - assert ( is_int($port) ); - $this->_host = $host; - $this->_port = $port; - $this->_path = ''; - - } - - /// set server connection timeout (0 to remove) - function SetConnectTimeout ( $timeout ) - { - assert ( is_numeric($timeout) ); - $this->_timeout = $timeout; - } - - - function _Send ( $handle, $data, $length ) - { - if ( feof($handle) || fwrite ( $handle, $data, $length ) !== $length ) - { - $this->_error = 'connection unexpectedly closed (timed out?)'; - $this->_connerror = true; - return false; - } - return true; - } - - ///////////////////////////////////////////////////////////////////////////// - - /// enter mbstring workaround mode - function _MBPush () - { - $this->_mbenc = ""; - if ( ini_get ( "mbstring.func_overload" ) & 2 ) - { - $this->_mbenc = mb_internal_encoding(); - mb_internal_encoding ( "latin1" ); - } - } - - /// leave mbstring workaround mode - function _MBPop () - { - if ( $this->_mbenc ) - mb_internal_encoding ( $this->_mbenc ); - } - - /// connect to searchd server - function _Connect () - { - if ( $this->_socket!==false ) - { - // we are in persistent connection mode, so we have a socket - // however, need to check whether it's still alive - if ( !@feof ( $this->_socket ) ) - return $this->_socket; - - // force reopen - $this->_socket = false; - } - - $errno = 0; - $errstr = ""; - $this->_connerror = false; - - if ( $this->_path ) - { - $host = $this->_path; - $port = 0; - } - else - { - $host = $this->_host; - $port = $this->_port; - } - - if ( $this->_timeout<=0 ) - $fp = @fsockopen ( $host, $port, $errno, $errstr ); - else - $fp = @fsockopen ( $host, $port, $errno, $errstr, $this->_timeout ); - - if ( !$fp ) - { - if ( $this->_path ) - $location = $this->_path; - else - $location = "{$this->_host}:{$this->_port}"; - - $errstr = trim ( $errstr ); - $this->_error = "connection to $location failed (errno=$errno, msg=$errstr)"; - $this->_connerror = true; - return false; - } - - // send my version - // this is a subtle part. we must do it before (!) reading back from searchd. - // because otherwise under some conditions (reported on FreeBSD for instance) - // TCP stack could throttle write-write-read pattern because of Nagle. - if ( !$this->_Send ( $fp, pack ( "N", 1 ), 4 ) ) - { - fclose ( $fp ); - $this->_error = "failed to send client protocol version"; - return false; - } - - // check version - list(,$v) = unpack ( "N*", fread ( $fp, 4 ) ); - $v = (int)$v; - if ( $v<1 ) - { - fclose ( $fp ); - $this->_error = "expected searchd protocol version 1+, got version '$v'"; - return false; - } - - return $fp; - } - - /// get and check response packet from searchd server - function _GetResponse ( $fp, $client_ver ) - { - $response = ""; - $len = 0; - - $header = fread ( $fp, 8 ); - if ( strlen($header)==8 ) - { - list ( $status, $ver, $len ) = array_values ( unpack ( "n2a/Nb", $header ) ); - $left = $len; - while ( $left>0 && !feof($fp) ) - { - $chunk = fread ( $fp, min ( 8192, $left ) ); - if ( $chunk ) - { - $response .= $chunk; - $left -= strlen($chunk); - } - } - } - if ( $this->_socket === false ) - fclose ( $fp ); - - // check response - $read = strlen ( $response ); - if ( !$response || $read!=$len ) - { - $this->_error = $len - ? "failed to read searchd response (status=$status, ver=$ver, len=$len, read=$read)" - : "received zero-sized searchd response"; - return false; - } - - // check status - if ( $status==SEARCHD_WARNING ) - { - list(,$wlen) = unpack ( "N*", substr ( $response, 0, 4 ) ); - $this->_warning = substr ( $response, 4, $wlen ); - return substr ( $response, 4+$wlen ); - } - if ( $status==SEARCHD_ERROR ) - { - $this->_error = "searchd error: " . substr ( $response, 4 ); - return false; - } - if ( $status==SEARCHD_RETRY ) - { - $this->_error = "temporary searchd error: " . substr ( $response, 4 ); - return false; - } - if ( $status!=SEARCHD_OK ) - { - $this->_error = "unknown status code '$status'"; - return false; - } - - // check version - if ( $ver<$client_ver ) - { - $this->_warning = sprintf ( "searchd command v.%d.%d older than client's v.%d.%d, some options might not work", - $ver>>8, $ver&0xff, $client_ver>>8, $client_ver&0xff ); - } - - return $response; - } - - ///////////////////////////////////////////////////////////////////////////// - // searching - ///////////////////////////////////////////////////////////////////////////// - - /// set offset and count into result set, - /// and optionally set max-matches and cutoff limits - function SetLimits ( $offset, $limit, $max=0, $cutoff=0 ) - { - assert ( is_int($offset) ); - assert ( is_int($limit) ); - assert ( $offset>=0 ); - assert ( $limit>0 ); - assert ( $max>=0 ); - $this->_offset = $offset; - $this->_limit = $limit; - if ( $max>0 ) - $this->_maxmatches = $max; - if ( $cutoff>0 ) - $this->_cutoff = $cutoff; - } - - /// set maximum query time, in milliseconds, per-index - /// integer, 0 means "do not limit" - function SetMaxQueryTime ( $max ) - { - assert ( is_int($max) ); - assert ( $max>=0 ); - $this->_maxquerytime = $max; - } - - /// set matching mode - function SetMatchMode ( $mode ) - { - assert ( $mode==SPH_MATCH_ALL - || $mode==SPH_MATCH_ANY - || $mode==SPH_MATCH_PHRASE - || $mode==SPH_MATCH_BOOLEAN - || $mode==SPH_MATCH_EXTENDED - || $mode==SPH_MATCH_FULLSCAN - || $mode==SPH_MATCH_EXTENDED2 ); - $this->_mode = $mode; - } - - /// set ranking mode - function SetRankingMode ( $ranker, $rankexpr="" ) - { - assert ( $ranker>=0 && $ranker_ranker = $ranker; - $this->_rankexpr = $rankexpr; - } - - /// set matches sorting mode - function SetSortMode ( $mode, $sortby="" ) - { - assert ( - $mode==SPH_SORT_RELEVANCE || - $mode==SPH_SORT_ATTR_DESC || - $mode==SPH_SORT_ATTR_ASC || - $mode==SPH_SORT_TIME_SEGMENTS || - $mode==SPH_SORT_EXTENDED || - $mode==SPH_SORT_EXPR ); - assert ( is_string($sortby) ); - assert ( $mode==SPH_SORT_RELEVANCE || strlen($sortby)>0 ); - - $this->_sort = $mode; - $this->_sortby = $sortby; - } - - /// bind per-field weights by order - /// DEPRECATED; use SetFieldWeights() instead - function SetWeights ( $weights ) - { - assert ( is_array($weights) ); - foreach ( $weights as $weight ) - assert ( is_int($weight) ); - - $this->_weights = $weights; - } - - /// bind per-field weights by name - function SetFieldWeights ( $weights ) - { - assert ( is_array($weights) ); - foreach ( $weights as $name=>$weight ) - { - assert ( is_string($name) ); - assert ( is_int($weight) ); - } - $this->_fieldweights = $weights; - } - - /// bind per-index weights by name - function SetIndexWeights ( $weights ) - { - assert ( is_array($weights) ); - foreach ( $weights as $index=>$weight ) - { - assert ( is_string($index) ); - assert ( is_int($weight) ); - } - $this->_indexweights = $weights; - } - - /// set IDs range to match - /// only match records if document ID is beetwen $min and $max (inclusive) - function SetIDRange ( $min, $max ) - { - assert ( is_numeric($min) ); - assert ( is_numeric($max) ); - assert ( $min<=$max ); - $this->_min_id = $min; - $this->_max_id = $max; - } - - /// set values set filter - /// only match records where $attribute value is in given set - function SetFilter ( $attribute, $values, $exclude=false ) - { - assert ( is_string($attribute) ); - assert ( is_array($values) ); - assert ( count($values) ); - - if ( is_array($values) && count($values) ) - { - foreach ( $values as $value ) - assert ( is_numeric($value) ); - - $this->_filters[] = array ( "type"=>SPH_FILTER_VALUES, "attr"=>$attribute, "exclude"=>$exclude, "values"=>$values ); - } - } - - /// set range filter - /// only match records if $attribute value is beetwen $min and $max (inclusive) - function SetFilterRange ( $attribute, $min, $max, $exclude=false ) - { - assert ( is_string($attribute) ); - assert ( is_numeric($min) ); - assert ( is_numeric($max) ); - assert ( $min<=$max ); - - $this->_filters[] = array ( "type"=>SPH_FILTER_RANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max ); - } - - /// set float range filter - /// only match records if $attribute value is beetwen $min and $max (inclusive) - function SetFilterFloatRange ( $attribute, $min, $max, $exclude=false ) - { - assert ( is_string($attribute) ); - assert ( is_float($min) ); - assert ( is_float($max) ); - assert ( $min<=$max ); - - $this->_filters[] = array ( "type"=>SPH_FILTER_FLOATRANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max ); - } - - /// setup anchor point for geosphere distance calculations - /// required to use @geodist in filters and sorting - /// latitude and longitude must be in radians - function SetGeoAnchor ( $attrlat, $attrlong, $lat, $long ) - { - assert ( is_string($attrlat) ); - assert ( is_string($attrlong) ); - assert ( is_float($lat) ); - assert ( is_float($long) ); - - $this->_anchor = array ( "attrlat"=>$attrlat, "attrlong"=>$attrlong, "lat"=>$lat, "long"=>$long ); - } - - /// set grouping attribute and function - function SetGroupBy ( $attribute, $func, $groupsort="@group desc" ) - { - assert ( is_string($attribute) ); - assert ( is_string($groupsort) ); - assert ( $func==SPH_GROUPBY_DAY - || $func==SPH_GROUPBY_WEEK - || $func==SPH_GROUPBY_MONTH - || $func==SPH_GROUPBY_YEAR - || $func==SPH_GROUPBY_ATTR - || $func==SPH_GROUPBY_ATTRPAIR ); - - $this->_groupby = $attribute; - $this->_groupfunc = $func; - $this->_groupsort = $groupsort; - } - - /// set count-distinct attribute for group-by queries - function SetGroupDistinct ( $attribute ) - { - assert ( is_string($attribute) ); - $this->_groupdistinct = $attribute; - } - - /// set distributed retries count and delay - function SetRetries ( $count, $delay=0 ) - { - assert ( is_int($count) && $count>=0 ); - assert ( is_int($delay) && $delay>=0 ); - $this->_retrycount = $count; - $this->_retrydelay = $delay; - } - - /// set result set format (hash or array; hash by default) - /// PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs - function SetArrayResult ( $arrayresult ) - { - assert ( is_bool($arrayresult) ); - $this->_arrayresult = $arrayresult; - } - - /// set attribute values override - /// there can be only one override per attribute - /// $values must be a hash that maps document IDs to attribute values - function SetOverride ( $attrname, $attrtype, $values ) - { - assert ( is_string ( $attrname ) ); - assert ( in_array ( $attrtype, array ( SPH_ATTR_INTEGER, SPH_ATTR_TIMESTAMP, SPH_ATTR_BOOL, SPH_ATTR_FLOAT, SPH_ATTR_BIGINT ) ) ); - assert ( is_array ( $values ) ); - - $this->_overrides[$attrname] = array ( "attr"=>$attrname, "type"=>$attrtype, "values"=>$values ); - } - - /// set select-list (attributes or expressions), SQL-like syntax - function SetSelect ( $select ) - { - assert ( is_string ( $select ) ); - $this->_select = $select; - } - - ////////////////////////////////////////////////////////////////////////////// - - /// clear all filters (for multi-queries) - function ResetFilters () - { - $this->_filters = array(); - $this->_anchor = array(); - } - - /// clear groupby settings (for multi-queries) - function ResetGroupBy () - { - $this->_groupby = ""; - $this->_groupfunc = SPH_GROUPBY_DAY; - $this->_groupsort = "@group desc"; - $this->_groupdistinct= ""; - } - - /// clear all attribute value overrides (for multi-queries) - function ResetOverrides () - { - $this->_overrides = array (); - } - - ////////////////////////////////////////////////////////////////////////////// - - /// connect to searchd server, run given search query through given indexes, - /// and return the search results - function Query ( $query, $index="*", $comment="" ) - { - assert ( empty($this->_reqs) ); - - $this->AddQuery ( $query, $index, $comment ); - $results = $this->RunQueries (); - $this->_reqs = array (); // just in case it failed too early - - if ( !is_array($results) ) - return false; // probably network error; error message should be already filled - - $this->_error = $results[0]["error"]; - $this->_warning = $results[0]["warning"]; - if ( $results[0]["status"]==SEARCHD_ERROR ) - return false; - else - return $results[0]; - } - - /// helper to pack floats in network byte order - function _PackFloat ( $f ) - { - $t1 = pack ( "f", $f ); // machine order - list(,$t2) = unpack ( "L*", $t1 ); // int in machine order - return pack ( "N", $t2 ); - } - - /// add query to multi-query batch - /// returns index into results array from RunQueries() call - function AddQuery ( $query, $index="*", $comment="" ) - { - // mbstring workaround - $this->_MBPush (); - - // build request - $req = pack ( "NNNN", $this->_offset, $this->_limit, $this->_mode, $this->_ranker ); - if ( $this->_ranker==SPH_RANK_EXPR ) - $req .= pack ( "N", strlen($this->_rankexpr) ) . $this->_rankexpr; - $req .= pack ( "N", $this->_sort ); // (deprecated) sort mode - $req .= pack ( "N", strlen($this->_sortby) ) . $this->_sortby; - $req .= pack ( "N", strlen($query) ) . $query; // query itself - $req .= pack ( "N", count($this->_weights) ); // weights - foreach ( $this->_weights as $weight ) - $req .= pack ( "N", (int)$weight ); - $req .= pack ( "N", strlen($index) ) . $index; // indexes - $req .= pack ( "N", 1 ); // id64 range marker - $req .= sphPackU64 ( $this->_min_id ) . sphPackU64 ( $this->_max_id ); // id64 range - - // filters - $req .= pack ( "N", count($this->_filters) ); - foreach ( $this->_filters as $filter ) - { - $req .= pack ( "N", strlen($filter["attr"]) ) . $filter["attr"]; - $req .= pack ( "N", $filter["type"] ); - switch ( $filter["type"] ) - { - case SPH_FILTER_VALUES: - $req .= pack ( "N", count($filter["values"]) ); - foreach ( $filter["values"] as $value ) - $req .= sphPackI64 ( $value ); - break; - - case SPH_FILTER_RANGE: - $req .= sphPackI64 ( $filter["min"] ) . sphPackI64 ( $filter["max"] ); - break; - - case SPH_FILTER_FLOATRANGE: - $req .= $this->_PackFloat ( $filter["min"] ) . $this->_PackFloat ( $filter["max"] ); - break; - - default: - assert ( 0 && "internal error: unhandled filter type" ); - } - $req .= pack ( "N", $filter["exclude"] ); - } - - // group-by clause, max-matches count, group-sort clause, cutoff count - $req .= pack ( "NN", $this->_groupfunc, strlen($this->_groupby) ) . $this->_groupby; - $req .= pack ( "N", $this->_maxmatches ); - $req .= pack ( "N", strlen($this->_groupsort) ) . $this->_groupsort; - $req .= pack ( "NNN", $this->_cutoff, $this->_retrycount, $this->_retrydelay ); - $req .= pack ( "N", strlen($this->_groupdistinct) ) . $this->_groupdistinct; - - // anchor point - if ( empty($this->_anchor) ) - { - $req .= pack ( "N", 0 ); - } else - { - $a =& $this->_anchor; - $req .= pack ( "N", 1 ); - $req .= pack ( "N", strlen($a["attrlat"]) ) . $a["attrlat"]; - $req .= pack ( "N", strlen($a["attrlong"]) ) . $a["attrlong"]; - $req .= $this->_PackFloat ( $a["lat"] ) . $this->_PackFloat ( $a["long"] ); - } - - // per-index weights - $req .= pack ( "N", count($this->_indexweights) ); - foreach ( $this->_indexweights as $idx=>$weight ) - $req .= pack ( "N", strlen($idx) ) . $idx . pack ( "N", $weight ); - - // max query time - $req .= pack ( "N", $this->_maxquerytime ); - - // per-field weights - $req .= pack ( "N", count($this->_fieldweights) ); - foreach ( $this->_fieldweights as $field=>$weight ) - $req .= pack ( "N", strlen($field) ) . $field . pack ( "N", $weight ); - - // comment - $req .= pack ( "N", strlen($comment) ) . $comment; - - // attribute overrides - $req .= pack ( "N", count($this->_overrides) ); - foreach ( $this->_overrides as $key => $entry ) - { - $req .= pack ( "N", strlen($entry["attr"]) ) . $entry["attr"]; - $req .= pack ( "NN", $entry["type"], count($entry["values"]) ); - foreach ( $entry["values"] as $id=>$val ) - { - assert ( is_numeric($id) ); - assert ( is_numeric($val) ); - - $req .= sphPackU64 ( $id ); - switch ( $entry["type"] ) - { - case SPH_ATTR_FLOAT: $req .= $this->_PackFloat ( $val ); break; - case SPH_ATTR_BIGINT: $req .= sphPackI64 ( $val ); break; - default: $req .= pack ( "N", $val ); break; - } - } - } - - // select-list - $req .= pack ( "N", strlen($this->_select) ) . $this->_select; - - // mbstring workaround - $this->_MBPop (); - - // store request to requests array - $this->_reqs[] = $req; - return count($this->_reqs)-1; - } - - /// connect to searchd, run queries batch, and return an array of result sets - function RunQueries () - { - if ( empty($this->_reqs) ) - { - $this->_error = "no queries defined, issue AddQuery() first"; - return false; - } - - // mbstring workaround - $this->_MBPush (); - - if (!( $fp = $this->_Connect() )) - { - $this->_MBPop (); - return false; - } - - // send query, get response - $nreqs = count($this->_reqs); - $req = join ( "", $this->_reqs ); - $len = 8+strlen($req); - $req = pack ( "nnNNN", SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, 0, $nreqs ) . $req; // add header - - if ( !( $this->_Send ( $fp, $req, $len+8 ) ) || - !( $response = $this->_GetResponse ( $fp, VER_COMMAND_SEARCH ) ) ) - { - $this->_MBPop (); - return false; - } - - // query sent ok; we can reset reqs now - $this->_reqs = array (); - - // parse and return response - return $this->_ParseSearchResponse ( $response, $nreqs ); - } - - /// parse and return search query (or queries) response - function _ParseSearchResponse ( $response, $nreqs ) - { - $p = 0; // current position - $max = strlen($response); // max position for checks, to protect against broken responses - - $results = array (); - for ( $ires=0; $ires<$nreqs && $p<$max; $ires++ ) - { - $results[] = array(); - $result =& $results[$ires]; - - $result["error"] = ""; - $result["warning"] = ""; - - // extract status - list(,$status) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - $result["status"] = $status; - if ( $status!=SEARCHD_OK ) - { - list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - $message = substr ( $response, $p, $len ); $p += $len; - - if ( $status==SEARCHD_WARNING ) - { - $result["warning"] = $message; - } else - { - $result["error"] = $message; - continue; - } - } - - // read schema - $fields = array (); - $attrs = array (); - - list(,$nfields) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - while ( $nfields-->0 && $p<$max ) - { - list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - $fields[] = substr ( $response, $p, $len ); $p += $len; - } - $result["fields"] = $fields; - - list(,$nattrs) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - while ( $nattrs-->0 && $p<$max ) - { - list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - $attr = substr ( $response, $p, $len ); $p += $len; - list(,$type) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - $attrs[$attr] = $type; - } - $result["attrs"] = $attrs; - - // read match count - list(,$count) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - list(,$id64) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - - // read matches - $idx = -1; - while ( $count-->0 && $p<$max ) - { - // index into result array - $idx++; - - // parse document id and weight - if ( $id64 ) - { - $doc = sphUnpackU64 ( substr ( $response, $p, 8 ) ); $p += 8; - list(,$weight) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - } - else - { - list ( $doc, $weight ) = array_values ( unpack ( "N*N*", - substr ( $response, $p, 8 ) ) ); - $p += 8; - $doc = sphFixUint($doc); - } - $weight = sprintf ( "%u", $weight ); - - // create match entry - if ( $this->_arrayresult ) - $result["matches"][$idx] = array ( "id"=>$doc, "weight"=>$weight ); - else - $result["matches"][$doc]["weight"] = $weight; - - // parse and create attributes - $attrvals = array (); - foreach ( $attrs as $attr=>$type ) - { - // handle 64bit ints - if ( $type==SPH_ATTR_BIGINT ) - { - $attrvals[$attr] = sphUnpackI64 ( substr ( $response, $p, 8 ) ); $p += 8; - continue; - } - - // handle floats - if ( $type==SPH_ATTR_FLOAT ) - { - list(,$uval) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - list(,$fval) = unpack ( "f*", pack ( "L", $uval ) ); - $attrvals[$attr] = $fval; - continue; - } - - // handle everything else as unsigned ints - list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - if ( $type==SPH_ATTR_MULTI ) - { - $attrvals[$attr] = array (); - $nvalues = $val; - while ( $nvalues-->0 && $p<$max ) - { - list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - $attrvals[$attr][] = sphFixUint($val); - } - } else if ( $type==SPH_ATTR_MULTI64 ) - { - $attrvals[$attr] = array (); - $nvalues = $val; - while ( $nvalues>0 && $p<$max ) - { - $attrvals[$attr][] = sphUnpackU64 ( substr ( $response, $p, 8 ) ); $p += 8; - $nvalues -= 2; - } - } else if ( $type==SPH_ATTR_STRING ) - { - $attrvals[$attr] = substr ( $response, $p, $val ); - $p += $val; - } else - { - $attrvals[$attr] = sphFixUint($val); - } - } - - if ( $this->_arrayresult ) - $result["matches"][$idx]["attrs"] = $attrvals; - else - $result["matches"][$doc]["attrs"] = $attrvals; - } - - list ( $total, $total_found, $msecs, $words ) = - array_values ( unpack ( "N*N*N*N*", substr ( $response, $p, 16 ) ) ); - $result["total"] = sprintf ( "%u", $total ); - $result["total_found"] = sprintf ( "%u", $total_found ); - $result["time"] = sprintf ( "%.3f", $msecs/1000 ); - $p += 16; - - while ( $words-->0 && $p<$max ) - { - list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - $word = substr ( $response, $p, $len ); $p += $len; - list ( $docs, $hits ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8; - $result["words"][$word] = array ( - "docs"=>sprintf ( "%u", $docs ), - "hits"=>sprintf ( "%u", $hits ) ); - } - } - - $this->_MBPop (); - return $results; - } - - ///////////////////////////////////////////////////////////////////////////// - // excerpts generation - ///////////////////////////////////////////////////////////////////////////// - - /// connect to searchd server, and generate exceprts (snippets) - /// of given documents for given query. returns false on failure, - /// an array of snippets on success - function BuildExcerpts ( $docs, $index, $words, $opts=array() ) - { - assert ( is_array($docs) ); - assert ( is_string($index) ); - assert ( is_string($words) ); - assert ( is_array($opts) ); - - $this->_MBPush (); - - if (!( $fp = $this->_Connect() )) - { - $this->_MBPop(); - return false; - } - - ///////////////// - // fixup options - ///////////////// - - if ( !isset($opts["before_match"]) ) $opts["before_match"] = ""; - if ( !isset($opts["after_match"]) ) $opts["after_match"] = ""; - if ( !isset($opts["chunk_separator"]) ) $opts["chunk_separator"] = " ... "; - if ( !isset($opts["limit"]) ) $opts["limit"] = 256; - if ( !isset($opts["limit_passages"]) ) $opts["limit_passages"] = 0; - if ( !isset($opts["limit_words"]) ) $opts["limit_words"] = 0; - if ( !isset($opts["around"]) ) $opts["around"] = 5; - if ( !isset($opts["exact_phrase"]) ) $opts["exact_phrase"] = false; - if ( !isset($opts["single_passage"]) ) $opts["single_passage"] = false; - if ( !isset($opts["use_boundaries"]) ) $opts["use_boundaries"] = false; - if ( !isset($opts["weight_order"]) ) $opts["weight_order"] = false; - if ( !isset($opts["query_mode"]) ) $opts["query_mode"] = false; - if ( !isset($opts["force_all_words"]) ) $opts["force_all_words"] = false; - if ( !isset($opts["start_passage_id"]) ) $opts["start_passage_id"] = 1; - if ( !isset($opts["load_files"]) ) $opts["load_files"] = false; - if ( !isset($opts["html_strip_mode"]) ) $opts["html_strip_mode"] = "index"; - if ( !isset($opts["allow_empty"]) ) $opts["allow_empty"] = false; - if ( !isset($opts["passage_boundary"]) ) $opts["passage_boundary"] = "none"; - if ( !isset($opts["emit_zones"]) ) $opts["emit_zones"] = false; - if ( !isset($opts["load_files_scattered"]) ) $opts["load_files_scattered"] = false; - - - ///////////////// - // build request - ///////////////// - - // v.1.2 req - $flags = 1; // remove spaces - if ( $opts["exact_phrase"] ) $flags |= 2; - if ( $opts["single_passage"] ) $flags |= 4; - if ( $opts["use_boundaries"] ) $flags |= 8; - if ( $opts["weight_order"] ) $flags |= 16; - if ( $opts["query_mode"] ) $flags |= 32; - if ( $opts["force_all_words"] ) $flags |= 64; - if ( $opts["load_files"] ) $flags |= 128; - if ( $opts["allow_empty"] ) $flags |= 256; - if ( $opts["emit_zones"] ) $flags |= 512; - if ( $opts["load_files_scattered"] ) $flags |= 1024; - $req = pack ( "NN", 0, $flags ); // mode=0, flags=$flags - $req .= pack ( "N", strlen($index) ) . $index; // req index - $req .= pack ( "N", strlen($words) ) . $words; // req words - - // options - $req .= pack ( "N", strlen($opts["before_match"]) ) . $opts["before_match"]; - $req .= pack ( "N", strlen($opts["after_match"]) ) . $opts["after_match"]; - $req .= pack ( "N", strlen($opts["chunk_separator"]) ) . $opts["chunk_separator"]; - $req .= pack ( "NN", (int)$opts["limit"], (int)$opts["around"] ); - $req .= pack ( "NNN", (int)$opts["limit_passages"], (int)$opts["limit_words"], (int)$opts["start_passage_id"] ); // v.1.2 - $req .= pack ( "N", strlen($opts["html_strip_mode"]) ) . $opts["html_strip_mode"]; - $req .= pack ( "N", strlen($opts["passage_boundary"]) ) . $opts["passage_boundary"]; - - // documents - $req .= pack ( "N", count($docs) ); - foreach ( $docs as $doc ) - { - assert ( is_string($doc) ); - $req .= pack ( "N", strlen($doc) ) . $doc; - } - - //////////////////////////// - // send query, get response - //////////////////////////// - - $len = strlen($req); - $req = pack ( "nnN", SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, $len ) . $req; // add header - if ( !( $this->_Send ( $fp, $req, $len+8 ) ) || - !( $response = $this->_GetResponse ( $fp, VER_COMMAND_EXCERPT ) ) ) - { - $this->_MBPop (); - return false; - } - - ////////////////// - // parse response - ////////////////// - - $pos = 0; - $res = array (); - $rlen = strlen($response); - for ( $i=0; $i $rlen ) - { - $this->_error = "incomplete reply"; - $this->_MBPop (); - return false; - } - $res[] = $len ? substr ( $response, $pos, $len ) : ""; - $pos += $len; - } - - $this->_MBPop (); - return $res; - } - - - ///////////////////////////////////////////////////////////////////////////// - // keyword generation - ///////////////////////////////////////////////////////////////////////////// - - /// connect to searchd server, and generate keyword list for a given query - /// returns false on failure, - /// an array of words on success - function BuildKeywords ( $query, $index, $hits ) - { - assert ( is_string($query) ); - assert ( is_string($index) ); - assert ( is_bool($hits) ); - - $this->_MBPush (); - - if (!( $fp = $this->_Connect() )) - { - $this->_MBPop(); - return false; - } - - ///////////////// - // build request - ///////////////// - - // v.1.0 req - $req = pack ( "N", strlen($query) ) . $query; // req query - $req .= pack ( "N", strlen($index) ) . $index; // req index - $req .= pack ( "N", (int)$hits ); - - //////////////////////////// - // send query, get response - //////////////////////////// - - $len = strlen($req); - $req = pack ( "nnN", SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, $len ) . $req; // add header - if ( !( $this->_Send ( $fp, $req, $len+8 ) ) || - !( $response = $this->_GetResponse ( $fp, VER_COMMAND_KEYWORDS ) ) ) - { - $this->_MBPop (); - return false; - } - - ////////////////// - // parse response - ////////////////// - - $pos = 0; - $res = array (); - $rlen = strlen($response); - list(,$nwords) = unpack ( "N*", substr ( $response, $pos, 4 ) ); - $pos += 4; - for ( $i=0; $i<$nwords; $i++ ) - { - list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) ); $pos += 4; - $tokenized = $len ? substr ( $response, $pos, $len ) : ""; - $pos += $len; - - list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) ); $pos += 4; - $normalized = $len ? substr ( $response, $pos, $len ) : ""; - $pos += $len; - - $res[] = array ( "tokenized"=>$tokenized, "normalized"=>$normalized ); - - if ( $hits ) - { - list($ndocs,$nhits) = array_values ( unpack ( "N*N*", substr ( $response, $pos, 8 ) ) ); - $pos += 8; - $res [$i]["docs"] = $ndocs; - $res [$i]["hits"] = $nhits; - } - - if ( $pos > $rlen ) - { - $this->_error = "incomplete reply"; - $this->_MBPop (); - return false; - } - } - - $this->_MBPop (); - return $res; - } - - function EscapeString ( $string ) - { - $from = array ( '\\', '(',')','|','-','!','@','~','"','&', '/', '^', '$', '=' ); - $to = array ( '\\\\', '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/', '\^', '\$', '\=' ); - - return str_replace ( $from, $to, $string ); - } - - ///////////////////////////////////////////////////////////////////////////// - // attribute updates - ///////////////////////////////////////////////////////////////////////////// - - /// batch update given attributes in given rows in given indexes - /// returns amount of updated documents (0 or more) on success, or -1 on failure - function UpdateAttributes ( $index, $attrs, $values, $mva=false ) - { - // verify everything - assert ( is_string($index) ); - assert ( is_bool($mva) ); - - assert ( is_array($attrs) ); - foreach ( $attrs as $attr ) - assert ( is_string($attr) ); - - assert ( is_array($values) ); - foreach ( $values as $id=>$entry ) - { - assert ( is_numeric($id) ); - assert ( is_array($entry) ); - assert ( count($entry)==count($attrs) ); - foreach ( $entry as $v ) - { - if ( $mva ) - { - assert ( is_array($v) ); - foreach ( $v as $vv ) - assert ( is_int($vv) ); - } else - assert ( is_int($v) ); - } - } - - // build request - $this->_MBPush (); - $req = pack ( "N", strlen($index) ) . $index; - - $req .= pack ( "N", count($attrs) ); - foreach ( $attrs as $attr ) - { - $req .= pack ( "N", strlen($attr) ) . $attr; - $req .= pack ( "N", $mva ? 1 : 0 ); - } - - $req .= pack ( "N", count($values) ); - foreach ( $values as $id=>$entry ) - { - $req .= sphPackU64 ( $id ); - foreach ( $entry as $v ) - { - $req .= pack ( "N", $mva ? count($v) : $v ); - if ( $mva ) - foreach ( $v as $vv ) - $req .= pack ( "N", $vv ); - } - } - - // connect, send query, get response - if (!( $fp = $this->_Connect() )) - { - $this->_MBPop (); - return -1; - } - - $len = strlen($req); - $req = pack ( "nnN", SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, $len ) . $req; // add header - if ( !$this->_Send ( $fp, $req, $len+8 ) ) - { - $this->_MBPop (); - return -1; - } - - if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_UPDATE ) )) - { - $this->_MBPop (); - return -1; - } - - // parse response - list(,$updated) = unpack ( "N*", substr ( $response, 0, 4 ) ); - $this->_MBPop (); - return $updated; - } - - ///////////////////////////////////////////////////////////////////////////// - // persistent connections - ///////////////////////////////////////////////////////////////////////////// - - function Open() - { - if ( $this->_socket !== false ) - { - $this->_error = 'already connected'; - return false; - } - if ( !$fp = $this->_Connect() ) - return false; - - // command, command version = 0, body length = 4, body = 1 - $req = pack ( "nnNN", SEARCHD_COMMAND_PERSIST, 0, 4, 1 ); - if ( !$this->_Send ( $fp, $req, 12 ) ) - return false; - - $this->_socket = $fp; - return true; - } - - function Close() - { - if ( $this->_socket === false ) - { - $this->_error = 'not connected'; - return false; - } - - fclose ( $this->_socket ); - $this->_socket = false; - - return true; - } - - ////////////////////////////////////////////////////////////////////////// - // status - ////////////////////////////////////////////////////////////////////////// - - function Status () - { - $this->_MBPush (); - if (!( $fp = $this->_Connect() )) - { - $this->_MBPop(); - return false; - } - - $req = pack ( "nnNN", SEARCHD_COMMAND_STATUS, VER_COMMAND_STATUS, 4, 1 ); // len=4, body=1 - if ( !( $this->_Send ( $fp, $req, 12 ) ) || - !( $response = $this->_GetResponse ( $fp, VER_COMMAND_STATUS ) ) ) - { - $this->_MBPop (); - return false; - } - - $res = substr ( $response, 4 ); // just ignore length, error handling, etc - $p = 0; - list ( $rows, $cols ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8; - - $res = array(); - for ( $i=0; $i<$rows; $i++ ) - for ( $j=0; $j<$cols; $j++ ) - { - list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; - $res[$i][] = substr ( $response, $p, $len ); $p += $len; - } - - $this->_MBPop (); - return $res; - } - - ////////////////////////////////////////////////////////////////////////// - // flush - ////////////////////////////////////////////////////////////////////////// - - function FlushAttributes () - { - $this->_MBPush (); - if (!( $fp = $this->_Connect() )) - { - $this->_MBPop(); - return -1; - } - - $req = pack ( "nnN", SEARCHD_COMMAND_FLUSHATTRS, VER_COMMAND_FLUSHATTRS, 0 ); // len=0 - if ( !( $this->_Send ( $fp, $req, 8 ) ) || - !( $response = $this->_GetResponse ( $fp, VER_COMMAND_FLUSHATTRS ) ) ) - { - $this->_MBPop (); - return -1; - } - - $tag = -1; - if ( strlen($response)==4 ) - list(,$tag) = unpack ( "N*", $response ); - else - $this->_error = "unexpected response length"; - - $this->_MBPop (); - return $tag; - } -} - -// -// $Id: sphinxapi.php 3087 2012-01-30 23:07:35Z shodan $ -// +=8 ) + { + $v = (int)$v; + return pack ( "NN", $v>>32, $v&0xFFFFFFFF ); + } + + // x32, int + if ( is_int($v) ) + return pack ( "NN", $v < 0 ? -1 : 0, $v ); + + // x32, bcmath + if ( function_exists("bcmul") ) + { + if ( bccomp ( $v, 0 ) == -1 ) + $v = bcadd ( "18446744073709551616", $v ); + $h = bcdiv ( $v, "4294967296", 0 ); + $l = bcmod ( $v, "4294967296" ); + return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit + } + + // x32, no-bcmath + $p = max(0, strlen($v) - 13); + $lo = abs((float)substr($v, $p)); + $hi = abs((float)substr($v, 0, $p)); + + $m = $lo + $hi*1316134912.0; // (10 ^ 13) % (1 << 32) = 1316134912 + $q = floor($m/4294967296.0); + $l = $m - ($q*4294967296.0); + $h = $hi*2328.0 + $q; // (10 ^ 13) / (1 << 32) = 2328 + + if ( $v<0 ) + { + if ( $l==0 ) + $h = 4294967296.0 - $h; + else + { + $h = 4294967295.0 - $h; + $l = 4294967296.0 - $l; + } + } + return pack ( "NN", $h, $l ); +} + +/// pack 64-bit unsigned +function sphPackU64 ( $v ) +{ + assert ( is_numeric($v) ); + + // x64 + if ( PHP_INT_SIZE>=8 ) + { + assert ( $v>=0 ); + + // x64, int + if ( is_int($v) ) + return pack ( "NN", $v>>32, $v&0xFFFFFFFF ); + + // x64, bcmath + if ( function_exists("bcmul") ) + { + $h = bcdiv ( $v, 4294967296, 0 ); + $l = bcmod ( $v, 4294967296 ); + return pack ( "NN", $h, $l ); + } + + // x64, no-bcmath + $p = max ( 0, strlen($v) - 13 ); + $lo = (int)substr ( $v, $p ); + $hi = (int)substr ( $v, 0, $p ); + + $m = $lo + $hi*1316134912; + $l = $m % 4294967296; + $h = $hi*2328 + (int)($m/4294967296); + + return pack ( "NN", $h, $l ); + } + + // x32, int + if ( is_int($v) ) + return pack ( "NN", 0, $v ); + + // x32, bcmath + if ( function_exists("bcmul") ) + { + $h = bcdiv ( $v, "4294967296", 0 ); + $l = bcmod ( $v, "4294967296" ); + return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit + } + + // x32, no-bcmath + $p = max(0, strlen($v) - 13); + $lo = (float)substr($v, $p); + $hi = (float)substr($v, 0, $p); + + $m = $lo + $hi*1316134912.0; + $q = floor($m / 4294967296.0); + $l = $m - ($q * 4294967296.0); + $h = $hi*2328.0 + $q; + + return pack ( "NN", $h, $l ); +} + +// unpack 64-bit unsigned +function sphUnpackU64 ( $v ) +{ + list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) ); + + if ( PHP_INT_SIZE>=8 ) + { + if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again + if ( $lo<0 ) $lo += (1<<32); + + // x64, int + if ( $hi<=2147483647 ) + return ($hi<<32) + $lo; + + // x64, bcmath + if ( function_exists("bcmul") ) + return bcadd ( $lo, bcmul ( $hi, "4294967296" ) ); + + // x64, no-bcmath + $C = 100000; + $h = ((int)($hi / $C) << 32) + (int)($lo / $C); + $l = (($hi % $C) << 32) + ($lo % $C); + if ( $l>$C ) + { + $h += (int)($l / $C); + $l = $l % $C; + } + + if ( $h==0 ) + return $l; + return sprintf ( "%d%05d", $h, $l ); + } + + // x32, int + if ( $hi==0 ) + { + if ( $lo>0 ) + return $lo; + return sprintf ( "%u", $lo ); + } + + $hi = sprintf ( "%u", $hi ); + $lo = sprintf ( "%u", $lo ); + + // x32, bcmath + if ( function_exists("bcmul") ) + return bcadd ( $lo, bcmul ( $hi, "4294967296" ) ); + + // x32, no-bcmath + $hi = (float)$hi; + $lo = (float)$lo; + + $q = floor($hi/10000000.0); + $r = $hi - $q*10000000.0; + $m = $lo + $r*4967296.0; + $mq = floor($m/10000000.0); + $l = $m - $mq*10000000.0; + $h = $q*4294967296.0 + $r*429.0 + $mq; + + $h = sprintf ( "%.0f", $h ); + $l = sprintf ( "%07.0f", $l ); + if ( $h=="0" ) + return sprintf( "%.0f", (float)$l ); + return $h . $l; +} + +// unpack 64-bit signed +function sphUnpackI64 ( $v ) +{ + list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) ); + + // x64 + if ( PHP_INT_SIZE>=8 ) + { + if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again + if ( $lo<0 ) $lo += (1<<32); + + return ($hi<<32) + $lo; + } + + // x32, int + if ( $hi==0 ) + { + if ( $lo>0 ) + return $lo; + return sprintf ( "%u", $lo ); + } + // x32, int + elseif ( $hi==-1 ) + { + if ( $lo<0 ) + return $lo; + return sprintf ( "%.0f", $lo - 4294967296.0 ); + } + + $neg = ""; + $c = 0; + if ( $hi<0 ) + { + $hi = ~$hi; + $lo = ~$lo; + $c = 1; + $neg = "-"; + } + + $hi = sprintf ( "%u", $hi ); + $lo = sprintf ( "%u", $lo ); + + // x32, bcmath + if ( function_exists("bcmul") ) + return $neg . bcadd ( bcadd ( $lo, bcmul ( $hi, "4294967296" ) ), $c ); + + // x32, no-bcmath + $hi = (float)$hi; + $lo = (float)$lo; + + $q = floor($hi/10000000.0); + $r = $hi - $q*10000000.0; + $m = $lo + $r*4967296.0; + $mq = floor($m/10000000.0); + $l = $m - $mq*10000000.0 + $c; + $h = $q*4294967296.0 + $r*429.0 + $mq; + if ( $l==10000000 ) + { + $l = 0; + $h += 1; + } + + $h = sprintf ( "%.0f", $h ); + $l = sprintf ( "%07.0f", $l ); + if ( $h=="0" ) + return $neg . sprintf( "%.0f", (float)$l ); + return $neg . $h . $l; +} + + +function sphFixUint ( $value ) +{ + if ( PHP_INT_SIZE>=8 ) + { + // x64 route, workaround broken unpack() in 5.2.2+ + if ( $value<0 ) $value += (1<<32); + return $value; + } + else + { + // x32 route, workaround php signed/unsigned braindamage + return sprintf ( "%u", $value ); + } +} + + +/// sphinx searchd client class +class SphinxClient +{ + var $_host; ///< searchd host (default is "localhost") + var $_port; ///< searchd port (default is 9312) + var $_offset; ///< how many records to seek from result-set start (default is 0) + var $_limit; ///< how many records to return from result-set starting at offset (default is 20) + var $_mode; ///< query matching mode (default is SPH_MATCH_ALL) + var $_weights; ///< per-field weights (default is 1 for all fields) + var $_sort; ///< match sorting mode (default is SPH_SORT_RELEVANCE) + var $_sortby; ///< attribute to sort by (defualt is "") + var $_min_id; ///< min ID to match (default is 0, which means no limit) + var $_max_id; ///< max ID to match (default is 0, which means no limit) + var $_filters; ///< search filters + var $_groupby; ///< group-by attribute name + var $_groupfunc; ///< group-by function (to pre-process group-by attribute value with) + var $_groupsort; ///< group-by sorting clause (to sort groups in result set with) + var $_groupdistinct;///< group-by count-distinct attribute + var $_maxmatches; ///< max matches to retrieve + var $_cutoff; ///< cutoff to stop searching at (default is 0) + var $_retrycount; ///< distributed retries count + var $_retrydelay; ///< distributed retries delay + var $_anchor; ///< geographical anchor point + var $_indexweights; ///< per-index weights + var $_ranker; ///< ranking mode (default is SPH_RANK_PROXIMITY_BM25) + var $_rankexpr; ///< ranking mode expression (for SPH_RANK_EXPR) + var $_maxquerytime; ///< max query time, milliseconds (default is 0, do not limit) + var $_fieldweights; ///< per-field-name weights + var $_overrides; ///< per-query attribute values overrides + var $_select; ///< select-list (attributes or expressions, with optional aliases) + + var $_error; ///< last error message + var $_warning; ///< last warning message + var $_connerror; ///< connection error vs remote error flag + + var $_reqs; ///< requests array for multi-query + var $_mbenc; ///< stored mbstring encoding + var $_arrayresult; ///< whether $result["matches"] should be a hash or an array + var $_timeout; ///< connect timeout + + ///////////////////////////////////////////////////////////////////////////// + // common stuff + ///////////////////////////////////////////////////////////////////////////// + + /// create a new client object and fill defaults + function SphinxClient () + { + // per-client-object settings + $this->_host = "localhost"; + $this->_port = 9312; + $this->_path = false; + $this->_socket = false; + + // per-query settings + $this->_offset = 0; + $this->_limit = 20; + $this->_mode = SPH_MATCH_ALL; + $this->_weights = array (); + $this->_sort = SPH_SORT_RELEVANCE; + $this->_sortby = ""; + $this->_min_id = 0; + $this->_max_id = 0; + $this->_filters = array (); + $this->_groupby = ""; + $this->_groupfunc = SPH_GROUPBY_DAY; + $this->_groupsort = "@group desc"; + $this->_groupdistinct= ""; + $this->_maxmatches = 1000; + $this->_cutoff = 0; + $this->_retrycount = 0; + $this->_retrydelay = 0; + $this->_anchor = array (); + $this->_indexweights= array (); + $this->_ranker = SPH_RANK_PROXIMITY_BM25; + $this->_rankexpr = ""; + $this->_maxquerytime= 0; + $this->_fieldweights= array(); + $this->_overrides = array(); + $this->_select = "*"; + + $this->_error = ""; // per-reply fields (for single-query case) + $this->_warning = ""; + $this->_connerror = false; + + $this->_reqs = array (); // requests storage (for multi-query case) + $this->_mbenc = ""; + $this->_arrayresult = false; + $this->_timeout = 0; + } + + function __destruct() + { + if ( $this->_socket !== false ) + fclose ( $this->_socket ); + } + + /// get last error message (string) + function GetLastError () + { + return $this->_error; + } + + /// get last warning message (string) + function GetLastWarning () + { + return $this->_warning; + } + + /// get last error flag (to tell network connection errors from searchd errors or broken responses) + function IsConnectError() + { + return $this->_connerror; + } + + /// set searchd host name (string) and port (integer) + function SetServer ( $host, $port = 0 ) + { + assert ( is_string($host) ); + if ( $host[0] == '/') + { + $this->_path = 'unix://' . $host; + return; + } + if ( substr ( $host, 0, 7 )=="unix://" ) + { + $this->_path = $host; + return; + } + + assert ( is_int($port) ); + $this->_host = $host; + $this->_port = $port; + $this->_path = ''; + + } + + /// set server connection timeout (0 to remove) + function SetConnectTimeout ( $timeout ) + { + assert ( is_numeric($timeout) ); + $this->_timeout = $timeout; + } + + + function _Send ( $handle, $data, $length ) + { + if ( feof($handle) || fwrite ( $handle, $data, $length ) !== $length ) + { + $this->_error = 'connection unexpectedly closed (timed out?)'; + $this->_connerror = true; + return false; + } + return true; + } + + ///////////////////////////////////////////////////////////////////////////// + + /// enter mbstring workaround mode + function _MBPush () + { + $this->_mbenc = ""; + if ( ini_get ( "mbstring.func_overload" ) & 2 ) + { + $this->_mbenc = mb_internal_encoding(); + mb_internal_encoding ( "latin1" ); + } + } + + /// leave mbstring workaround mode + function _MBPop () + { + if ( $this->_mbenc ) + mb_internal_encoding ( $this->_mbenc ); + } + + /// connect to searchd server + function _Connect () + { + if ( $this->_socket!==false ) + { + // we are in persistent connection mode, so we have a socket + // however, need to check whether it's still alive + if ( !@feof ( $this->_socket ) ) + return $this->_socket; + + // force reopen + $this->_socket = false; + } + + $errno = 0; + $errstr = ""; + $this->_connerror = false; + + if ( $this->_path ) + { + $host = $this->_path; + $port = 0; + } + else + { + $host = $this->_host; + $port = $this->_port; + } + + if ( $this->_timeout<=0 ) + $fp = @fsockopen ( $host, $port, $errno, $errstr ); + else + $fp = @fsockopen ( $host, $port, $errno, $errstr, $this->_timeout ); + + if ( !$fp ) + { + if ( $this->_path ) + $location = $this->_path; + else + $location = "{$this->_host}:{$this->_port}"; + + $errstr = trim ( $errstr ); + $this->_error = "connection to $location failed (errno=$errno, msg=$errstr)"; + $this->_connerror = true; + return false; + } + + // send my version + // this is a subtle part. we must do it before (!) reading back from searchd. + // because otherwise under some conditions (reported on FreeBSD for instance) + // TCP stack could throttle write-write-read pattern because of Nagle. + if ( !$this->_Send ( $fp, pack ( "N", 1 ), 4 ) ) + { + fclose ( $fp ); + $this->_error = "failed to send client protocol version"; + return false; + } + + // check version + list(,$v) = unpack ( "N*", fread ( $fp, 4 ) ); + $v = (int)$v; + if ( $v<1 ) + { + fclose ( $fp ); + $this->_error = "expected searchd protocol version 1+, got version '$v'"; + return false; + } + + return $fp; + } + + /// get and check response packet from searchd server + function _GetResponse ( $fp, $client_ver ) + { + $response = ""; + $len = 0; + + $header = fread ( $fp, 8 ); + if ( strlen($header)==8 ) + { + list ( $status, $ver, $len ) = array_values ( unpack ( "n2a/Nb", $header ) ); + $left = $len; + while ( $left>0 && !feof($fp) ) + { + $chunk = fread ( $fp, min ( 8192, $left ) ); + if ( $chunk ) + { + $response .= $chunk; + $left -= strlen($chunk); + } + } + } + if ( $this->_socket === false ) + fclose ( $fp ); + + // check response + $read = strlen ( $response ); + if ( !$response || $read!=$len ) + { + $this->_error = $len + ? "failed to read searchd response (status=$status, ver=$ver, len=$len, read=$read)" + : "received zero-sized searchd response"; + return false; + } + + // check status + if ( $status==SEARCHD_WARNING ) + { + list(,$wlen) = unpack ( "N*", substr ( $response, 0, 4 ) ); + $this->_warning = substr ( $response, 4, $wlen ); + return substr ( $response, 4+$wlen ); + } + if ( $status==SEARCHD_ERROR ) + { + $this->_error = "searchd error: " . substr ( $response, 4 ); + return false; + } + if ( $status==SEARCHD_RETRY ) + { + $this->_error = "temporary searchd error: " . substr ( $response, 4 ); + return false; + } + if ( $status!=SEARCHD_OK ) + { + $this->_error = "unknown status code '$status'"; + return false; + } + + // check version + if ( $ver<$client_ver ) + { + $this->_warning = sprintf ( "searchd command v.%d.%d older than client's v.%d.%d, some options might not work", + $ver>>8, $ver&0xff, $client_ver>>8, $client_ver&0xff ); + } + + return $response; + } + + ///////////////////////////////////////////////////////////////////////////// + // searching + ///////////////////////////////////////////////////////////////////////////// + + /// set offset and count into result set, + /// and optionally set max-matches and cutoff limits + function SetLimits ( $offset, $limit, $max=0, $cutoff=0 ) + { + assert ( is_int($offset) ); + assert ( is_int($limit) ); + assert ( $offset>=0 ); + assert ( $limit>0 ); + assert ( $max>=0 ); + $this->_offset = $offset; + $this->_limit = $limit; + if ( $max>0 ) + $this->_maxmatches = $max; + if ( $cutoff>0 ) + $this->_cutoff = $cutoff; + } + + /// set maximum query time, in milliseconds, per-index + /// integer, 0 means "do not limit" + function SetMaxQueryTime ( $max ) + { + assert ( is_int($max) ); + assert ( $max>=0 ); + $this->_maxquerytime = $max; + } + + /// set matching mode + function SetMatchMode ( $mode ) + { + assert ( $mode==SPH_MATCH_ALL + || $mode==SPH_MATCH_ANY + || $mode==SPH_MATCH_PHRASE + || $mode==SPH_MATCH_BOOLEAN + || $mode==SPH_MATCH_EXTENDED + || $mode==SPH_MATCH_FULLSCAN + || $mode==SPH_MATCH_EXTENDED2 ); + $this->_mode = $mode; + } + + /// set ranking mode + function SetRankingMode ( $ranker, $rankexpr="" ) + { + assert ( $ranker>=0 && $ranker_ranker = $ranker; + $this->_rankexpr = $rankexpr; + } + + /// set matches sorting mode + function SetSortMode ( $mode, $sortby="" ) + { + assert ( + $mode==SPH_SORT_RELEVANCE || + $mode==SPH_SORT_ATTR_DESC || + $mode==SPH_SORT_ATTR_ASC || + $mode==SPH_SORT_TIME_SEGMENTS || + $mode==SPH_SORT_EXTENDED || + $mode==SPH_SORT_EXPR ); + assert ( is_string($sortby) ); + assert ( $mode==SPH_SORT_RELEVANCE || strlen($sortby)>0 ); + + $this->_sort = $mode; + $this->_sortby = $sortby; + } + + /// bind per-field weights by order + /// DEPRECATED; use SetFieldWeights() instead + function SetWeights ( $weights ) + { + assert ( is_array($weights) ); + foreach ( $weights as $weight ) + assert ( is_int($weight) ); + + $this->_weights = $weights; + } + + /// bind per-field weights by name + function SetFieldWeights ( $weights ) + { + assert ( is_array($weights) ); + foreach ( $weights as $name=>$weight ) + { + assert ( is_string($name) ); + assert ( is_int($weight) ); + } + $this->_fieldweights = $weights; + } + + /// bind per-index weights by name + function SetIndexWeights ( $weights ) + { + assert ( is_array($weights) ); + foreach ( $weights as $index=>$weight ) + { + assert ( is_string($index) ); + assert ( is_int($weight) ); + } + $this->_indexweights = $weights; + } + + /// set IDs range to match + /// only match records if document ID is beetwen $min and $max (inclusive) + function SetIDRange ( $min, $max ) + { + assert ( is_numeric($min) ); + assert ( is_numeric($max) ); + assert ( $min<=$max ); + $this->_min_id = $min; + $this->_max_id = $max; + } + + /// set values set filter + /// only match records where $attribute value is in given set + function SetFilter ( $attribute, $values, $exclude=false ) + { + assert ( is_string($attribute) ); + assert ( is_array($values) ); + assert ( count($values) ); + + if ( is_array($values) && count($values) ) + { + foreach ( $values as $value ) + assert ( is_numeric($value) ); + + $this->_filters[] = array ( "type"=>SPH_FILTER_VALUES, "attr"=>$attribute, "exclude"=>$exclude, "values"=>$values ); + } + } + + /// set range filter + /// only match records if $attribute value is beetwen $min and $max (inclusive) + function SetFilterRange ( $attribute, $min, $max, $exclude=false ) + { + assert ( is_string($attribute) ); + assert ( is_numeric($min) ); + assert ( is_numeric($max) ); + assert ( $min<=$max ); + + $this->_filters[] = array ( "type"=>SPH_FILTER_RANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max ); + } + + /// set float range filter + /// only match records if $attribute value is beetwen $min and $max (inclusive) + function SetFilterFloatRange ( $attribute, $min, $max, $exclude=false ) + { + assert ( is_string($attribute) ); + assert ( is_float($min) ); + assert ( is_float($max) ); + assert ( $min<=$max ); + + $this->_filters[] = array ( "type"=>SPH_FILTER_FLOATRANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max ); + } + + /// setup anchor point for geosphere distance calculations + /// required to use @geodist in filters and sorting + /// latitude and longitude must be in radians + function SetGeoAnchor ( $attrlat, $attrlong, $lat, $long ) + { + assert ( is_string($attrlat) ); + assert ( is_string($attrlong) ); + assert ( is_float($lat) ); + assert ( is_float($long) ); + + $this->_anchor = array ( "attrlat"=>$attrlat, "attrlong"=>$attrlong, "lat"=>$lat, "long"=>$long ); + } + + /// set grouping attribute and function + function SetGroupBy ( $attribute, $func, $groupsort="@group desc" ) + { + assert ( is_string($attribute) ); + assert ( is_string($groupsort) ); + assert ( $func==SPH_GROUPBY_DAY + || $func==SPH_GROUPBY_WEEK + || $func==SPH_GROUPBY_MONTH + || $func==SPH_GROUPBY_YEAR + || $func==SPH_GROUPBY_ATTR + || $func==SPH_GROUPBY_ATTRPAIR ); + + $this->_groupby = $attribute; + $this->_groupfunc = $func; + $this->_groupsort = $groupsort; + } + + /// set count-distinct attribute for group-by queries + function SetGroupDistinct ( $attribute ) + { + assert ( is_string($attribute) ); + $this->_groupdistinct = $attribute; + } + + /// set distributed retries count and delay + function SetRetries ( $count, $delay=0 ) + { + assert ( is_int($count) && $count>=0 ); + assert ( is_int($delay) && $delay>=0 ); + $this->_retrycount = $count; + $this->_retrydelay = $delay; + } + + /// set result set format (hash or array; hash by default) + /// PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs + function SetArrayResult ( $arrayresult ) + { + assert ( is_bool($arrayresult) ); + $this->_arrayresult = $arrayresult; + } + + /// set attribute values override + /// there can be only one override per attribute + /// $values must be a hash that maps document IDs to attribute values + function SetOverride ( $attrname, $attrtype, $values ) + { + assert ( is_string ( $attrname ) ); + assert ( in_array ( $attrtype, array ( SPH_ATTR_INTEGER, SPH_ATTR_TIMESTAMP, SPH_ATTR_BOOL, SPH_ATTR_FLOAT, SPH_ATTR_BIGINT ) ) ); + assert ( is_array ( $values ) ); + + $this->_overrides[$attrname] = array ( "attr"=>$attrname, "type"=>$attrtype, "values"=>$values ); + } + + /// set select-list (attributes or expressions), SQL-like syntax + function SetSelect ( $select ) + { + assert ( is_string ( $select ) ); + $this->_select = $select; + } + + ////////////////////////////////////////////////////////////////////////////// + + /// clear all filters (for multi-queries) + function ResetFilters () + { + $this->_filters = array(); + $this->_anchor = array(); + } + + /// clear groupby settings (for multi-queries) + function ResetGroupBy () + { + $this->_groupby = ""; + $this->_groupfunc = SPH_GROUPBY_DAY; + $this->_groupsort = "@group desc"; + $this->_groupdistinct= ""; + } + + /// clear all attribute value overrides (for multi-queries) + function ResetOverrides () + { + $this->_overrides = array (); + } + + ////////////////////////////////////////////////////////////////////////////// + + /// connect to searchd server, run given search query through given indexes, + /// and return the search results + function Query ( $query, $index="*", $comment="" ) + { + assert ( empty($this->_reqs) ); + + $this->AddQuery ( $query, $index, $comment ); + $results = $this->RunQueries (); + $this->_reqs = array (); // just in case it failed too early + + if ( !is_array($results) ) + return false; // probably network error; error message should be already filled + + $this->_error = $results[0]["error"]; + $this->_warning = $results[0]["warning"]; + if ( $results[0]["status"]==SEARCHD_ERROR ) + return false; + else + return $results[0]; + } + + /// helper to pack floats in network byte order + function _PackFloat ( $f ) + { + $t1 = pack ( "f", $f ); // machine order + list(,$t2) = unpack ( "L*", $t1 ); // int in machine order + return pack ( "N", $t2 ); + } + + /// add query to multi-query batch + /// returns index into results array from RunQueries() call + function AddQuery ( $query, $index="*", $comment="" ) + { + // mbstring workaround + $this->_MBPush (); + + // build request + $req = pack ( "NNNN", $this->_offset, $this->_limit, $this->_mode, $this->_ranker ); + if ( $this->_ranker==SPH_RANK_EXPR ) + $req .= pack ( "N", strlen($this->_rankexpr) ) . $this->_rankexpr; + $req .= pack ( "N", $this->_sort ); // (deprecated) sort mode + $req .= pack ( "N", strlen($this->_sortby) ) . $this->_sortby; + $req .= pack ( "N", strlen($query) ) . $query; // query itself + $req .= pack ( "N", count($this->_weights) ); // weights + foreach ( $this->_weights as $weight ) + $req .= pack ( "N", (int)$weight ); + $req .= pack ( "N", strlen($index) ) . $index; // indexes + $req .= pack ( "N", 1 ); // id64 range marker + $req .= sphPackU64 ( $this->_min_id ) . sphPackU64 ( $this->_max_id ); // id64 range + + // filters + $req .= pack ( "N", count($this->_filters) ); + foreach ( $this->_filters as $filter ) + { + $req .= pack ( "N", strlen($filter["attr"]) ) . $filter["attr"]; + $req .= pack ( "N", $filter["type"] ); + switch ( $filter["type"] ) + { + case SPH_FILTER_VALUES: + $req .= pack ( "N", count($filter["values"]) ); + foreach ( $filter["values"] as $value ) + $req .= sphPackI64 ( $value ); + break; + + case SPH_FILTER_RANGE: + $req .= sphPackI64 ( $filter["min"] ) . sphPackI64 ( $filter["max"] ); + break; + + case SPH_FILTER_FLOATRANGE: + $req .= $this->_PackFloat ( $filter["min"] ) . $this->_PackFloat ( $filter["max"] ); + break; + + default: + assert ( 0 && "internal error: unhandled filter type" ); + } + $req .= pack ( "N", $filter["exclude"] ); + } + + // group-by clause, max-matches count, group-sort clause, cutoff count + $req .= pack ( "NN", $this->_groupfunc, strlen($this->_groupby) ) . $this->_groupby; + $req .= pack ( "N", $this->_maxmatches ); + $req .= pack ( "N", strlen($this->_groupsort) ) . $this->_groupsort; + $req .= pack ( "NNN", $this->_cutoff, $this->_retrycount, $this->_retrydelay ); + $req .= pack ( "N", strlen($this->_groupdistinct) ) . $this->_groupdistinct; + + // anchor point + if ( empty($this->_anchor) ) + { + $req .= pack ( "N", 0 ); + } else + { + $a =& $this->_anchor; + $req .= pack ( "N", 1 ); + $req .= pack ( "N", strlen($a["attrlat"]) ) . $a["attrlat"]; + $req .= pack ( "N", strlen($a["attrlong"]) ) . $a["attrlong"]; + $req .= $this->_PackFloat ( $a["lat"] ) . $this->_PackFloat ( $a["long"] ); + } + + // per-index weights + $req .= pack ( "N", count($this->_indexweights) ); + foreach ( $this->_indexweights as $idx=>$weight ) + $req .= pack ( "N", strlen($idx) ) . $idx . pack ( "N", $weight ); + + // max query time + $req .= pack ( "N", $this->_maxquerytime ); + + // per-field weights + $req .= pack ( "N", count($this->_fieldweights) ); + foreach ( $this->_fieldweights as $field=>$weight ) + $req .= pack ( "N", strlen($field) ) . $field . pack ( "N", $weight ); + + // comment + $req .= pack ( "N", strlen($comment) ) . $comment; + + // attribute overrides + $req .= pack ( "N", count($this->_overrides) ); + foreach ( $this->_overrides as $key => $entry ) + { + $req .= pack ( "N", strlen($entry["attr"]) ) . $entry["attr"]; + $req .= pack ( "NN", $entry["type"], count($entry["values"]) ); + foreach ( $entry["values"] as $id=>$val ) + { + assert ( is_numeric($id) ); + assert ( is_numeric($val) ); + + $req .= sphPackU64 ( $id ); + switch ( $entry["type"] ) + { + case SPH_ATTR_FLOAT: $req .= $this->_PackFloat ( $val ); break; + case SPH_ATTR_BIGINT: $req .= sphPackI64 ( $val ); break; + default: $req .= pack ( "N", $val ); break; + } + } + } + + // select-list + $req .= pack ( "N", strlen($this->_select) ) . $this->_select; + + // mbstring workaround + $this->_MBPop (); + + // store request to requests array + $this->_reqs[] = $req; + return count($this->_reqs)-1; + } + + /// connect to searchd, run queries batch, and return an array of result sets + function RunQueries () + { + if ( empty($this->_reqs) ) + { + $this->_error = "no queries defined, issue AddQuery() first"; + return false; + } + + // mbstring workaround + $this->_MBPush (); + + if (!( $fp = $this->_Connect() )) + { + $this->_MBPop (); + return false; + } + + // send query, get response + $nreqs = count($this->_reqs); + $req = join ( "", $this->_reqs ); + $len = 8+strlen($req); + $req = pack ( "nnNNN", SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, 0, $nreqs ) . $req; // add header + + if ( !( $this->_Send ( $fp, $req, $len+8 ) ) || + !( $response = $this->_GetResponse ( $fp, VER_COMMAND_SEARCH ) ) ) + { + $this->_MBPop (); + return false; + } + + // query sent ok; we can reset reqs now + $this->_reqs = array (); + + // parse and return response + return $this->_ParseSearchResponse ( $response, $nreqs ); + } + + /// parse and return search query (or queries) response + function _ParseSearchResponse ( $response, $nreqs ) + { + $p = 0; // current position + $max = strlen($response); // max position for checks, to protect against broken responses + + $results = array (); + for ( $ires=0; $ires<$nreqs && $p<$max; $ires++ ) + { + $results[] = array(); + $result =& $results[$ires]; + + $result["error"] = ""; + $result["warning"] = ""; + + // extract status + list(,$status) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $result["status"] = $status; + if ( $status!=SEARCHD_OK ) + { + list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $message = substr ( $response, $p, $len ); $p += $len; + + if ( $status==SEARCHD_WARNING ) + { + $result["warning"] = $message; + } else + { + $result["error"] = $message; + continue; + } + } + + // read schema + $fields = array (); + $attrs = array (); + + list(,$nfields) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + while ( $nfields-->0 && $p<$max ) + { + list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $fields[] = substr ( $response, $p, $len ); $p += $len; + } + $result["fields"] = $fields; + + list(,$nattrs) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + while ( $nattrs-->0 && $p<$max ) + { + list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $attr = substr ( $response, $p, $len ); $p += $len; + list(,$type) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $attrs[$attr] = $type; + } + $result["attrs"] = $attrs; + + // read match count + list(,$count) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + list(,$id64) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + + // read matches + $idx = -1; + while ( $count-->0 && $p<$max ) + { + // index into result array + $idx++; + + // parse document id and weight + if ( $id64 ) + { + $doc = sphUnpackU64 ( substr ( $response, $p, 8 ) ); $p += 8; + list(,$weight) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + } + else + { + list ( $doc, $weight ) = array_values ( unpack ( "N*N*", + substr ( $response, $p, 8 ) ) ); + $p += 8; + $doc = sphFixUint($doc); + } + $weight = sprintf ( "%u", $weight ); + + // create match entry + if ( $this->_arrayresult ) + $result["matches"][$idx] = array ( "id"=>$doc, "weight"=>$weight ); + else + $result["matches"][$doc]["weight"] = $weight; + + // parse and create attributes + $attrvals = array (); + foreach ( $attrs as $attr=>$type ) + { + // handle 64bit ints + if ( $type==SPH_ATTR_BIGINT ) + { + $attrvals[$attr] = sphUnpackI64 ( substr ( $response, $p, 8 ) ); $p += 8; + continue; + } + + // handle floats + if ( $type==SPH_ATTR_FLOAT ) + { + list(,$uval) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + list(,$fval) = unpack ( "f*", pack ( "L", $uval ) ); + $attrvals[$attr] = $fval; + continue; + } + + // handle everything else as unsigned ints + list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + if ( $type==SPH_ATTR_MULTI ) + { + $attrvals[$attr] = array (); + $nvalues = $val; + while ( $nvalues-->0 && $p<$max ) + { + list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $attrvals[$attr][] = sphFixUint($val); + } + } else if ( $type==SPH_ATTR_MULTI64 ) + { + $attrvals[$attr] = array (); + $nvalues = $val; + while ( $nvalues>0 && $p<$max ) + { + $attrvals[$attr][] = sphUnpackU64 ( substr ( $response, $p, 8 ) ); $p += 8; + $nvalues -= 2; + } + } else if ( $type==SPH_ATTR_STRING ) + { + $attrvals[$attr] = substr ( $response, $p, $val ); + $p += $val; + } else + { + $attrvals[$attr] = sphFixUint($val); + } + } + + if ( $this->_arrayresult ) + $result["matches"][$idx]["attrs"] = $attrvals; + else + $result["matches"][$doc]["attrs"] = $attrvals; + } + + list ( $total, $total_found, $msecs, $words ) = + array_values ( unpack ( "N*N*N*N*", substr ( $response, $p, 16 ) ) ); + $result["total"] = sprintf ( "%u", $total ); + $result["total_found"] = sprintf ( "%u", $total_found ); + $result["time"] = sprintf ( "%.3f", $msecs/1000 ); + $p += 16; + + while ( $words-->0 && $p<$max ) + { + list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $word = substr ( $response, $p, $len ); $p += $len; + list ( $docs, $hits ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8; + $result["words"][$word] = array ( + "docs"=>sprintf ( "%u", $docs ), + "hits"=>sprintf ( "%u", $hits ) ); + } + } + + $this->_MBPop (); + return $results; + } + + ///////////////////////////////////////////////////////////////////////////// + // excerpts generation + ///////////////////////////////////////////////////////////////////////////// + + /// connect to searchd server, and generate exceprts (snippets) + /// of given documents for given query. returns false on failure, + /// an array of snippets on success + function BuildExcerpts ( $docs, $index, $words, $opts=array() ) + { + assert ( is_array($docs) ); + assert ( is_string($index) ); + assert ( is_string($words) ); + assert ( is_array($opts) ); + + $this->_MBPush (); + + if (!( $fp = $this->_Connect() )) + { + $this->_MBPop(); + return false; + } + + ///////////////// + // fixup options + ///////////////// + + if ( !isset($opts["before_match"]) ) $opts["before_match"] = ""; + if ( !isset($opts["after_match"]) ) $opts["after_match"] = ""; + if ( !isset($opts["chunk_separator"]) ) $opts["chunk_separator"] = " ... "; + if ( !isset($opts["limit"]) ) $opts["limit"] = 256; + if ( !isset($opts["limit_passages"]) ) $opts["limit_passages"] = 0; + if ( !isset($opts["limit_words"]) ) $opts["limit_words"] = 0; + if ( !isset($opts["around"]) ) $opts["around"] = 5; + if ( !isset($opts["exact_phrase"]) ) $opts["exact_phrase"] = false; + if ( !isset($opts["single_passage"]) ) $opts["single_passage"] = false; + if ( !isset($opts["use_boundaries"]) ) $opts["use_boundaries"] = false; + if ( !isset($opts["weight_order"]) ) $opts["weight_order"] = false; + if ( !isset($opts["query_mode"]) ) $opts["query_mode"] = false; + if ( !isset($opts["force_all_words"]) ) $opts["force_all_words"] = false; + if ( !isset($opts["start_passage_id"]) ) $opts["start_passage_id"] = 1; + if ( !isset($opts["load_files"]) ) $opts["load_files"] = false; + if ( !isset($opts["html_strip_mode"]) ) $opts["html_strip_mode"] = "index"; + if ( !isset($opts["allow_empty"]) ) $opts["allow_empty"] = false; + if ( !isset($opts["passage_boundary"]) ) $opts["passage_boundary"] = "none"; + if ( !isset($opts["emit_zones"]) ) $opts["emit_zones"] = false; + if ( !isset($opts["load_files_scattered"]) ) $opts["load_files_scattered"] = false; + + + ///////////////// + // build request + ///////////////// + + // v.1.2 req + $flags = 1; // remove spaces + if ( $opts["exact_phrase"] ) $flags |= 2; + if ( $opts["single_passage"] ) $flags |= 4; + if ( $opts["use_boundaries"] ) $flags |= 8; + if ( $opts["weight_order"] ) $flags |= 16; + if ( $opts["query_mode"] ) $flags |= 32; + if ( $opts["force_all_words"] ) $flags |= 64; + if ( $opts["load_files"] ) $flags |= 128; + if ( $opts["allow_empty"] ) $flags |= 256; + if ( $opts["emit_zones"] ) $flags |= 512; + if ( $opts["load_files_scattered"] ) $flags |= 1024; + $req = pack ( "NN", 0, $flags ); // mode=0, flags=$flags + $req .= pack ( "N", strlen($index) ) . $index; // req index + $req .= pack ( "N", strlen($words) ) . $words; // req words + + // options + $req .= pack ( "N", strlen($opts["before_match"]) ) . $opts["before_match"]; + $req .= pack ( "N", strlen($opts["after_match"]) ) . $opts["after_match"]; + $req .= pack ( "N", strlen($opts["chunk_separator"]) ) . $opts["chunk_separator"]; + $req .= pack ( "NN", (int)$opts["limit"], (int)$opts["around"] ); + $req .= pack ( "NNN", (int)$opts["limit_passages"], (int)$opts["limit_words"], (int)$opts["start_passage_id"] ); // v.1.2 + $req .= pack ( "N", strlen($opts["html_strip_mode"]) ) . $opts["html_strip_mode"]; + $req .= pack ( "N", strlen($opts["passage_boundary"]) ) . $opts["passage_boundary"]; + + // documents + $req .= pack ( "N", count($docs) ); + foreach ( $docs as $doc ) + { + assert ( is_string($doc) ); + $req .= pack ( "N", strlen($doc) ) . $doc; + } + + //////////////////////////// + // send query, get response + //////////////////////////// + + $len = strlen($req); + $req = pack ( "nnN", SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, $len ) . $req; // add header + if ( !( $this->_Send ( $fp, $req, $len+8 ) ) || + !( $response = $this->_GetResponse ( $fp, VER_COMMAND_EXCERPT ) ) ) + { + $this->_MBPop (); + return false; + } + + ////////////////// + // parse response + ////////////////// + + $pos = 0; + $res = array (); + $rlen = strlen($response); + for ( $i=0; $i $rlen ) + { + $this->_error = "incomplete reply"; + $this->_MBPop (); + return false; + } + $res[] = $len ? substr ( $response, $pos, $len ) : ""; + $pos += $len; + } + + $this->_MBPop (); + return $res; + } + + + ///////////////////////////////////////////////////////////////////////////// + // keyword generation + ///////////////////////////////////////////////////////////////////////////// + + /// connect to searchd server, and generate keyword list for a given query + /// returns false on failure, + /// an array of words on success + function BuildKeywords ( $query, $index, $hits ) + { + assert ( is_string($query) ); + assert ( is_string($index) ); + assert ( is_bool($hits) ); + + $this->_MBPush (); + + if (!( $fp = $this->_Connect() )) + { + $this->_MBPop(); + return false; + } + + ///////////////// + // build request + ///////////////// + + // v.1.0 req + $req = pack ( "N", strlen($query) ) . $query; // req query + $req .= pack ( "N", strlen($index) ) . $index; // req index + $req .= pack ( "N", (int)$hits ); + + //////////////////////////// + // send query, get response + //////////////////////////// + + $len = strlen($req); + $req = pack ( "nnN", SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, $len ) . $req; // add header + if ( !( $this->_Send ( $fp, $req, $len+8 ) ) || + !( $response = $this->_GetResponse ( $fp, VER_COMMAND_KEYWORDS ) ) ) + { + $this->_MBPop (); + return false; + } + + ////////////////// + // parse response + ////////////////// + + $pos = 0; + $res = array (); + $rlen = strlen($response); + list(,$nwords) = unpack ( "N*", substr ( $response, $pos, 4 ) ); + $pos += 4; + for ( $i=0; $i<$nwords; $i++ ) + { + list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) ); $pos += 4; + $tokenized = $len ? substr ( $response, $pos, $len ) : ""; + $pos += $len; + + list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) ); $pos += 4; + $normalized = $len ? substr ( $response, $pos, $len ) : ""; + $pos += $len; + + $res[] = array ( "tokenized"=>$tokenized, "normalized"=>$normalized ); + + if ( $hits ) + { + list($ndocs,$nhits) = array_values ( unpack ( "N*N*", substr ( $response, $pos, 8 ) ) ); + $pos += 8; + $res [$i]["docs"] = $ndocs; + $res [$i]["hits"] = $nhits; + } + + if ( $pos > $rlen ) + { + $this->_error = "incomplete reply"; + $this->_MBPop (); + return false; + } + } + + $this->_MBPop (); + return $res; + } + + function EscapeString ( $string ) + { + $from = array ( '\\', '(',')','|','-','!','@','~','"','&', '/', '^', '$', '=' ); + $to = array ( '\\\\', '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/', '\^', '\$', '\=' ); + + return str_replace ( $from, $to, $string ); + } + + ///////////////////////////////////////////////////////////////////////////// + // attribute updates + ///////////////////////////////////////////////////////////////////////////// + + /// batch update given attributes in given rows in given indexes + /// returns amount of updated documents (0 or more) on success, or -1 on failure + function UpdateAttributes ( $index, $attrs, $values, $mva=false ) + { + // verify everything + assert ( is_string($index) ); + assert ( is_bool($mva) ); + + assert ( is_array($attrs) ); + foreach ( $attrs as $attr ) + assert ( is_string($attr) ); + + assert ( is_array($values) ); + foreach ( $values as $id=>$entry ) + { + assert ( is_numeric($id) ); + assert ( is_array($entry) ); + assert ( count($entry)==count($attrs) ); + foreach ( $entry as $v ) + { + if ( $mva ) + { + assert ( is_array($v) ); + foreach ( $v as $vv ) + assert ( is_int($vv) ); + } else + assert ( is_int($v) ); + } + } + + // build request + $this->_MBPush (); + $req = pack ( "N", strlen($index) ) . $index; + + $req .= pack ( "N", count($attrs) ); + foreach ( $attrs as $attr ) + { + $req .= pack ( "N", strlen($attr) ) . $attr; + $req .= pack ( "N", $mva ? 1 : 0 ); + } + + $req .= pack ( "N", count($values) ); + foreach ( $values as $id=>$entry ) + { + $req .= sphPackU64 ( $id ); + foreach ( $entry as $v ) + { + $req .= pack ( "N", $mva ? count($v) : $v ); + if ( $mva ) + foreach ( $v as $vv ) + $req .= pack ( "N", $vv ); + } + } + + // connect, send query, get response + if (!( $fp = $this->_Connect() )) + { + $this->_MBPop (); + return -1; + } + + $len = strlen($req); + $req = pack ( "nnN", SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, $len ) . $req; // add header + if ( !$this->_Send ( $fp, $req, $len+8 ) ) + { + $this->_MBPop (); + return -1; + } + + if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_UPDATE ) )) + { + $this->_MBPop (); + return -1; + } + + // parse response + list(,$updated) = unpack ( "N*", substr ( $response, 0, 4 ) ); + $this->_MBPop (); + return $updated; + } + + ///////////////////////////////////////////////////////////////////////////// + // persistent connections + ///////////////////////////////////////////////////////////////////////////// + + function Open() + { + if ( $this->_socket !== false ) + { + $this->_error = 'already connected'; + return false; + } + if ( !$fp = $this->_Connect() ) + return false; + + // command, command version = 0, body length = 4, body = 1 + $req = pack ( "nnNN", SEARCHD_COMMAND_PERSIST, 0, 4, 1 ); + if ( !$this->_Send ( $fp, $req, 12 ) ) + return false; + + $this->_socket = $fp; + return true; + } + + function Close() + { + if ( $this->_socket === false ) + { + $this->_error = 'not connected'; + return false; + } + + fclose ( $this->_socket ); + $this->_socket = false; + + return true; + } + + ////////////////////////////////////////////////////////////////////////// + // status + ////////////////////////////////////////////////////////////////////////// + + function Status () + { + $this->_MBPush (); + if (!( $fp = $this->_Connect() )) + { + $this->_MBPop(); + return false; + } + + $req = pack ( "nnNN", SEARCHD_COMMAND_STATUS, VER_COMMAND_STATUS, 4, 1 ); // len=4, body=1 + if ( !( $this->_Send ( $fp, $req, 12 ) ) || + !( $response = $this->_GetResponse ( $fp, VER_COMMAND_STATUS ) ) ) + { + $this->_MBPop (); + return false; + } + + $res = substr ( $response, 4 ); // just ignore length, error handling, etc + $p = 0; + list ( $rows, $cols ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8; + + $res = array(); + for ( $i=0; $i<$rows; $i++ ) + for ( $j=0; $j<$cols; $j++ ) + { + list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $res[$i][] = substr ( $response, $p, $len ); $p += $len; + } + + $this->_MBPop (); + return $res; + } + + ////////////////////////////////////////////////////////////////////////// + // flush + ////////////////////////////////////////////////////////////////////////// + + function FlushAttributes () + { + $this->_MBPush (); + if (!( $fp = $this->_Connect() )) + { + $this->_MBPop(); + return -1; + } + + $req = pack ( "nnN", SEARCHD_COMMAND_FLUSHATTRS, VER_COMMAND_FLUSHATTRS, 0 ); // len=0 + if ( !( $this->_Send ( $fp, $req, 8 ) ) || + !( $response = $this->_GetResponse ( $fp, VER_COMMAND_FLUSHATTRS ) ) ) + { + $this->_MBPop (); + return -1; + } + + $tag = -1; + if ( strlen($response)==4 ) + list(,$tag) = unpack ( "N*", $response ); + else + $this->_error = "unexpected response length"; + + $this->_MBPop (); + return $tag; + } +} + +// +// $Id: sphinxapi.php 3087 2012-01-30 23:07:35Z shodan $ +// -- cgit v1.2.1 From 0446886f91376c258df3729287824e505ff788b3 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Mon, 10 Dec 2012 13:35:15 -0500 Subject: [feature/template-events] Pass arguments in correct order. Thank you imkingdavid. PHPBB3-9550 --- phpBB/includes/style/style.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/style/style.php b/phpBB/includes/style/style.php index 7c91edd034..1b2e756064 100644 --- a/phpBB/includes/style/style.php +++ b/phpBB/includes/style/style.php @@ -106,7 +106,7 @@ class phpbb_style $paths[] = $this->get_style_path($name); } - return $this->set_custom_style($style_path, $names, $paths); + return $this->set_custom_style($style_path, $paths, $paths); } /** -- cgit v1.2.1 From a0211ff2eb3a61f627d4ac4732c6ab68c2529d9e Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Mon, 10 Dec 2012 13:35:15 -0500 Subject: [feature/template-events] Pass arguments in correct order. Thank you imkingdavid. PHPBB3-9550 --- phpBB/includes/style/style.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/style/style.php b/phpBB/includes/style/style.php index 7c91edd034..4703c3a219 100644 --- a/phpBB/includes/style/style.php +++ b/phpBB/includes/style/style.php @@ -106,7 +106,7 @@ class phpbb_style $paths[] = $this->get_style_path($name); } - return $this->set_custom_style($style_path, $names, $paths); + return $this->set_custom_style($style_path, $paths, $names); } /** -- cgit v1.2.1 From 7ce009f2e29045b2bd29dd670c900b79f7f3bd70 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Wed, 12 Dec 2012 12:46:20 +0100 Subject: [ticket/10954] Mark forums read without popup or page refresh PHPBB3-10954 --- phpBB/includes/functions_display.php | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions_display.php b/phpBB/includes/functions_display.php index 73129803ee..9745d493c4 100644 --- a/phpBB/includes/functions_display.php +++ b/phpBB/includes/functions_display.php @@ -61,10 +61,23 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod { markread('all', false, false, request_var('mark_time', 0)); - trigger_error( - $user->lang['FORUMS_MARKED'] . '

' . - sprintf($user->lang['RETURN_INDEX'], '', '') - ); + if (!$request->is_ajax()) + { + trigger_error( + $user->lang['FORUMS_MARKED'] . '

' . + sprintf($user->lang['RETURN_INDEX'], '', '') + ); + } + else + { + // Tell the ajax script what language vars need to be replaced + $data = array( + 'NO_UNREAD_POSTS' => $user->lang['NO_UNREAD_POSTS'], + 'UNREAD_POSTS' => $user->lang['UNREAD_POSTS'] + ); + $json_response = new phpbb_json_response(); + $json_response->send($data); + } } else { @@ -313,7 +326,20 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod $message = sprintf($user->lang['RETURN_FORUM'], '', ''); meta_refresh(3, $redirect); - trigger_error($user->lang['FORUMS_MARKED'] . '

' . $message); + if (!$request->is_ajax()) + { + trigger_error($user->lang['FORUMS_MARKED'] . '

' . $message); + } + else + { + // Tell the ajax script what language vars need to be replaced + $data = array( + 'NO_UNREAD_POSTS' => $user->lang['NO_UNREAD_POSTS'], + 'UNREAD_POSTS' => $user->lang['UNREAD_POSTS'] + ); + $json_response = new phpbb_json_response(); + $json_response->send($data); + } } else { -- cgit v1.2.1 From 4980d8b01150593175274dad45890544f56075d6 Mon Sep 17 00:00:00 2001 From: David King Date: Sun, 2 Sep 2012 23:44:13 -0400 Subject: [ticket/11088] Move style, extension and language pack management to customise Instead of being separated, these related ACP modules are now grouped intuitively. PHPBB3-11088 --- phpBB/includes/acp/info/acp_extensions.php | 4 ++-- phpBB/includes/acp/info/acp_language.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/acp/info/acp_extensions.php b/phpBB/includes/acp/info/acp_extensions.php index f5953fb1dd..03d7059165 100644 --- a/phpBB/includes/acp/info/acp_extensions.php +++ b/phpBB/includes/acp/info/acp_extensions.php @@ -16,10 +16,10 @@ class acp_extensions_info { return array( 'filename' => 'acp_extensions', - 'title' => 'ACP_EXTENSIONS', + 'title' => 'ACP_EXTENSIONS_MANAGEMENT', 'version' => '1.0.0', 'modes' => array( - 'main' => array('title' => 'ACP_EXTENSIONS', 'auth' => 'acl_a_extensions', 'cat' => array('ACP_GENERAL_TASKS')), + 'main' => array('title' => 'ACP_EXTENSIONS', 'auth' => 'acl_a_extensions', 'cat' => array('ACP_EXTENSIONS_MANAGEMENT')), ), ); } diff --git a/phpBB/includes/acp/info/acp_language.php b/phpBB/includes/acp/info/acp_language.php index 85dfb119ea..7f33a22fa6 100644 --- a/phpBB/includes/acp/info/acp_language.php +++ b/phpBB/includes/acp/info/acp_language.php @@ -19,7 +19,7 @@ class acp_language_info 'title' => 'ACP_LANGUAGE', 'version' => '1.0.0', 'modes' => array( - 'lang_packs' => array('title' => 'ACP_LANGUAGE_PACKS', 'auth' => 'acl_a_language', 'cat' => array('ACP_GENERAL_TASKS')), + 'lang_packs' => array('title' => 'ACP_LANGUAGE_PACKS', 'auth' => 'acl_a_language', 'cat' => array('ACP_LANGUAGE')), ), ); } -- cgit v1.2.1 From 7efc37d1f9d4a4335fc91af147cd059c17b7d45e Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Wed, 12 Dec 2012 19:09:18 +0100 Subject: [ticket/10954] Modify is_ajax check for consistency PHPBB3-10954 --- phpBB/includes/functions_display.php | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions_display.php b/phpBB/includes/functions_display.php index 9745d493c4..f3198bc1e5 100644 --- a/phpBB/includes/functions_display.php +++ b/phpBB/includes/functions_display.php @@ -61,14 +61,7 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod { markread('all', false, false, request_var('mark_time', 0)); - if (!$request->is_ajax()) - { - trigger_error( - $user->lang['FORUMS_MARKED'] . '

' . - sprintf($user->lang['RETURN_INDEX'], '', '') - ); - } - else + if ($request->is_ajax()) { // Tell the ajax script what language vars need to be replaced $data = array( @@ -78,6 +71,11 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod $json_response = new phpbb_json_response(); $json_response->send($data); } + + trigger_error( + $user->lang['FORUMS_MARKED'] . '

' . + sprintf($user->lang['RETURN_INDEX'], '', '') + ); } else { @@ -326,11 +324,7 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod $message = sprintf($user->lang['RETURN_FORUM'], '', ''); meta_refresh(3, $redirect); - if (!$request->is_ajax()) - { - trigger_error($user->lang['FORUMS_MARKED'] . '

' . $message); - } - else + if ($request->is_ajax()) { // Tell the ajax script what language vars need to be replaced $data = array( @@ -340,6 +334,8 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod $json_response = new phpbb_json_response(); $json_response->send($data); } + + trigger_error($user->lang['FORUMS_MARKED'] . '

' . $message); } else { -- cgit v1.2.1 From e2c67a8e42beefe1631a90feeb2215e2137c944b Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Wed, 12 Dec 2012 21:46:38 -0500 Subject: [ticket/11162] Rename tricky updates to database helper. PHPBB3-11162 --- phpBB/includes/functions_database_helper.php | 206 +++++++++++++++++++++++++++ phpBB/includes/functions_tricky_update.php | 206 --------------------------- phpBB/includes/mcp/mcp_forum.php | 2 +- phpBB/includes/mcp/mcp_topic.php | 2 +- 4 files changed, 208 insertions(+), 208 deletions(-) create mode 100644 phpBB/includes/functions_database_helper.php delete mode 100644 phpBB/includes/functions_tricky_update.php (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions_database_helper.php b/phpBB/includes/functions_database_helper.php new file mode 100644 index 0000000000..664c246888 --- /dev/null +++ b/phpBB/includes/functions_database_helper.php @@ -0,0 +1,206 @@ +sql_in_set($column, $from_values); + $result = $db->sql_query($sql); + + $old_user_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $old_user_ids[$row[$column]][] = (int) $row['user_id']; + } + $db->sql_freeresult($result); + + $sql = "SELECT $column, user_id + FROM $table + WHERE $column = " . (int) $to_value; + $result = $db->sql_query($sql); + + $new_user_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $new_user_ids[$row[$column]][] = (int) $row['user_id']; + } + $db->sql_freeresult($result); + + $queries = array(); + foreach ($from_values as $from_value) + { + if (!isset($old_user_ids[$from_value])) + { + continue; + } + if (empty($new_user_ids)) + { + $sql = "UPDATE $table + SET $column = " . (int) $to_value . " + WHERE $column = '" . $db->sql_escape($from_value) . "'"; + $queries[] = $sql; + } + else + { + $different_user_ids = array_diff($old_user_ids[$from_value], $new_user_ids[$to_value]); + if (!empty($different_user_ids)) + { + $sql = "UPDATE $table + SET $column = " . (int) $to_value . " + WHERE $column = '" . $db->sql_escape($from_value) . "' + AND " . $db->sql_in_set('user_id', $different_user_ids); + $queries[] = $sql; + } + } + } + + if (!empty($queries)) + { + $db->sql_transaction('begin'); + + foreach ($queries as $sql) + { + $db->sql_query($sql); + } + + $sql = "DELETE FROM $table + WHERE " . $db->sql_in_set($column, $from_values); + $db->sql_query($sql); + + $db->sql_transaction('commit'); + } +} + +/** +* Updates rows in given table from a set of values to a new value. +* If this results in rows violating uniqueness constraints, the duplicate +* rows are merged respecting notify_status (0 takes precedence over 1). +* +* The only supported table is topics_watch. +* +* @param dbal $db Database object +* @param string $table Table on which to perform the update +* @param string $column Column whose values to change +* @param array $from_values An array of values that should be changed +* @param int $to_value The new value +* @return null +*/ +function phpbb_update_rows_avoiding_duplicates_notify_status($db, $table, $column, $from_values, $to_value) +{ + $sql = "SELECT $column, user_id, notify_status + FROM $table + WHERE " . $db->sql_in_set($column, $from_values); + $result = $db->sql_query($sql); + + $old_user_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $old_user_ids[(int) $row['notify_status']][$row[$column]][] = (int) $row['user_id']; + } + $db->sql_freeresult($result); + + $sql = "SELECT $column, user_id + FROM $table + WHERE $column = " . (int) $to_value; + $result = $db->sql_query($sql); + + $new_user_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $new_user_ids[$row[$column]][] = (int) $row['user_id']; + } + $db->sql_freeresult($result); + + $queries = array(); + $extra_updates = array( + 0 => 'notify_status = 0', + 1 => '', + ); + foreach ($from_values as $from_value) + { + foreach ($extra_updates as $notify_status => $extra_update) + { + if (!isset($old_user_ids[$notify_status][$from_value])) + { + continue; + } + if (empty($new_user_ids)) + { + $sql = "UPDATE $table + SET $column = " . (int) $to_value . " + WHERE $column = '" . $db->sql_escape($from_value) . "'"; + $queries[] = $sql; + } + else + { + $different_user_ids = array_diff($old_user_ids[$notify_status][$from_value], $new_user_ids[$to_value]); + if (!empty($different_user_ids)) + { + $sql = "UPDATE $table + SET $column = " . (int) $to_value . " + WHERE $column = '" . $db->sql_escape($from_value) . "' + AND " . $db->sql_in_set('user_id', $different_user_ids); + $queries[] = $sql; + } + + if ($extra_update) + { + $same_user_ids = array_diff($old_user_ids[$notify_status][$from_value], $different_user_ids); + if (!empty($same_user_ids)) + { + $sql = "UPDATE $table + SET $extra_update + WHERE $column = '" . (int) $to_value . "' + AND " . $db->sql_in_set('user_id', $same_user_ids); + $queries[] = $sql; + } + } + } + } + } + + if (!empty($queries)) + { + $db->sql_transaction('begin'); + + foreach ($queries as $sql) + { + $db->sql_query($sql); + } + + $sql = "DELETE FROM $table + WHERE " . $db->sql_in_set($column, $from_values); + $db->sql_query($sql); + + $db->sql_transaction('commit'); + } +} diff --git a/phpBB/includes/functions_tricky_update.php b/phpBB/includes/functions_tricky_update.php deleted file mode 100644 index 664c246888..0000000000 --- a/phpBB/includes/functions_tricky_update.php +++ /dev/null @@ -1,206 +0,0 @@ -sql_in_set($column, $from_values); - $result = $db->sql_query($sql); - - $old_user_ids = array(); - while ($row = $db->sql_fetchrow($result)) - { - $old_user_ids[$row[$column]][] = (int) $row['user_id']; - } - $db->sql_freeresult($result); - - $sql = "SELECT $column, user_id - FROM $table - WHERE $column = " . (int) $to_value; - $result = $db->sql_query($sql); - - $new_user_ids = array(); - while ($row = $db->sql_fetchrow($result)) - { - $new_user_ids[$row[$column]][] = (int) $row['user_id']; - } - $db->sql_freeresult($result); - - $queries = array(); - foreach ($from_values as $from_value) - { - if (!isset($old_user_ids[$from_value])) - { - continue; - } - if (empty($new_user_ids)) - { - $sql = "UPDATE $table - SET $column = " . (int) $to_value . " - WHERE $column = '" . $db->sql_escape($from_value) . "'"; - $queries[] = $sql; - } - else - { - $different_user_ids = array_diff($old_user_ids[$from_value], $new_user_ids[$to_value]); - if (!empty($different_user_ids)) - { - $sql = "UPDATE $table - SET $column = " . (int) $to_value . " - WHERE $column = '" . $db->sql_escape($from_value) . "' - AND " . $db->sql_in_set('user_id', $different_user_ids); - $queries[] = $sql; - } - } - } - - if (!empty($queries)) - { - $db->sql_transaction('begin'); - - foreach ($queries as $sql) - { - $db->sql_query($sql); - } - - $sql = "DELETE FROM $table - WHERE " . $db->sql_in_set($column, $from_values); - $db->sql_query($sql); - - $db->sql_transaction('commit'); - } -} - -/** -* Updates rows in given table from a set of values to a new value. -* If this results in rows violating uniqueness constraints, the duplicate -* rows are merged respecting notify_status (0 takes precedence over 1). -* -* The only supported table is topics_watch. -* -* @param dbal $db Database object -* @param string $table Table on which to perform the update -* @param string $column Column whose values to change -* @param array $from_values An array of values that should be changed -* @param int $to_value The new value -* @return null -*/ -function phpbb_update_rows_avoiding_duplicates_notify_status($db, $table, $column, $from_values, $to_value) -{ - $sql = "SELECT $column, user_id, notify_status - FROM $table - WHERE " . $db->sql_in_set($column, $from_values); - $result = $db->sql_query($sql); - - $old_user_ids = array(); - while ($row = $db->sql_fetchrow($result)) - { - $old_user_ids[(int) $row['notify_status']][$row[$column]][] = (int) $row['user_id']; - } - $db->sql_freeresult($result); - - $sql = "SELECT $column, user_id - FROM $table - WHERE $column = " . (int) $to_value; - $result = $db->sql_query($sql); - - $new_user_ids = array(); - while ($row = $db->sql_fetchrow($result)) - { - $new_user_ids[$row[$column]][] = (int) $row['user_id']; - } - $db->sql_freeresult($result); - - $queries = array(); - $extra_updates = array( - 0 => 'notify_status = 0', - 1 => '', - ); - foreach ($from_values as $from_value) - { - foreach ($extra_updates as $notify_status => $extra_update) - { - if (!isset($old_user_ids[$notify_status][$from_value])) - { - continue; - } - if (empty($new_user_ids)) - { - $sql = "UPDATE $table - SET $column = " . (int) $to_value . " - WHERE $column = '" . $db->sql_escape($from_value) . "'"; - $queries[] = $sql; - } - else - { - $different_user_ids = array_diff($old_user_ids[$notify_status][$from_value], $new_user_ids[$to_value]); - if (!empty($different_user_ids)) - { - $sql = "UPDATE $table - SET $column = " . (int) $to_value . " - WHERE $column = '" . $db->sql_escape($from_value) . "' - AND " . $db->sql_in_set('user_id', $different_user_ids); - $queries[] = $sql; - } - - if ($extra_update) - { - $same_user_ids = array_diff($old_user_ids[$notify_status][$from_value], $different_user_ids); - if (!empty($same_user_ids)) - { - $sql = "UPDATE $table - SET $extra_update - WHERE $column = '" . (int) $to_value . "' - AND " . $db->sql_in_set('user_id', $same_user_ids); - $queries[] = $sql; - } - } - } - } - } - - if (!empty($queries)) - { - $db->sql_transaction('begin'); - - foreach ($queries as $sql) - { - $db->sql_query($sql); - } - - $sql = "DELETE FROM $table - WHERE " . $db->sql_in_set($column, $from_values); - $db->sql_query($sql); - - $db->sql_transaction('commit'); - } -} diff --git a/phpBB/includes/mcp/mcp_forum.php b/phpBB/includes/mcp/mcp_forum.php index cd938e83a0..db9fbd90bd 100644 --- a/phpBB/includes/mcp/mcp_forum.php +++ b/phpBB/includes/mcp/mcp_forum.php @@ -417,7 +417,7 @@ function merge_topics($forum_id, $topic_ids, $to_topic_id) // Update the topic watch table. if (!function_exists('phpbb_update_rows_avoiding_duplicates_notify_status')) { - include($phpbb_root_path . 'includes/functions_tricky_update.' . $phpEx); + include($phpbb_root_path . 'includes/functions_database_helper.' . $phpEx); } phpbb_update_rows_avoiding_duplicates_notify_status($db, TOPICS_WATCH_TABLE, 'topic_id', $topic_ids, $to_topic_id); diff --git a/phpBB/includes/mcp/mcp_topic.php b/phpBB/includes/mcp/mcp_topic.php index 29dabdd2a2..66d0c7a47e 100644 --- a/phpBB/includes/mcp/mcp_topic.php +++ b/phpBB/includes/mcp/mcp_topic.php @@ -622,7 +622,7 @@ function merge_posts($topic_id, $to_topic_id) // If the topic no longer exist, we will update the topic watch table. if (!function_exists('phpbb_update_rows_avoiding_duplicates_notify_status')) { - include($phpbb_root_path . 'includes/functions_tricky_update.' . $phpEx); + include($phpbb_root_path . 'includes/functions_database_helper.' . $phpEx); } phpbb_update_rows_avoiding_duplicates_notify_status($db, TOPICS_WATCH_TABLE, 'topic_id', $topic_ids, $to_topic_id); } -- cgit v1.2.1 From 10ee54d0280087a81236c05fbaf2c30f3b976b08 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Thu, 13 Dec 2012 07:56:23 -0500 Subject: [ticket/11015] Restore whitespace to avoid conflict when merging develop. PHPBB3-11015 --- phpBB/includes/db/driver/mssql.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/db/driver/mssql.php b/phpBB/includes/db/driver/mssql.php index 2e9debf84f..0c3d201b29 100644 --- a/phpBB/includes/db/driver/mssql.php +++ b/phpBB/includes/db/driver/mssql.php @@ -372,7 +372,7 @@ class phpbb_db_driver_mssql extends phpbb_db_driver FROM master.dbo.sysmessages WHERE error = ' . $error['code']; $result_id = @mssql_query($sql); - + if ($result_id) { $row = @mssql_fetch_assoc($result_id); -- cgit v1.2.1 From ec074eb97c2b2dc490089782420b065f4c1274cb Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Thu, 13 Dec 2012 13:10:26 -0600 Subject: [ticket/11263] Fix PHP Notice: Undefined variable: extension_manager $extension_manager should be $phpbb_extension_manager PHPBB3-11263 --- phpBB/includes/functions_messenger.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions_messenger.php b/phpBB/includes/functions_messenger.php index a33d7f0aa3..d0a02567ad 100644 --- a/phpBB/includes/functions_messenger.php +++ b/phpBB/includes/functions_messenger.php @@ -210,7 +210,7 @@ class messenger { $style_resource_locator = new phpbb_style_resource_locator(); $style_path_provider = new phpbb_style_extension_path_provider($phpbb_extension_manager, new phpbb_style_path_provider()); - $tpl = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $style_resource_locator, new phpbb_template_context(), $extension_manager); + $tpl = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $style_resource_locator, new phpbb_template_context(), $phpbb_extension_manager); $style = new phpbb_style($phpbb_root_path, $phpEx, $config, $user, $style_resource_locator, $style_path_provider, $tpl); $this->tpl_msg[$template_lang . $template_file] = $tpl; -- cgit v1.2.1 From 12bc77d034b6ca3893bb7f9169cf1cfa1c19b440 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Thu, 13 Dec 2012 18:00:12 -0500 Subject: [ticket/11015] Change more docblocks to phpbb_db_driver. PHPBB3-11015 --- phpBB/includes/config/db.php | 4 ++-- phpBB/includes/cron/task/core/prune_all_forums.php | 2 +- phpBB/includes/cron/task/core/prune_forum.php | 2 +- phpBB/includes/cron/task/core/tidy_search.php | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/config/db.php b/phpBB/includes/config/db.php index ed86383b6b..b18369a479 100644 --- a/phpBB/includes/config/db.php +++ b/phpBB/includes/config/db.php @@ -29,7 +29,7 @@ class phpbb_config_db extends phpbb_config /** * Database connection - * @var dbal + * @var phpbb_db_driver */ protected $db; @@ -42,7 +42,7 @@ class phpbb_config_db extends phpbb_config /** * Creates a configuration container with a default set of values * - * @param dbal $db Database connection + * @param phpbb_db_driver $db Database connection * @param phpbb_cache_driver_interface $cache Cache instance * @param string $table Configuration table name */ diff --git a/phpBB/includes/cron/task/core/prune_all_forums.php b/phpBB/includes/cron/task/core/prune_all_forums.php index 26e7528035..2c5d38cec0 100644 --- a/phpBB/includes/cron/task/core/prune_all_forums.php +++ b/phpBB/includes/cron/task/core/prune_all_forums.php @@ -37,7 +37,7 @@ class phpbb_cron_task_core_prune_all_forums extends phpbb_cron_task_base * @param string $phpbb_root_path The root path * @param string $php_ext The PHP extension * @param phpbb_config $config The config - * @param dbal $db The db connection + * @param phpbb_db_driver $db The db connection */ public function __construct($phpbb_root_path, $php_ext, phpbb_config $config, phpbb_db_driver $db) { diff --git a/phpBB/includes/cron/task/core/prune_forum.php b/phpBB/includes/cron/task/core/prune_forum.php index 9861d8b0c6..e3c497f072 100644 --- a/phpBB/includes/cron/task/core/prune_forum.php +++ b/phpBB/includes/cron/task/core/prune_forum.php @@ -47,7 +47,7 @@ class phpbb_cron_task_core_prune_forum extends phpbb_cron_task_base implements p * @param string $phpbb_root_path The root path * @param string $php_ext The PHP extension * @param phpbb_config $config The config - * @param dbal $db The db connection + * @param phpbb_db_driver $db The db connection */ public function __construct($phpbb_root_path, $php_ext, phpbb_config $config, phpbb_db_driver $db) { diff --git a/phpBB/includes/cron/task/core/tidy_search.php b/phpBB/includes/cron/task/core/tidy_search.php index 169fd3ba73..3ec25aa021 100644 --- a/phpBB/includes/cron/task/core/tidy_search.php +++ b/phpBB/includes/cron/task/core/tidy_search.php @@ -38,7 +38,7 @@ class phpbb_cron_task_core_tidy_search extends phpbb_cron_task_base * @param string $php_ext The PHP extension * @param phpbb_auth $auth The auth * @param phpbb_config $config The config - * @param dbal $db The db connection + * @param phpbb_db_driver $db The db connection * @param phpbb_user $user The user */ public function __construct($phpbb_root_path, $php_ext, phpbb_auth $auth, phpbb_config $config, phpbb_db_driver $db, phpbb_user $user) -- cgit v1.2.1 From 9e3fd3bf8e8ff08b159d9151aef6f9bb6b9244ee Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Thu, 13 Dec 2012 19:07:49 -0500 Subject: [ticket/11015] Fix 3.0 to 3.1 dbms conversion for mysqli. PHPBB3-11015 --- phpBB/includes/functions.php | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 4bf991ca9e..5c08d457b6 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -5550,23 +5550,39 @@ function phpbb_to_numeric($input) } /** -* Convert 3.0 dbms to 3.1 db driver class name +* Convert either 3.0 dbms or 3.1 db driver class name to 3.1 db driver class name. +* +* If $dbms is a valid 3.1 db driver class name, returns it unchanged. +* Otherwise prepends phpbb_db_driver_ to the dbms to convert a 3.0 dbms +* to 3.1 db driver class name. * * @param string $dbms dbms parameter * @return db driver class */ function phpbb_convert_30_dbms_to_31($dbms) { - if (class_exists($dbms)) + // Note: this check is done first because mysqli extension + // supplies a mysqli class, and class_exists($dbms) would return + // true for mysqli class. + // However, per the docblock any valid 3.1 driver name should be + // recognized by this function, and have priority over 3.0 dbms. + if (class_exists('phpbb_db_driver_' . $dbms)) { - return $dbms; + return 'phpbb_db_driver_' . $dbms; } - if (class_exists('phpbb_db_driver_' . $dbms)) + if (class_exists($dbms)) { - return 'phpbb_db_driver_' . $dbms; + return $dbms; } + // Additionally we could check that $dbms extends phpbb_db_driver. + // http://php.net/manual/en/class.reflectionclass.php + // Beware of possible performance issues: + // http://stackoverflow.com/questions/294582/php-5-reflection-api-performance + // We could check for interface implementation in all paths or + // only when we do not prepend phpbb_db_driver_. + throw new \RuntimeException("You have specified an invalid dbms driver: $dbms"); } -- cgit v1.2.1 From ee7dc9e5a061038eadce40ed295d47f978b9e567 Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Fri, 14 Dec 2012 01:08:09 +0100 Subject: [ticket/11015] Correctly transform 'mysqli' etc. in phpbb_convert_30_dbms_to_31 PHPBB3-11015 --- phpBB/includes/functions.php | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 5c08d457b6..575dd11388 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -5573,6 +5573,15 @@ function phpbb_convert_30_dbms_to_31($dbms) if (class_exists($dbms)) { + /* + $reflection = new \ReflectionClass($dbms); + + if ($reflection->isSubclassOf('phpbb_db_driver')) + { + return $dbms; + } + */ + return $dbms; } -- cgit v1.2.1 From 89f069637cd95f584db924407378b64df9910243 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Thu, 13 Dec 2012 19:21:23 -0500 Subject: [ticket/11015] Move comment in the right place. PHPBB3-11015 --- phpBB/includes/functions.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 575dd11388..8ef5284134 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -5573,6 +5573,13 @@ function phpbb_convert_30_dbms_to_31($dbms) if (class_exists($dbms)) { + // Additionally we could check that $dbms extends phpbb_db_driver. + // http://php.net/manual/en/class.reflectionclass.php + // Beware of possible performance issues: + // http://stackoverflow.com/questions/294582/php-5-reflection-api-performance + // We could check for interface implementation in all paths or + // only when we do not prepend phpbb_db_driver_. + /* $reflection = new \ReflectionClass($dbms); @@ -5585,13 +5592,6 @@ function phpbb_convert_30_dbms_to_31($dbms) return $dbms; } - // Additionally we could check that $dbms extends phpbb_db_driver. - // http://php.net/manual/en/class.reflectionclass.php - // Beware of possible performance issues: - // http://stackoverflow.com/questions/294582/php-5-reflection-api-performance - // We could check for interface implementation in all paths or - // only when we do not prepend phpbb_db_driver_. - throw new \RuntimeException("You have specified an invalid dbms driver: $dbms"); } -- cgit v1.2.1 From abcc59fca6bd5d899adbec248ba60db6c013e6a2 Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Mon, 3 Sep 2012 15:10:08 -0500 Subject: [ticket/8610] Update Bookmarks when merging topics Update bookmarks to the new topic when merging multiple topics using the merge_topics function. PHPBB3-8610 --- phpBB/includes/mcp/mcp_forum.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/mcp/mcp_forum.php b/phpBB/includes/mcp/mcp_forum.php index b70601b479..865e464e81 100644 --- a/phpBB/includes/mcp/mcp_forum.php +++ b/phpBB/includes/mcp/mcp_forum.php @@ -422,6 +422,14 @@ function merge_topics($forum_id, $topic_ids, $to_topic_id) $db->sql_query('DELETE FROM ' . TOPICS_WATCH_TABLE . ' WHERE ' . $db->sql_in_set('topic_id', $topic_ids)); + // If the topic no longer exist, we will update the bookmarks table. + // To not let it error out on users who bookmarked both topics, we just return on an error... + $db->sql_return_on_error(true); + $db->sql_query('UPDATE ' . BOOKMARKS_TABLE . ' SET topic_id = ' . (int) $to_topic_id . ' WHERE ' . $db->sql_in_set('topic_id', $topic_ids)); + $db->sql_return_on_error(false); + + $db->sql_query('DELETE FROM ' . BOOKMARKS_TABLE . ' WHERE ' . $db->sql_in_set('topic_id', $topic_ids)); + // Link to the new topic $return_link .= (($return_link) ? '

' : '') . sprintf($user->lang['RETURN_NEW_TOPIC'], '', ''); } @@ -444,4 +452,4 @@ function merge_topics($forum_id, $topic_ids, $to_topic_id) } } -?> \ No newline at end of file +?> -- cgit v1.2.1 From 8dd2a151cc53a5482fcf462736cf5c2eabccf599 Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Mon, 3 Sep 2012 15:18:30 -0500 Subject: [ticket/8610] Update Bookmarks when forking topics Update bookmarks to the new topic when forking topics using the mcp_fork_topic function. PHPBB3-8610 --- phpBB/includes/mcp/mcp_main.php | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/mcp/mcp_main.php b/phpBB/includes/mcp/mcp_main.php index ffede11d37..f7c49539ea 100644 --- a/phpBB/includes/mcp/mcp_main.php +++ b/phpBB/includes/mcp/mcp_main.php @@ -1251,6 +1251,26 @@ function mcp_fork_topic($topic_ids) { $db->sql_multi_insert(TOPICS_WATCH_TABLE, $sql_ary); } + + $sql = 'SELECT user_id + FROM ' . BOOKMARKS_TABLE . ' + WHERE topic_id = ' . $topic_id; + $result = $db->sql_query($sql); + + $sql_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + $sql_ary[] = array( + 'topic_id' => (int) $new_topic_id, + 'user_id' => (int) $row['user_id'], + ); + } + $db->sql_freeresult($result); + + if (sizeof($sql_ary)) + { + $db->sql_multi_insert(BOOKMARKS_TABLE, $sql_ary); + } } // Sync new topics, parent forums and board stats @@ -1314,4 +1334,4 @@ function mcp_fork_topic($topic_ids) } } -?> \ No newline at end of file +?> -- cgit v1.2.1 From 05d7decdd35eac3b58807b9ced318421a6ce15da Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Mon, 3 Sep 2012 15:30:52 -0500 Subject: [ticket/8610] Update Bookmarks when merging posts into another topic Update bookmarks to the new topic when merging posts into another topic using the merge_posts function. PHPBB3-8610 --- phpBB/includes/mcp/mcp_topic.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/mcp/mcp_topic.php b/phpBB/includes/mcp/mcp_topic.php index 7d4edaf362..36cfe1e355 100644 --- a/phpBB/includes/mcp/mcp_topic.php +++ b/phpBB/includes/mcp/mcp_topic.php @@ -626,6 +626,14 @@ function merge_posts($topic_id, $to_topic_id) $db->sql_return_on_error(false); $db->sql_query('DELETE FROM ' . TOPICS_WATCH_TABLE . ' WHERE topic_id = ' . (int) $topic_id); + + // If the topic no longer exist, we will update the bookmarks table. + // To not let it error out on users who bookmarked both topics, we just return on an error... + $db->sql_return_on_error(true); + $db->sql_query('UPDATE ' . BOOKMARKS_TABLE . ' SET topic_id = ' . (int) $to_topic_id . ' WHERE topic_id = ' . (int) $topic_id); + $db->sql_return_on_error(false); + + $db->sql_query('DELETE FROM ' . BOOKMARKS_TABLE . ' WHERE topic_id = ' . (int) $topic_id); } // Link to the new topic @@ -650,4 +658,4 @@ function merge_posts($topic_id, $to_topic_id) } } -?> \ No newline at end of file +?> -- cgit v1.2.1 From 5a88bd1bf103537aece6713145fd13288cf3c329 Mon Sep 17 00:00:00 2001 From: Nathaniel Guse Date: Mon, 3 Sep 2012 15:35:06 -0500 Subject: [ticket/8610] Update Bookmarks and Subscriptions when splitting topics Update bookmarks and subscriptions to add bookmarks/subscriptions to the new topic when using the split_topic function PHPBB3-8610 --- phpBB/includes/mcp/mcp_forum.php | 12 +++++++-- phpBB/includes/mcp/mcp_topic.php | 56 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 64 insertions(+), 4 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/mcp/mcp_forum.php b/phpBB/includes/mcp/mcp_forum.php index 865e464e81..ce709e7f71 100644 --- a/phpBB/includes/mcp/mcp_forum.php +++ b/phpBB/includes/mcp/mcp_forum.php @@ -98,6 +98,9 @@ function mcp_forum_view($id, $mode, $action, $forum_info) $forum_topics = ($total == -1) ? $forum_info['forum_topics'] : $total; $limit_time_sql = ($sort_days) ? 'AND t.topic_last_post_time >= ' . (time() - ($sort_days * 86400)) : ''; + $base_url = $url . "&i=$id&action=$action&mode=$mode&sd=$sort_dir&sk=$sort_key&st=$sort_days" . (($merge_select) ? $selected_ids : ''); + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $forum_topics, $topics_per_page, $start); + $template->assign_vars(array( 'ACTION' => $action, 'FORUM_NAME' => $forum_info['forum_name'], @@ -425,10 +428,15 @@ function merge_topics($forum_id, $topic_ids, $to_topic_id) // If the topic no longer exist, we will update the bookmarks table. // To not let it error out on users who bookmarked both topics, we just return on an error... $db->sql_return_on_error(true); - $db->sql_query('UPDATE ' . BOOKMARKS_TABLE . ' SET topic_id = ' . (int) $to_topic_id . ' WHERE ' . $db->sql_in_set('topic_id', $topic_ids)); + $sql = 'UPDATE ' . BOOKMARKS_TABLE . ' + SET topic_id = ' . (int) $to_topic_id . ' + WHERE ' . $db->sql_in_set('topic_id', $topic_ids); + $db->sql_query($sql); $db->sql_return_on_error(false); - $db->sql_query('DELETE FROM ' . BOOKMARKS_TABLE . ' WHERE ' . $db->sql_in_set('topic_id', $topic_ids)); + $sql = 'DELETE FROM ' . BOOKMARKS_TABLE . ' + WHERE ' . $db->sql_in_set('topic_id', $topic_ids); + $db->sql_query($sql); // Link to the new topic $return_link .= (($return_link) ? '

' : '') . sprintf($user->lang['RETURN_NEW_TOPIC'], '', ''); diff --git a/phpBB/includes/mcp/mcp_topic.php b/phpBB/includes/mcp/mcp_topic.php index 36cfe1e355..f0775f9137 100644 --- a/phpBB/includes/mcp/mcp_topic.php +++ b/phpBB/includes/mcp/mcp_topic.php @@ -307,6 +307,12 @@ function mcp_topic_view($id, $mode, $action) 'post_ids' => $post_id_list, )); + $base_url = append_sid("{$phpbb_root_path}mcp.$phpEx", "i=$id&t={$topic_info['topic_id']}&mode=$mode&action=$action&to_topic_id=$to_topic_id&posts_per_page=$posts_per_page&st=$sort_days&sk=$sort_key&sd=$sort_dir"); + if ($posts_per_page) + { + phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $total, $posts_per_page, $start); + } + $template->assign_vars(array( 'TOPIC_TITLE' => $topic_info['topic_title'], 'U_VIEW_TOPIC' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $topic_info['forum_id'] . '&t=' . $topic_info['topic_id']), @@ -517,6 +523,47 @@ function split_topic($action, $topic_id, $to_forum_id, $subject) WHERE post_id = {$post_id_list[0]}"; $db->sql_query($sql); + $sql = 'SELECT user_id, notify_status + FROM ' . TOPICS_WATCH_TABLE . ' + WHERE topic_id = ' . $topic_id; + $result = $db->sql_query($sql); + + $sql_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + $sql_ary[] = array( + 'topic_id' => (int) $to_topic_id, + 'user_id' => (int) $row['user_id'], + 'notify_status' => (int) $row['notify_status'], + ); + } + $db->sql_freeresult($result); + + if (sizeof($sql_ary)) + { + $db->sql_multi_insert(TOPICS_WATCH_TABLE, $sql_ary); + } + + $sql = 'SELECT user_id + FROM ' . BOOKMARKS_TABLE . ' + WHERE topic_id = ' . $topic_id; + $result = $db->sql_query($sql); + + $sql_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + $sql_ary[] = array( + 'topic_id' => (int) $to_topic_id, + 'user_id' => (int) $row['user_id'], + ); + } + $db->sql_freeresult($result); + + if (sizeof($sql_ary)) + { + $db->sql_multi_insert(BOOKMARKS_TABLE, $sql_ary); + } + $success_msg = 'TOPIC_SPLIT_SUCCESS'; // Update forum statistics @@ -630,10 +677,15 @@ function merge_posts($topic_id, $to_topic_id) // If the topic no longer exist, we will update the bookmarks table. // To not let it error out on users who bookmarked both topics, we just return on an error... $db->sql_return_on_error(true); - $db->sql_query('UPDATE ' . BOOKMARKS_TABLE . ' SET topic_id = ' . (int) $to_topic_id . ' WHERE topic_id = ' . (int) $topic_id); + $sql = 'UPDATE ' . BOOKMARKS_TABLE . ' + SET topic_id = ' . (int) $to_topic_id . ' + WHERE topic_id = ' . (int) $topic_id; + $db->sql_query($sql); $db->sql_return_on_error(false); - $db->sql_query('DELETE FROM ' . BOOKMARKS_TABLE . ' WHERE topic_id = ' . (int) $topic_id); + $sql = 'DELETE FROM ' . BOOKMARKS_TABLE . ' + WHERE topic_id = ' . (int) $topic_id; + $db->sql_query($sql); } // Link to the new topic -- cgit v1.2.1 From 71bcc58feecd49fe05f6b7e0e2bca8a94424f240 Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Tue, 30 Oct 2012 12:27:02 -0500 Subject: [ticket/8610] Add some comments PHPBB3-8610 --- phpBB/includes/mcp/mcp_main.php | 2 ++ phpBB/includes/mcp/mcp_topic.php | 2 ++ 2 files changed, 4 insertions(+) (limited to 'phpBB/includes') diff --git a/phpBB/includes/mcp/mcp_main.php b/phpBB/includes/mcp/mcp_main.php index f7c49539ea..cce4db8b4d 100644 --- a/phpBB/includes/mcp/mcp_main.php +++ b/phpBB/includes/mcp/mcp_main.php @@ -1231,6 +1231,7 @@ function mcp_fork_topic($topic_ids) } } + // Copy topic subscriptions to new topic $sql = 'SELECT user_id, notify_status FROM ' . TOPICS_WATCH_TABLE . ' WHERE topic_id = ' . $topic_id; @@ -1252,6 +1253,7 @@ function mcp_fork_topic($topic_ids) $db->sql_multi_insert(TOPICS_WATCH_TABLE, $sql_ary); } + // Copy bookmarks to new topic $sql = 'SELECT user_id FROM ' . BOOKMARKS_TABLE . ' WHERE topic_id = ' . $topic_id; diff --git a/phpBB/includes/mcp/mcp_topic.php b/phpBB/includes/mcp/mcp_topic.php index f0775f9137..28f5c6a41d 100644 --- a/phpBB/includes/mcp/mcp_topic.php +++ b/phpBB/includes/mcp/mcp_topic.php @@ -523,6 +523,7 @@ function split_topic($action, $topic_id, $to_forum_id, $subject) WHERE post_id = {$post_id_list[0]}"; $db->sql_query($sql); + // Copy topic subscriptions to new topic $sql = 'SELECT user_id, notify_status FROM ' . TOPICS_WATCH_TABLE . ' WHERE topic_id = ' . $topic_id; @@ -544,6 +545,7 @@ function split_topic($action, $topic_id, $to_forum_id, $subject) $db->sql_multi_insert(TOPICS_WATCH_TABLE, $sql_ary); } + // Copy bookmarks to new topic $sql = 'SELECT user_id FROM ' . BOOKMARKS_TABLE . ' WHERE topic_id = ' . $topic_id; -- cgit v1.2.1 From 5e38317dcf28627abbb622d0412cc6cc3416071b Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Thu, 13 Dec 2012 21:27:30 -0500 Subject: [ticket/11162] Chase dbal autoloading changes in develop. PHPBB3-11162 --- phpBB/includes/functions_database_helper.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions_database_helper.php b/phpBB/includes/functions_database_helper.php index 664c246888..1b508e6a02 100644 --- a/phpBB/includes/functions_database_helper.php +++ b/phpBB/includes/functions_database_helper.php @@ -22,14 +22,14 @@ if (!defined('IN_PHPBB')) * * The only supported table is bookmarks. * -* @param dbal $db Database object +* @param phpbb_db_driver $db Database object * @param string $table Table on which to perform the update * @param string $column Column whose values to change * @param array $from_values An array of values that should be changed * @param int $to_value The new value * @return null */ -function phpbb_update_rows_avoiding_duplicates($db, $table, $column, $from_values, $to_value) +function phpbb_update_rows_avoiding_duplicates(phpbb_db_driver $db, $table, $column, $from_values, $to_value) { $sql = "SELECT $column, user_id FROM $table @@ -107,14 +107,14 @@ function phpbb_update_rows_avoiding_duplicates($db, $table, $column, $from_value * * The only supported table is topics_watch. * -* @param dbal $db Database object +* @param phpbb_db_driver $db Database object * @param string $table Table on which to perform the update * @param string $column Column whose values to change * @param array $from_values An array of values that should be changed * @param int $to_value The new value * @return null */ -function phpbb_update_rows_avoiding_duplicates_notify_status($db, $table, $column, $from_values, $to_value) +function phpbb_update_rows_avoiding_duplicates_notify_status(phpbb_db_driver $db, $table, $column, $from_values, $to_value) { $sql = "SELECT $column, user_id, notify_status FROM $table -- cgit v1.2.1 From 5c614ff6249c441637103ba0b78338f878057479 Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Thu, 13 Dec 2012 20:30:16 -0600 Subject: [ticket/8610] Revert some funky merging I did from rebase PHPBB3-8610 --- phpBB/includes/mcp/mcp_forum.php | 5 +---- phpBB/includes/mcp/mcp_main.php | 2 +- phpBB/includes/mcp/mcp_topic.php | 8 +------- 3 files changed, 3 insertions(+), 12 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/mcp/mcp_forum.php b/phpBB/includes/mcp/mcp_forum.php index ce709e7f71..78239698a9 100644 --- a/phpBB/includes/mcp/mcp_forum.php +++ b/phpBB/includes/mcp/mcp_forum.php @@ -98,9 +98,6 @@ function mcp_forum_view($id, $mode, $action, $forum_info) $forum_topics = ($total == -1) ? $forum_info['forum_topics'] : $total; $limit_time_sql = ($sort_days) ? 'AND t.topic_last_post_time >= ' . (time() - ($sort_days * 86400)) : ''; - $base_url = $url . "&i=$id&action=$action&mode=$mode&sd=$sort_dir&sk=$sort_key&st=$sort_days" . (($merge_select) ? $selected_ids : ''); - phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $forum_topics, $topics_per_page, $start); - $template->assign_vars(array( 'ACTION' => $action, 'FORUM_NAME' => $forum_info['forum_name'], @@ -460,4 +457,4 @@ function merge_topics($forum_id, $topic_ids, $to_topic_id) } } -?> +?> \ No newline at end of file diff --git a/phpBB/includes/mcp/mcp_main.php b/phpBB/includes/mcp/mcp_main.php index cce4db8b4d..0cef8933fc 100644 --- a/phpBB/includes/mcp/mcp_main.php +++ b/phpBB/includes/mcp/mcp_main.php @@ -1336,4 +1336,4 @@ function mcp_fork_topic($topic_ids) } } -?> +?> \ No newline at end of file diff --git a/phpBB/includes/mcp/mcp_topic.php b/phpBB/includes/mcp/mcp_topic.php index 28f5c6a41d..ba11b04884 100644 --- a/phpBB/includes/mcp/mcp_topic.php +++ b/phpBB/includes/mcp/mcp_topic.php @@ -307,12 +307,6 @@ function mcp_topic_view($id, $mode, $action) 'post_ids' => $post_id_list, )); - $base_url = append_sid("{$phpbb_root_path}mcp.$phpEx", "i=$id&t={$topic_info['topic_id']}&mode=$mode&action=$action&to_topic_id=$to_topic_id&posts_per_page=$posts_per_page&st=$sort_days&sk=$sort_key&sd=$sort_dir"); - if ($posts_per_page) - { - phpbb_generate_template_pagination($template, $base_url, 'pagination', 'start', $total, $posts_per_page, $start); - } - $template->assign_vars(array( 'TOPIC_TITLE' => $topic_info['topic_title'], 'U_VIEW_TOPIC' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $topic_info['forum_id'] . '&t=' . $topic_info['topic_id']), @@ -712,4 +706,4 @@ function merge_posts($topic_id, $to_topic_id) } } -?> +?> \ No newline at end of file -- cgit v1.2.1 From a50907250e487675baf9ac77be564b6ccb72b056 Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Thu, 13 Dec 2012 20:42:10 -0600 Subject: [ticket/8610] Use phpbb_update_rows_avoiding_duplicates PHPBB3-8610 --- phpBB/includes/mcp/mcp_forum.php | 16 +++++----------- phpBB/includes/mcp/mcp_topic.php | 12 +----------- 2 files changed, 6 insertions(+), 22 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/mcp/mcp_forum.php b/phpBB/includes/mcp/mcp_forum.php index 78239698a9..097f67487d 100644 --- a/phpBB/includes/mcp/mcp_forum.php +++ b/phpBB/includes/mcp/mcp_forum.php @@ -423,17 +423,11 @@ function merge_topics($forum_id, $topic_ids, $to_topic_id) $db->sql_query('DELETE FROM ' . TOPICS_WATCH_TABLE . ' WHERE ' . $db->sql_in_set('topic_id', $topic_ids)); // If the topic no longer exist, we will update the bookmarks table. - // To not let it error out on users who bookmarked both topics, we just return on an error... - $db->sql_return_on_error(true); - $sql = 'UPDATE ' . BOOKMARKS_TABLE . ' - SET topic_id = ' . (int) $to_topic_id . ' - WHERE ' . $db->sql_in_set('topic_id', $topic_ids); - $db->sql_query($sql); - $db->sql_return_on_error(false); - - $sql = 'DELETE FROM ' . BOOKMARKS_TABLE . ' - WHERE ' . $db->sql_in_set('topic_id', $topic_ids); - $db->sql_query($sql); + if (!function_exists('phpbb_update_rows_avoiding_duplicates')) + { + include($phpbb_root_path . 'includes/functions_database_helper.' . $phpEx); + } + phpbb_update_rows_avoiding_duplicates($db, BOOKMARKS_TABLE, 'topic_id', $topic_ids, $to_topic_id); // Link to the new topic $return_link .= (($return_link) ? '

' : '') . sprintf($user->lang['RETURN_NEW_TOPIC'], '', ''); diff --git a/phpBB/includes/mcp/mcp_topic.php b/phpBB/includes/mcp/mcp_topic.php index ba11b04884..29d7a0e41b 100644 --- a/phpBB/includes/mcp/mcp_topic.php +++ b/phpBB/includes/mcp/mcp_topic.php @@ -671,17 +671,7 @@ function merge_posts($topic_id, $to_topic_id) $db->sql_query('DELETE FROM ' . TOPICS_WATCH_TABLE . ' WHERE topic_id = ' . (int) $topic_id); // If the topic no longer exist, we will update the bookmarks table. - // To not let it error out on users who bookmarked both topics, we just return on an error... - $db->sql_return_on_error(true); - $sql = 'UPDATE ' . BOOKMARKS_TABLE . ' - SET topic_id = ' . (int) $to_topic_id . ' - WHERE topic_id = ' . (int) $topic_id; - $db->sql_query($sql); - $db->sql_return_on_error(false); - - $sql = 'DELETE FROM ' . BOOKMARKS_TABLE . ' - WHERE topic_id = ' . (int) $topic_id; - $db->sql_query($sql); + phpbb_update_rows_avoiding_duplicates($db, BOOKMARKS_TABLE, 'topic_id', $topic_id, $to_topic_id); } // Link to the new topic -- cgit v1.2.1 From ade9f831aa151de428c4d2b33fce48f9733db336 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Fri, 14 Dec 2012 12:58:57 +0100 Subject: [ticket/11250] Move quote special cases from class to unit tests PHPBB3-11250 --- phpBB/includes/message_parser.php | 11 ----------- 1 file changed, 11 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/message_parser.php b/phpBB/includes/message_parser.php index 1cd2a46fa1..44960dd78d 100644 --- a/phpBB/includes/message_parser.php +++ b/phpBB/includes/message_parser.php @@ -702,17 +702,6 @@ class bbcode_firstpass extends bbcode { global $config, $user; - /** - * If you change this code, make sure the cases described within the following reports are still working: - * #3572 - [quote="[test]test"]test [ test[/quote] - (correct: parsed) - * #14667 - [quote]test[/quote] test ] and [ test [quote]test[/quote] (correct: parsed) - * #14770 - [quote="["]test[/quote] (correct: parsed) - * [quote="[i]test[/i]"]test[/quote] (correct: parsed) - * [quote="[quote]test[/quote]"]test[/quote] (correct: parsed - Username displayed as [quote]test[/quote]) - * #20735 - [quote]test[/[/b]quote] test [/quote][/quote] test - (correct: quoted: "test[/[/b]quote] test" / non-quoted: "[/quote] test" - also failed if layout distorted) - * #40565 - [quote="a"]a[/quote][quote="a]a[/quote] (correct: first quote tag parsed, second quote tag unparsed) - */ - $in = str_replace("\r\n", "\n", str_replace('\"', '"', trim($in))); if (!$in) -- cgit v1.2.1 From 16c021e98667fe353e6b3a6cb0bbf3b8f2319f98 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Fri, 14 Dec 2012 15:46:45 +0100 Subject: [ticket/10954] Change behavior of marking topics/forums It will now display a popup message for 3 seconds which will confirm the taken action. The amount of DOM traversals have been significantly reduced and jQuery.each is now used instead of for loops. Additionally, it is now possible to click on the mark topics/forums read links without triggering an AJAX error. PHPBB3-10954 --- phpBB/includes/functions_display.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions_display.php b/phpBB/includes/functions_display.php index f3198bc1e5..cd4c901b58 100644 --- a/phpBB/includes/functions_display.php +++ b/phpBB/includes/functions_display.php @@ -63,10 +63,13 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod if ($request->is_ajax()) { - // Tell the ajax script what language vars need to be replaced + // Tell the ajax script what language vars and URL need to be replaced $data = array( 'NO_UNREAD_POSTS' => $user->lang['NO_UNREAD_POSTS'], - 'UNREAD_POSTS' => $user->lang['UNREAD_POSTS'] + 'UNREAD_POSTS' => $user->lang['UNREAD_POSTS'], + 'U_MARK_FORUMS' => ($user->data['is_registered'] || $config['load_anon_lastread']) ? append_sid("{$phpbb_root_path}index.$phpEx", 'hash=' . generate_link_hash('global') . '&mark=forums&mark_time=' . time()) : '', + 'MESSAGE_TITLE' => $user->lang['INFORMATION'], + 'MESSAGE_TEXT' => $user->lang['FORUMS_MARKED'] ); $json_response = new phpbb_json_response(); $json_response->send($data); @@ -326,10 +329,13 @@ function display_forums($root_data = '', $display_moderators = true, $return_mod if ($request->is_ajax()) { - // Tell the ajax script what language vars need to be replaced + // Tell the ajax script what language vars and URL need to be replaced $data = array( 'NO_UNREAD_POSTS' => $user->lang['NO_UNREAD_POSTS'], - 'UNREAD_POSTS' => $user->lang['UNREAD_POSTS'] + 'UNREAD_POSTS' => $user->lang['UNREAD_POSTS'], + 'U_MARK_FORUMS' => ($user->data['is_registered'] || $config['load_anon_lastread']) ? append_sid("{$phpbb_root_path}viewforum.$phpEx", 'hash=' . generate_link_hash('global') . '&f=' . $root_data['forum_id'] . '&mark=forums&mark_time=' . time()) : '', + 'MESSAGE_TITLE' => $user->lang['INFORMATION'], + 'MESSAGE_TEXT' => $user->lang['FORUMS_MARKED'] ); $json_response = new phpbb_json_response(); $json_response->send($data); -- cgit v1.2.1 From 649e009f7b42a751d7b208bd5a4659c0fc442912 Mon Sep 17 00:00:00 2001 From: Nathan Guse Date: Sat, 15 Dec 2012 16:23:48 -0600 Subject: [ticket/8610] Update comment PHPBB3-8610 --- phpBB/includes/mcp/mcp_forum.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/mcp/mcp_forum.php b/phpBB/includes/mcp/mcp_forum.php index de2966d34e..04e0e70f1d 100644 --- a/phpBB/includes/mcp/mcp_forum.php +++ b/phpBB/includes/mcp/mcp_forum.php @@ -422,7 +422,7 @@ function merge_topics($forum_id, $topic_ids, $to_topic_id) // Update the topic watch table. phpbb_update_rows_avoiding_duplicates_notify_status($db, TOPICS_WATCH_TABLE, 'topic_id', $topic_ids, $to_topic_id); - // If the topic no longer exist, we will update the bookmarks table. + // Update the bookmarks table. phpbb_update_rows_avoiding_duplicates($db, BOOKMARKS_TABLE, 'topic_id', $topic_ids, $to_topic_id); // Link to the new topic -- cgit v1.2.1 From 066649946802896a3a18cc1868fe0e1795db3efb Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Sat, 15 Dec 2012 23:54:49 +0100 Subject: [ticket/11273] Add space before MiB to Sphinx "Indexer memory limit" input box. PHPBB3-11273 --- phpBB/includes/search/fulltext_sphinx.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/search/fulltext_sphinx.php b/phpBB/includes/search/fulltext_sphinx.php index a84a64825c..4bacf74f93 100644 --- a/phpBB/includes/search/fulltext_sphinx.php +++ b/phpBB/includes/search/fulltext_sphinx.php @@ -875,7 +875,7 @@ class phpbb_search_fulltext_sphinx

' . $this->user->lang['FULLTEXT_SPHINX_INDEXER_MEM_LIMIT_EXPLAIN'] . '
-
' . $this->user->lang['MIB'] . '
+
' . $this->user->lang['MIB'] . '

' . $this->user->lang['FULLTEXT_SPHINX_CONFIG_FILE_EXPLAIN'] . '
-- cgit v1.2.1 From 54d96dfac720d5aa1cdc617ca93749d26ee9f264 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 17 Dec 2012 12:59:44 +0100 Subject: [ticket/10763] Make functions for remote avatars static fileupload::image_types() and filespec::get_extension() are called statically while submitting the form for the remote avatar. Make them static as described in the ticket in order to prevent a PHP notice. Also change the tests to use the static functions. PHPBB3-10763 --- phpBB/includes/functions_upload.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions_upload.php b/phpBB/includes/functions_upload.php index b467aa93d1..d4cb34cc4e 100644 --- a/phpBB/includes/functions_upload.php +++ b/phpBB/includes/functions_upload.php @@ -70,7 +70,7 @@ class filespec $this->mimetype = 'application/octetstream'; } - $this->extension = strtolower($this->get_extension($this->realname)); + $this->extension = strtolower(filespec::get_extension($this->realname)); // Try to get real filesize from temporary folder (not always working) ;) $this->filesize = (@filesize($this->filename)) ? @filesize($this->filename) : $this->filesize; @@ -187,8 +187,11 @@ class filespec /** * Get file extension + * + * @param string Filename that needs to be checked + * @return string Extension of the supplied filename */ - function get_extension($filename) + static public function get_extension($filename) { if (strpos($filename, '.') === false) { @@ -369,7 +372,7 @@ class filespec } // Check image type - $types = $this->upload->image_types(); + $types = fileupload::image_types(); if (!isset($types[$this->image_info[2]]) || !in_array($this->extension, $types[$this->image_info[2]])) { @@ -1019,9 +1022,11 @@ class fileupload } /** - * Return image type/extension mapping + * Get image type/extension mapping + * + * @return array Array containing the image types and their extensions */ - function image_types() + static public function image_types() { return array( IMAGETYPE_GIF => array('gif'), -- cgit v1.2.1 From 4ae0c787828ea34bb927a8346804e52bd25cffac Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 17 Dec 2012 19:17:32 +0100 Subject: [ticket/10763] Use self when calling get_extension() in filespec class PHPBB3-10763 --- phpBB/includes/functions_upload.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions_upload.php b/phpBB/includes/functions_upload.php index d4cb34cc4e..4f31a85e83 100644 --- a/phpBB/includes/functions_upload.php +++ b/phpBB/includes/functions_upload.php @@ -70,7 +70,7 @@ class filespec $this->mimetype = 'application/octetstream'; } - $this->extension = strtolower(filespec::get_extension($this->realname)); + $this->extension = strtolower(self::get_extension($this->realname)); // Try to get real filesize from temporary folder (not always working) ;) $this->filesize = (@filesize($this->filename)) ? @filesize($this->filename) : $this->filesize; -- cgit v1.2.1 From 917a2fa9a6cf8b01a347df67fc14d3fc826aa3ed Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Tue, 22 May 2012 02:20:21 +0200 Subject: [ticket/10880] The m_approve permisson no longer implies f_noapprove. PHPBB3-10880 --- phpBB/includes/functions_posting.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/functions_posting.php b/phpBB/includes/functions_posting.php index a1029ab97a..e5cbae0d71 100644 --- a/phpBB/includes/functions_posting.php +++ b/phpBB/includes/functions_posting.php @@ -1698,8 +1698,9 @@ function submit_post($mode, $subject, $username, $topic_type, &$poll, &$data, $u // The variable name should be $post_approved, because it indicates if the post is approved or not $post_approval = 1; - // Check the permissions for post approval. Moderators are not affected. - if (!$auth->acl_get('f_noapprove', $data['forum_id']) && !$auth->acl_get('m_approve', $data['forum_id'])) + // Check the permissions for post approval. + // Moderators must go through post approval like ordinary users. + if (!$auth->acl_get('f_noapprove', $data['forum_id'])) { // Post not approved, but in queue $post_approval = 0; -- cgit v1.2.1 From e50f69187f21e29a12512880e0c69f2876e84aa1 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev Date: Thu, 20 Dec 2012 04:35:30 -0500 Subject: [ticket/11037] Eliminate global $db usage in cache drivers. The only time $db is needed in cache drivers is to navigate the result set in sql_save. Pass it as a parameter in that function. PHPBB3-11037 --- phpBB/includes/cache/driver/file.php | 6 ++---- phpBB/includes/cache/driver/interface.php | 3 ++- phpBB/includes/cache/driver/memory.php | 6 ++---- phpBB/includes/cache/driver/null.php | 4 ++-- phpBB/includes/db/driver/firebird.php | 2 +- phpBB/includes/db/driver/mssql.php | 2 +- phpBB/includes/db/driver/mssql_odbc.php | 2 +- phpBB/includes/db/driver/mssqlnative.php | 2 +- phpBB/includes/db/driver/mysql.php | 2 +- phpBB/includes/db/driver/mysqli.php | 2 +- phpBB/includes/db/driver/oracle.php | 2 +- phpBB/includes/db/driver/postgres.php | 2 +- phpBB/includes/db/driver/sqlite.php | 2 +- 13 files changed, 17 insertions(+), 20 deletions(-) (limited to 'phpBB/includes') diff --git a/phpBB/includes/cache/driver/file.php b/phpBB/includes/cache/driver/file.php index 691abe0438..85decbe3e8 100644 --- a/phpBB/includes/cache/driver/file.php +++ b/phpBB/includes/cache/driver/file.php @@ -367,12 +367,10 @@ class phpbb_cache_driver_file extends phpbb_cache_driver_base } /** - * Save sql query + * {@inheritDoc} */ - function sql_save($query, $query_result, $ttl) + function sql_save(phpbb_db_driver $db, $query, $query_result, $ttl) { - global $db; - // Remove extra spaces and tabs $query = preg_replace('/[\n\r\s\t]+/', ' ', $query); diff --git a/phpBB/includes/cache/driver/interface.php b/phpBB/includes/cache/driver/interface.php index d403bbcd71..53f684d1c8 100644 --- a/phpBB/includes/cache/driver/interface.php +++ b/phpBB/includes/cache/driver/interface.php @@ -85,6 +85,7 @@ interface phpbb_cache_driver_interface * result to persistent storage. In other words, there is no need * to call save() afterwards. * + * @param phpbb_db_driver $db Database connection * @param string $query SQL query, should be used for generating storage key * @param mixed $query_result The result from dbal::sql_query, to be passed to * dbal::sql_fetchrow to get all rows and store them @@ -95,7 +96,7 @@ interface phpbb_cache_driver_interface * representing the query should be returned. Otherwise * the original $query_result should be returned. */ - public function sql_save($query, $query_result, $ttl); + public function sql_save(phpbb_db_driver $db, $query, $query_result, $ttl); /** * Check if result for a given SQL query exists in cache. diff --git a/phpBB/includes/cache/driver/memory.php b/phpBB/includes/cache/driver/memory.php index c39f9f7850..f77a1df316 100644 --- a/phpBB/includes/cache/driver/memory.php +++ b/phpBB/includes/cache/driver/memory.php @@ -283,12 +283,10 @@ abstract class phpbb_cache_driver_memory extends phpbb_cache_driver_base } /** - * Save sql query + * {@inheritDoc} */ - function sql_save($query, $query_result, $ttl) + function sql_save(phpbb_db_driver $db, $query, $query_result, $ttl) { - global $db; - // Remove extra spaces and tabs $query = preg_replace('/[\n\r\s\t]+/', ' ', $query); $hash = md5($query); diff --git a/phpBB/includes/cache/driver/null.php b/phpBB/includes/cache/driver/null.php index 687604d14f..2fadc27ba3 100644 --- a/phpBB/includes/cache/driver/null.php +++ b/phpBB/includes/cache/driver/null.php @@ -105,9 +105,9 @@ class phpbb_cache_driver_null extends phpbb_cache_driver_base } /** - * Save sql query + * {@inheritDoc} */ - function sql_save($query, $query_result, $ttl) + function sql_save(phpbb_db_driver $db, $query, $query_result, $ttl) { return $query_result; } diff --git a/phpBB/includes/db/driver/firebird.php b/phpBB/includes/db/driver/firebird.php index a55175c345..4767b29f63 100644 --- a/phpBB/includes/db/driver/firebird.php +++ b/phpBB/includes/db/driver/firebird.php @@ -270,7 +270,7 @@ class phpbb_db_driver_firebird extends phpbb_db_driver if ($cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } else if (strpos($query, 'SELECT') === 0 && $this->query_result) { diff --git a/phpBB/includes/db/driver/mssql.php b/phpBB/includes/db/driver/mssql.php index ac957e7698..215c6345c6 100644 --- a/phpBB/includes/db/driver/mssql.php +++ b/phpBB/includes/db/driver/mssql.php @@ -168,7 +168,7 @@ class phpbb_db_driver_mssql extends phpbb_db_driver if ($cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } else if (strpos($query, 'SELECT') === 0 && $this->query_result) { diff --git a/phpBB/includes/db/driver/mssql_odbc.php b/phpBB/includes/db/driver/mssql_odbc.php index 13e74e66d4..7d93f939a2 100644 --- a/phpBB/includes/db/driver/mssql_odbc.php +++ b/phpBB/includes/db/driver/mssql_odbc.php @@ -197,7 +197,7 @@ class phpbb_db_driver_mssql_odbc extends phpbb_db_driver if ($cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } else if (strpos($query, 'SELECT') === 0 && $this->query_result) { diff --git a/phpBB/includes/db/driver/mssqlnative.php b/phpBB/includes/db/driver/mssqlnative.php index 4b1639aba2..f88c702d07 100644 --- a/phpBB/includes/db/driver/mssqlnative.php +++ b/phpBB/includes/db/driver/mssqlnative.php @@ -337,7 +337,7 @@ class phpbb_db_driver_mssqlnative extends phpbb_db_driver if ($cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } else if (strpos($query, 'SELECT') === 0 && $this->query_result) { diff --git a/phpBB/includes/db/driver/mysql.php b/phpBB/includes/db/driver/mysql.php index 6fc6fab483..30f78c9231 100644 --- a/phpBB/includes/db/driver/mysql.php +++ b/phpBB/includes/db/driver/mysql.php @@ -206,7 +206,7 @@ class phpbb_db_driver_mysql extends phpbb_db_driver if ($cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } else if (strpos($query, 'SELECT') === 0 && $this->query_result) { diff --git a/phpBB/includes/db/driver/mysqli.php b/phpBB/includes/db/driver/mysqli.php index be28a95715..4f45c5781c 100644 --- a/phpBB/includes/db/driver/mysqli.php +++ b/phpBB/includes/db/driver/mysqli.php @@ -201,7 +201,7 @@ class phpbb_db_driver_mysqli extends phpbb_db_driver if ($cache_ttl) { - $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } } else if (defined('DEBUG')) diff --git a/phpBB/includes/db/driver/oracle.php b/phpBB/includes/db/driver/oracle.php index 6263ea8414..53f9dd71e0 100644 --- a/phpBB/includes/db/driver/oracle.php +++ b/phpBB/includes/db/driver/oracle.php @@ -446,7 +446,7 @@ class phpbb_db_driver_oracle extends phpbb_db_driver if ($cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } else if (strpos($query, 'SELECT') === 0 && $this->query_result) { diff --git a/phpBB/includes/db/driver/postgres.php b/phpBB/includes/db/driver/postgres.php index 147ecd04d9..cca97c9c0e 100644 --- a/phpBB/includes/db/driver/postgres.php +++ b/phpBB/includes/db/driver/postgres.php @@ -211,7 +211,7 @@ class phpbb_db_driver_postgres extends phpbb_db_driver if ($cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } else if (strpos($query, 'SELECT') === 0 && $this->query_result) { diff --git a/phpBB/includes/db/driver/sqlite.php b/phpBB/includes/db/driver/sqlite.php index 6b9cc64d89..155409b665 100644 --- a/phpBB/includes/db/driver/sqlite.php +++ b/phpBB/includes/db/driver/sqlite.php @@ -152,7 +152,7 @@ class phpbb_db_driver_sqlite extends phpbb_db_driver if ($cache_ttl) { $this->open_queries[(int) $this->query_result] = $this->query_result; - $this->query_result = $cache->sql_save($query, $this->query_result, $cache_ttl); + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); } else if (strpos($query, 'SELECT') === 0 && $this->query_result) { -- cgit v1.2.1