diff options
Diffstat (limited to 'phpBB/phpbb/db')
76 files changed, 12621 insertions, 0 deletions
diff --git a/phpBB/phpbb/db/driver/driver.php b/phpBB/phpbb/db/driver/driver.php new file mode 100644 index 0000000000..08c966c07a --- /dev/null +++ b/phpBB/phpbb/db/driver/driver.php @@ -0,0 +1,1044 @@ +<?php +/** +* +* @package dbal +* @copyright (c) 2005 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* Database Abstraction Layer +* @package dbal +*/ +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 && $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 ($cache && !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 . ' ]<br /><br />' . $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) ? '<br /><br />SQL<br /><br />' . 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 .= '<br /><br />An sql error occurred while fetching this page. Please contact an administrator if this problem persists.'; +				} +				else +				{ +					if (!empty($config['board_contact'])) +					{ +						$message .= '<br /><br />' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '<a href="mailto:' . htmlspecialchars($config['board_contact']) . '">', '</a>'); +					} +					else +					{ +						$message .= '<br /><br />' . 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, $phpbb_admin_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 '<!DOCTYPE html> +					<html dir="ltr"> +					<head> +						<meta charset="utf-8"> +						<title>SQL Report</title> +						<link href="' . htmlspecialchars($phpbb_admin_path) . 'style/admin.css" rel="stylesheet" type="text/css" media="screen" /> +					</head> +					<body id="errorpage"> +					<div id="wrap"> +						<div id="page-header"> +							<a href="' . build_url('explain') . '">Return to previous page</a> +						</div> +						<div id="page-body"> +							<div id="acp"> +							<div class="panel"> +								<span class="corners-top"><span></span></span> +								<div id="content"> +									<h1>SQL Report</h1> +									<br /> +									<p><b>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' : '') . '</b></p> + +									<p>Time spent on ' . $this->sql_layer . ' queries: <b>' . round($this->sql_time, 5) . 's</b> | Time spent on PHP: <b>' . round($totaltime - $this->sql_time, 5) . 's</b></p> + +									<br /><br /> +									' . $this->sql_report . ' +								</div> +								<span class="corners-bottom"><span></span></span> +							</div> +							</div> +						</div> +						<div id="page-footer"> +							Powered by <a href="https://www.phpbb.com/">phpBB</a>® Forum Software © phpBB Group +						</div> +					</div> +					</body> +					</html>'; + +				exit_handler(); + +			break; + +			case 'stop': +				$endtime = explode(' ', microtime()); +				$endtime = $endtime[0] + $endtime[1]; + +				$this->sql_report .= ' + +					<table cellspacing="1"> +					<thead> +					<tr> +						<th>Query #' . $this->num_queries['total'] . '</th> +					</tr> +					</thead> +					<tbody> +					<tr> +						<td class="row3"><textarea style="font-family:\'Courier New\',monospace;width:99%" rows="5" cols="10">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query))) . '</textarea></td> +					</tr> +					</tbody> +					</table> + +					' . $this->html_hold . ' + +					<p style="text-align: center;"> +				'; + +				if ($this->query_result) +				{ +					if (preg_match('/^(UPDATE|DELETE|REPLACE)/', $query)) +					{ +						$this->sql_report .= 'Affected rows: <b>' . $this->sql_affectedrows($this->query_result) . '</b> | '; +					} +					$this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed: <b>' . sprintf('%.5f', $endtime - $this->curtime) . 's</b>'; +				} +				else +				{ +					$error = $this->sql_error(); +					$this->sql_report .= '<b style="color: red">FAILED</b> - ' . $this->sql_layer . ' Error ' . $error['code'] . ': ' . htmlspecialchars($error['message']); +				} + +				$this->sql_report .= '</p><br /><br />'; + +				$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 .= '<table cellspacing="1"><tr>'; + +					foreach (array_keys($row) as $val) +					{ +						$this->html_hold .= '<th>' . (($val) ? ucwords(str_replace('_', ' ', $val)) : ' ') . '</th>'; +					} +					$this->html_hold .= '</tr>'; +				} +				$this->html_hold .= '<tr>'; + +				$class = 'row1'; +				foreach (array_values($row) as $val) +				{ +					$class = ($class == 'row1') ? 'row2' : 'row1'; +					$this->html_hold .= '<td class="' . $class . '">' . (($val) ? $val : ' ') . '</td>'; +				} +				$this->html_hold .= '</tr>'; + +				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 .= '<table cellspacing="1"><thead><tr><th>Query results obtained from the cache</th></tr></thead><tbody><tr>'; +				$this->sql_report .= '<td class="row3"><textarea style="font-family:\'Courier New\',monospace;width:99%" rows="5" cols="10">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query))) . '</textarea></td></tr></tbody></table>'; +				$this->sql_report .= '<p style="text-align: center;">'; +				$this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed [cache]: <b style="color: ' . $color . '">' . sprintf('%.5f', ($time_cache)) . 's</b> | Elapsed [db]: <b>' . sprintf('%.5f', $time_db) . 's</b></p><br /><br />'; + +				// 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/phpbb/db/driver/firebird.php b/phpBB/phpbb/db/driver/firebird.php new file mode 100644 index 0000000000..787c28b812 --- /dev/null +++ b/phpBB/phpbb/db/driver/firebird.php @@ -0,0 +1,538 @@ +<?php +/** +* +* @package dbal +* @copyright (c) 2005 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* Firebird/Interbase Database Abstraction Layer +* Minimum Requirement is Firebird 2.1 +* @package dbal +*/ +class phpbb_db_driver_firebird extends phpbb_db_driver +{ +	var $last_query_text = ''; +	var $service_handle = false; +	var $affected_rows = 0; +	var $connect_error = ''; + +	/** +	* Connect to server +	*/ +	function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false) +	{ +		$this->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')) +			{ +				$this->sql_report('start', $query); +			} + +			$this->last_query_text = $query; +			$this->query_result = ($cache && $cache_ttl) ? $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')) +				{ +					$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 && $cache_ttl) +				{ +					$this->open_queries[(int) $this->query_result] = $this->query_result; +					$this->query_result = $cache->sql_save($this, $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')) +			{ +				$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 ($cache && $cache->sql_exists($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 ($cache && $cache->sql_exists($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/phpbb/db/driver/mssql.php b/phpBB/phpbb/db/driver/mssql.php new file mode 100644 index 0000000000..89c2c2351b --- /dev/null +++ b/phpBB/phpbb/db/driver/mssql.php @@ -0,0 +1,472 @@ +<?php +/** +* +* @package dbal +* @copyright (c) 2005 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* MSSQL Database Abstraction Layer +* Minimum Requirement is MSSQL 2000+ +* @package dbal +*/ +class phpbb_db_driver_mssql extends phpbb_db_driver +{ +	var $connect_error = ''; + +	/** +	* Connect to server +	*/ +	function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false) +	{ +		if (!function_exists('mssql_connect')) +		{ +			$this->connect_error = 'mssql_connect function does not exist, is mssql extension installed?'; +			return $this->sql_error(''); +		} + +		$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 : ''); + +		@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<br />' . $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')) +			{ +				$this->sql_report('start', $query); +			} + +			$this->query_result = ($cache && $cache_ttl) ? $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')) +				{ +					$this->sql_report('stop', $query); +				} + +				if ($cache && $cache_ttl) +				{ +					$this->open_queries[(int) $this->query_result] = $this->query_result; +					$this->query_result = $cache->sql_save($this, $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')) +			{ +				$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 ($cache && $cache->sql_exists($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 ($cache && $cache->sql_exists($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 ($cache && $cache->sql_exists($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() +	{ +		if (function_exists('mssql_get_last_message')) +		{ +			$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'] .= '<br />' . $row['message']; +				} +				@mssql_free_result($result_id); +			} +		} +		else +		{ +			$error = array( +				'message'	=> $this->connect_error, +				'code'		=> '', +			); +		} + +		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 .= '</table>'; +				} +			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/phpbb/db/driver/mssql_base.php b/phpBB/phpbb/db/driver/mssql_base.php new file mode 100644 index 0000000000..56c111c871 --- /dev/null +++ b/phpBB/phpbb/db/driver/mssql_base.php @@ -0,0 +1,65 @@ +<?php +/** +* +* @package dbal +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* MSSQL Database Base Abstraction Layer +* @package dbal + */ +abstract class phpbb_db_driver_mssql_base extends phpbb_db_driver +{ +	/** +	* {@inheritDoc} +	*/ +	public function sql_concatenate($expr1, $expr2) +	{ +		return $expr1 . ' + ' . $expr2; +	} + +	/** +	* 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; +	} +} diff --git a/phpBB/phpbb/db/driver/mssql_odbc.php b/phpBB/phpbb/db/driver/mssql_odbc.php new file mode 100644 index 0000000000..a1d1a5d5dd --- /dev/null +++ b/phpBB/phpbb/db/driver/mssql_odbc.php @@ -0,0 +1,383 @@ +<?php +/** +* +* @package dbal +* @copyright (c) 2005 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* Unified ODBC functions +* Unified ODBC functions support any database having ODBC driver, for example Adabas D, IBM DB2, iODBC, Solid, Sybase SQL Anywhere... +* Here we only support MSSQL Server 2000+ because of the provided schema +* +* @note number of bytes returned for returning data depends on odbc.defaultlrl php.ini setting. +* If it is limited to 4K for example only 4K of data is returned max, resulting in incomplete theme data for example. +* @note odbc.defaultbinmode may affect UTF8 characters +* +* @package dbal +*/ +class phpbb_db_driver_mssql_odbc extends phpbb_db_driver_mssql_base +{ +	var $last_query_text = ''; +	var $connect_error = ''; + +	/** +	* Connect to server +	*/ +	function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false) +	{ +		$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 : ''); + +		$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); +		} + +		if ($this->persistency) +		{ +			if (!function_exists('odbc_pconnect')) +			{ +				$this->connect_error = 'odbc_pconnect function does not exist, is odbc extension installed?'; +				return $this->sql_error(''); +			} +			$this->db_connect_id = @odbc_pconnect($this->server, $this->user, $sqlpassword); +		} +		else +		{ +			if (!function_exists('odbc_connect')) +			{ +				$this->connect_error = 'odbc_connect function does not exist, is odbc extension installed?'; +				return $this->sql_error(''); +			} +			$this->db_connect_id = @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)<br />' . $this->sql_server_version : 'MSSQL (ODBC)'; +	} + +	/** +	* 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')) +			{ +				$this->sql_report('start', $query); +			} + +			$this->last_query_text = $query; +			$this->query_result = ($cache && $cache_ttl) ? $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')) +				{ +					$this->sql_report('stop', $query); +				} + +				if ($cache && $cache_ttl) +				{ +					$this->open_queries[(int) $this->query_result] = $this->query_result; +					$this->query_result = $cache->sql_save($this, $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')) +			{ +				$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) +	{ +		global $cache; + +		if ($query_id === false) +		{ +			$query_id = $this->query_result; +		} + +		if ($cache && $cache->sql_exists($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 ($cache && $cache->sql_exists($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; +	} + +	/** +	* return sql error array +	* @access private +	*/ +	function _sql_error() +	{ +		if (function_exists('odbc_errormsg')) +		{ +			$error = array( +				'message'	=> @odbc_errormsg(), +				'code'		=> @odbc_error(), +			); +		} +		else +		{ +			$error = array( +				'message'	=> $this->connect_error, +				'code'		=> '', +			); +		} + +		return $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/phpbb/db/driver/mssqlnative.php b/phpBB/phpbb/db/driver/mssqlnative.php new file mode 100644 index 0000000000..28fc88298a --- /dev/null +++ b/phpBB/phpbb/db/driver/mssqlnative.php @@ -0,0 +1,613 @@ +<?php +/** +* +* @package dbal +* @copyright (c) 2010 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +* This is the MS SQL Server Native database abstraction layer. +* PHP mssql native driver required. +* @author Chris Pucci +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** + * Prior to version 1.1 the SQL Server Native PHP driver didn't support sqlsrv_num_rows, or cursor based seeking so we recall all rows into an array + * and maintain our own cursor index into that array. + */ +class result_mssqlnative +{ +	public function result_mssqlnative($queryresult = false) +	{ +		$this->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_mssql_base +{ +	var $m_insert_id = NULL; +	var $last_query_text = ''; +	var $query_options = array(); +	var $connect_error = ''; + +	/** +	* 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')) +		{ +			$this->connect_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'; +			return $this->sql_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 +		$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<br />' . $this->sql_server_version : 'MSSQL'; +	} + +	/** +	* {@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')) +			{ +				$this->sql_report('start', $query); +			} + +			$this->last_query_text = $query; +			$this->query_result = ($cache && $cache_ttl) ? $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')) +				{ +					$this->sql_report('stop', $query); +				} + +				if ($cache && $cache_ttl) +				{ +					$this->open_queries[(int) $this->query_result] = $this->query_result; +					$this->query_result = $cache->sql_save($this, $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')) +			{ +				$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 ($this->db_connect_id) ? @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 ($cache && $cache->sql_exists($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 ($cache->sql_exists($query_id)) +		{ +			return $cache->sql_freeresult($query_id); +		} + +		if (isset($this->open_queries[(int) $query_id])) +		{ +			unset($this->open_queries[(int) $query_id]); +			return @sqlsrv_free_stmt($query_id); +		} +		return false; +	} + +	/** +	* return sql error array +	* @access private +	*/ +	function _sql_error() +	{ +		if (function_exists('sqlsrv_errors')) +		{ +			$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(); +			} + +			$error = array( +				'message'	=> $error, +				'code'		=> $code, +			); +		} +		else +		{ +			$error = array( +				'message'	=> $this->connect_error, +				'code'		=> '', +			); +		} + +		return $error; +	} + +	/** +	* 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 .= '</table>'; +				} +			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/phpbb/db/driver/mysql.php b/phpBB/phpbb/db/driver/mysql.php new file mode 100644 index 0000000000..f3744ac09d --- /dev/null +++ b/phpBB/phpbb/db/driver/mysql.php @@ -0,0 +1,472 @@ +<?php +/** +* +* @package dbal +* @copyright (c) 2005 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* MySQL4 Database Abstraction Layer +* Compatible with: +* MySQL 3.23+ +* MySQL 4.0+ +* MySQL 4.1+ +* MySQL 5.0+ +* @package dbal +*/ +class phpbb_db_driver_mysql extends phpbb_db_driver_mysql_base +{ +	var $multi_insert = true; +	var $connect_error = ''; + +	/** +	* Connect to server +	* @access public +	*/ +	function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false) +	{ +		$this->persistency = $persistency; +		$this->user = $sqluser; +		$this->server = $sqlserver . (($port) ? ':' . $port : ''); +		$this->dbname = $database; + +		$this->sql_layer = 'mysql4'; + +		if ($this->persistency) +		{ +			if (!function_exists('mysql_pconnect')) +			{ +				$this->connect_error = 'mysql_pconnect function does not exist, is mysql extension installed?'; +				return $this->sql_error(''); +			} +			$this->db_connect_id = @mysql_pconnect($this->server, $this->user, $sqlpassword); +		} +		else +		{ +			if (!function_exists('mysql_connect')) +			{ +				$this->connect_error = 'mysql_connect function does not exist, is mysql extension installed?'; +				return $this->sql_error(''); +			} +			$this->db_connect_id = @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; +	} + +	/** +	* 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')) +			{ +				$this->sql_report('start', $query); +			} + +			$this->query_result = ($cache && $cache_ttl) ? $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')) +				{ +					$this->sql_report('stop', $query); +				} + +				if ($cache && $cache_ttl) +				{ +					$this->open_queries[(int) $this->query_result] = $this->query_result; +					$this->query_result = $cache->sql_save($this, $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')) +			{ +				$this->sql_report('fromcache', $query); +			} +		} +		else +		{ +			return false; +		} + +		return $this->query_result; +	} + +	/** +	* 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 ($cache && $cache->sql_exists($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 ($cache && $cache->sql_exists($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 ($cache && $cache->sql_exists($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); +	} + +	/** +	* return sql error array +	* @access private +	*/ +	function _sql_error() +	{ +		if ($this->db_connect_id) +		{ +			$error = array( +				'message'	=> @mysql_error($this->db_connect_id), +				'code'		=> @mysql_errno($this->db_connect_id), +			); +		} +		else if (function_exists('mysql_error')) +		{ +			$error = array( +				'message'	=> @mysql_error(), +				'code'		=> @mysql_errno(), +			); +		} +		else +		{ +			$error = array( +				'message'	=> $this->connect_error, +				'code'		=> '', +			); +		} + +		return $error; +	} + +	/** +	* 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 .= '</table>'; +					} + +					if ($test_prof) +					{ +						$html_table = false; + +						// get the last profile +						if ($result = @mysql_query('SHOW PROFILE ALL;', $this->db_connect_id)) +						{ +							$this->html_hold .= '<br />'; +							while ($row = @mysql_fetch_assoc($result)) +							{ +								// make <unknown> 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 .= '</table>'; +						} + +						@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/phpbb/db/driver/mysql_base.php b/phpBB/phpbb/db/driver/mysql_base.php new file mode 100644 index 0000000000..ba44ea61aa --- /dev/null +++ b/phpBB/phpbb/db/driver/mysql_base.php @@ -0,0 +1,145 @@ +<?php +/** +* +* @package dbal +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* Abstract MySQL Database Base Abstraction Layer +* @package dbal +*/ +abstract class phpbb_db_driver_mysql_base extends phpbb_db_driver +{ +	/** +	* {@inheritDoc} +	*/ +	public function sql_concatenate($expr1, $expr2) +	{ +		return 'CONCAT(' . $expr1 . ', ' . $expr2 . ')'; +	} + +	/** +	* 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); +	} + +	/** +	* 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; +	} +} diff --git a/phpBB/phpbb/db/driver/mysqli.php b/phpBB/phpbb/db/driver/mysqli.php new file mode 100644 index 0000000000..0f7a73ee6e --- /dev/null +++ b/phpBB/phpbb/db/driver/mysqli.php @@ -0,0 +1,463 @@ +<?php +/** +* +* @package dbal +* @copyright (c) 2005 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* MySQLi Database Abstraction Layer +* mysqli-extension has to be compiled with: +* MySQL 4.1+ or MySQL 5.0+ +* @package dbal +*/ +class phpbb_db_driver_mysqli extends phpbb_db_driver_mysql_base +{ +	var $multi_insert = true; +	var $connect_error = ''; + +	/** +	* Connect to server +	*/ +	function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false , $new_link = false) +	{ +		if (!function_exists('mysqli_connect')) +		{ +			$this->connect_error = 'mysqli_connect function does not exist, is mysqli extension installed?'; +			return $this->sql_error(''); +		} + +		// Mysqli extension supports persistent connection since PHP 5.3.0 +		$this->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 $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('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; +	} + +	/** +	* 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')) +			{ +				$this->sql_report('start', $query); +			} + +			$this->query_result = ($cache && $cache_ttl) ? $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')) +				{ +					$this->sql_report('stop', $query); +				} + +				if ($cache && $cache_ttl) +				{ +					$this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); +				} +			} +			else if (defined('DEBUG')) +			{ +				$this->sql_report('fromcache', $query); +			} +		} +		else +		{ +			return false; +		} + +		return $this->query_result; +	} + +	/** +	* 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 ($cache && !is_object($query_id) && $cache->sql_exists($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 ($cache && !is_object($query_id) && $cache->sql_exists($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 ($cache && !is_object($query_id) && $cache->sql_exists($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); +	} + +	/** +	* return sql error array +	* @access private +	*/ +	function _sql_error() +	{ +		if ($this->db_connect_id) +		{ +			$error = array( +				'message'	=> @mysqli_error($this->db_connect_id), +				'code'		=> @mysqli_errno($this->db_connect_id) +			); +		} +		else if (function_exists('mysqli_connect_error')) +		{ +			$error = array( +				'message'	=> @mysqli_connect_error(), +				'code'		=> @mysqli_connect_errno(), +			); +		} +		else +		{ +			$error = array( +				'message'	=> $this->connect_error, +				'code'		=> '', +			); +		} + +		return $error; +	} + +	/** +	* 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 .= '</table>'; +					} + +					if ($test_prof) +					{ +						$html_table = false; + +						// get the last profile +						if ($result = @mysqli_query($this->db_connect_id, 'SHOW PROFILE ALL;')) +						{ +							$this->html_hold .= '<br />'; +							while ($row = @mysqli_fetch_assoc($result)) +							{ +								// make <unknown> 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 .= '</table>'; +						} + +						@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/phpbb/db/driver/oracle.php b/phpBB/phpbb/db/driver/oracle.php new file mode 100644 index 0000000000..e21e07055d --- /dev/null +++ b/phpBB/phpbb/db/driver/oracle.php @@ -0,0 +1,803 @@ +<?php +/** +* +* @package dbal +* @copyright (c) 2005 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* Oracle Database Abstraction Layer +* @package dbal +*/ +class phpbb_db_driver_oracle extends phpbb_db_driver +{ +	var $last_query_text = ''; +	var $connect_error = ''; + +	/** +	* Connect to server +	*/ +	function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false) +	{ +		$this->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; +		} + +		if ($new_link) +		{ +			if (!function_exists('ocinlogon')) +			{ +				$this->connect_error = 'ocinlogon function does not exist, is oci extension installed?'; +				return $this->sql_error(''); +			} +			$this->db_connect_id = @ocinlogon($this->user, $sqlpassword, $connect, 'UTF8'); +		} +		else if ($this->persistency) +		{ +			if (!function_exists('ociplogon')) +			{ +				$this->connect_error = 'ociplogon function does not exist, is oci extension installed?'; +				return $this->sql_error(''); +			} +			$this->db_connect_id = @ociplogon($this->user, $sqlpassword, $connect, 'UTF8'); +		} +		else +		{ +			if (!function_exists('ocilogon')) +			{ +				$this->connect_error = 'ocilogon function does not exist, is oci extension installed?'; +				return $this->sql_error(''); +			} +			$this->db_connect_id = @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')) +			{ +				$this->sql_report('start', $query); +			} + +			$this->last_query_text = $query; +			$this->query_result = ($cache && $cache_ttl) ? $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')) +				{ +					$this->sql_report('stop', $query); +				} + +				if ($cache && $cache_ttl) +				{ +					$this->open_queries[(int) $this->query_result] = $this->query_result; +					$this->query_result = $cache->sql_save($this, $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')) +			{ +				$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 ($cache && $cache->sql_exists($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 ($cache && $cache->sql_exists($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 ($cache && $cache->sql_exists($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() +	{ +		if (function_exists('ocierror')) +		{ +			$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(); +			} +		} +		else +		{ +			$error = array( +				'message'	=> $this->connect_error, +				'code'		=> '', +			); +		} + +		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 .= '</table>'; +				} + +			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/phpbb/db/driver/postgres.php b/phpBB/phpbb/db/driver/postgres.php new file mode 100644 index 0000000000..14854d179d --- /dev/null +++ b/phpBB/phpbb/db/driver/postgres.php @@ -0,0 +1,491 @@ +<?php +/** +* +* @package dbal +* @copyright (c) 2005 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* PostgreSQL Database Abstraction Layer +* Minimum Requirement is Version 7.3+ +* @package dbal +*/ +class phpbb_db_driver_postgres extends phpbb_db_driver +{ +	var $last_query_text = ''; +	var $connect_error = ''; + +	/** +	* Connect to server +	*/ +	function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false) +	{ +		$connect_string = ''; + +		if ($sqluser) +		{ +			$connect_string .= "user=$sqluser "; +		} + +		if ($sqlpassword) +		{ +			$connect_string .= "password=$sqlpassword "; +		} + +		if ($sqlserver) +		{ +			// $sqlserver can carry a port separated by : for compatibility reasons +			// If $sqlserver has more than one : it's probably an IPv6 address. +			// In this case we only allow passing a port via the $port variable. +			if (substr_count($sqlserver, ':') === 1) +			{ +				list($sqlserver, $port) = explode(':', $sqlserver); +			} + +			if ($sqlserver !== 'localhost') +			{ +				$connect_string .= "host=$sqlserver "; +			} + +			if ($port) +			{ +				$connect_string .= "port=$port "; +			} +		} + +		$schema = ''; + +		if ($database) +		{ +			$this->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')) +			{ +				$this->sql_report('start', $query); +			} + +			$this->last_query_text = $query; +			$this->query_result = ($cache && $cache_ttl) ? $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')) +				{ +					$this->sql_report('stop', $query); +				} + +				if ($cache && $cache_ttl) +				{ +					$this->open_queries[(int) $this->query_result] = $this->query_result; +					$this->query_result = $cache->sql_save($this, $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')) +			{ +				$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 ($cache && $cache->sql_exists($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 ($cache && $cache->sql_exists($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 ($cache && $cache->sql_exists($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 .= '</table>'; +					} +				} + +			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/phpbb/db/driver/sqlite.php b/phpBB/phpbb/db/driver/sqlite.php new file mode 100644 index 0000000000..7188f0daa2 --- /dev/null +++ b/phpBB/phpbb/db/driver/sqlite.php @@ -0,0 +1,365 @@ +<?php +/** +* +* @package dbal +* @copyright (c) 2005 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* Sqlite Database Abstraction Layer +* Minimum Requirement: 2.8.2+ +* @package dbal +*/ +class phpbb_db_driver_sqlite extends phpbb_db_driver +{ +	var $connect_error = ''; + +	/** +	* Connect to server +	*/ +	function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false) +	{ +		$this->persistency = $persistency; +		$this->user = $sqluser; +		$this->server = $sqlserver . (($port) ? ':' . $port : ''); +		$this->dbname = $database; + +		$error = ''; +		if ($this->persistency) +		{ +			if (!function_exists('sqlite_popen')) +			{ +				$this->connect_error = 'sqlite_popen function does not exist, is sqlite extension installed?'; +				return $this->sql_error(''); +			} +			$this->db_connect_id = @sqlite_popen($this->server, 0666, $error); +		} +		else +		{ +			if (!function_exists('sqlite_open')) +			{ +				$this->connect_error = 'sqlite_open function does not exist, is sqlite extension installed?'; +				return $this->sql_error(''); +			} +			$this->db_connect_id = @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')) +			{ +				$this->sql_report('start', $query); +			} + +			$this->query_result = ($cache && $cache_ttl) ? $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')) +				{ +					$this->sql_report('stop', $query); +				} + +				if ($cache && $cache_ttl) +				{ +					$this->open_queries[(int) $this->query_result] = $this->query_result; +					$this->query_result = $cache->sql_save($this, $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')) +			{ +				$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 ($cache && $cache->sql_exists($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 ($cache && $cache->sql_exists($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 ($cache && $cache->sql_exists($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() +	{ +		if (function_exists('sqlite_error_string')) +		{ +			$error = array( +				'message'	=> @sqlite_error_string(@sqlite_last_error($this->db_connect_id)), +				'code'		=> @sqlite_last_error($this->db_connect_id), +			); +		} +		else +		{ +			$error = array( +				'message'	=> $this->connect_error, +				'code'		=> '', +			); +		} + +		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 @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/phpbb/db/migration/data/30x/3_0_1.php b/phpBB/phpbb/db/migration/data/30x/3_0_1.php new file mode 100644 index 0000000000..c996a0138a --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_1.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_1 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.1', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_1_rc1'); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.0.1')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_10.php b/phpBB/phpbb/db/migration/data/30x/3_0_10.php new file mode 100644 index 0000000000..122f93d6b4 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_10.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_10 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.10', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_10_rc3'); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.0.10')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_10_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_10_rc1.php new file mode 100644 index 0000000000..0ed05812dc --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_10_rc1.php @@ -0,0 +1,30 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_10_rc1 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.10-rc1', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_9'); +	} + +	public function update_data() +	{ +		return array( +			array('config.add', array('email_max_chunk_size', 50)), + +			array('config.update', array('version', '3.0.10-rc1')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_10_rc2.php b/phpBB/phpbb/db/migration/data/30x/3_0_10_rc2.php new file mode 100644 index 0000000000..b14b3b00aa --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_10_rc2.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_10_rc2 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.10-rc2', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_10_rc1'); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.0.10-rc2')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_10_rc3.php b/phpBB/phpbb/db/migration/data/30x/3_0_10_rc3.php new file mode 100644 index 0000000000..473057d65d --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_10_rc3.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_10_rc3 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.10-rc3', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_10_rc2'); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.0.10-rc3')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_11.php b/phpBB/phpbb/db/migration/data/30x/3_0_11.php new file mode 100644 index 0000000000..e063c699cc --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_11.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_11 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.11', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_11_rc2'); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.0.11')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_11_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_11_rc1.php new file mode 100644 index 0000000000..dddfc0e0e7 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_11_rc1.php @@ -0,0 +1,95 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_11_rc1 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.11-rc1', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_10'); +	} + +	public function update_data() +	{ +		return array( +			array('custom', array(array(&$this, 'cleanup_deactivated_styles'))), +			array('custom', array(array(&$this, 'delete_orphan_private_messages'))), + +			array('config.update', array('version', '3.0.11-rc1')), +		); +	} + +	public function cleanup_deactivated_styles() +	{ +		// Updates users having current style a deactivated one +		$sql = 'SELECT style_id +			FROM ' . STYLES_TABLE . ' +			WHERE style_active = 0'; +		$result = $this->sql_query($sql); + +		$deactivated_style_ids = array(); +		while ($style_id = $this->db->sql_fetchfield('style_id', false, $result)) +		{ +			$deactivated_style_ids[] = (int) $style_id; +		} +		$this->db->sql_freeresult($result); + +		if (!empty($deactivated_style_ids)) +		{ +			$sql = 'UPDATE ' . USERS_TABLE . ' +				SET user_style = ' . (int) $this->config['default_style'] .' +				WHERE ' . $this->db->sql_in_set('user_style', $deactivated_style_ids); +			$this->sql_query($sql); +		} +	} + +	public function delete_orphan_private_messages() +	{ +		// Delete orphan private messages +		$batch_size = 500; + +		$sql_array = array( +			'SELECT'	=> 'p.msg_id', +			'FROM'		=> array( +				PRIVMSGS_TABLE	=> 'p', +			), +			'LEFT_JOIN'	=> array( +				array( +					'FROM'	=> array(PRIVMSGS_TO_TABLE => 't'), +					'ON'	=> 'p.msg_id = t.msg_id', +				), +			), +			'WHERE'		=> 't.user_id IS NULL', +		); +		$sql = $this->db->sql_build_query('SELECT', $sql_array); + +		$result = $this->db->sql_query_limit($sql, $batch_size); + +		$delete_pms = array(); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$delete_pms[] = (int) $row['msg_id']; +		} +		$this->db->sql_freeresult($result); + +		if (!empty($delete_pms)) +		{ +			$sql = 'DELETE FROM ' . PRIVMSGS_TABLE . ' +				WHERE ' . $this->db->sql_in_set('msg_id', $delete_pms); +			$this->sql_query($sql); + +			// Return false to have the Migrator call this function again +			return false; +		} +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_11_rc2.php b/phpBB/phpbb/db/migration/data/30x/3_0_11_rc2.php new file mode 100644 index 0000000000..fac8523e8c --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_11_rc2.php @@ -0,0 +1,50 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_11_rc2 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.11-rc2', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_11_rc1'); +	} + +	public function update_schema() +	{ +		return array( +			'add_columns' => array( +				$this->table_prefix . 'profile_fields' => array( +					'field_show_novalue' => array('BOOL', 0), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'drop_columns' => array( +				$this->table_prefix . 'profile_fields' => array( +					'field_show_novalue', +				), +			), +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.0.11-rc2')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_12_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_12_rc1.php new file mode 100644 index 0000000000..6a31a51201 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_12_rc1.php @@ -0,0 +1,123 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +/** @todo DROP LOGIN_ATTEMPT_TABLE.attempt_id in 3.0.12-RC1 **/ + +class phpbb_db_migration_data_30x_3_0_12_rc1 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.12-rc1', '>='); +	} + + 	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_11'); +	} + +	public function update_data() +	{ +		return array( +			array('custom', array(array(&$this, 'update_module_auth'))), +			array('custom', array(array(&$this, 'update_bots'))), +			array('custom', array(array(&$this, 'disable_bots_from_receiving_pms'))), + +			array('config.update', array('version', '3.0.12-rc1')), +		); +	} + +	public function disable_bots_from_receiving_pms() +	{ +		// Disable receiving pms for bots +		$sql = 'SELECT user_id +			FROM ' . BOTS_TABLE; +		$result = $this->db->sql_query($sql); + +		$bot_user_ids = array(); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$bot_user_ids[] = (int) $row['user_id']; +		} +		$this->db->sql_freeresult($result); + +		if (!empty($bot_user_ids)) +		{ +			$sql = 'UPDATE ' . USERS_TABLE . ' +				SET user_allow_pm = 0 +				WHERE ' . $this->db->sql_in_set('user_id', $bot_user_ids); +			$this->sql_query($sql); +		} +	} + +	public function update_module_auth() +	{ +		$sql = 'UPDATE ' . MODULES_TABLE . ' +			SET module_auth = \'acl_u_sig\' +			WHERE module_class = \'ucp\' +				AND module_basename = \'profile\' +				AND module_mode = \'signature\''; +		$this->sql_query($sql); +	} + +	public function update_bots() +	{ +		// Update bots +		if (!function_exists('user_delete')) +		{ +			include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); +		} + +		$bots_updates = array( +			// Bot Deletions +			'NG-Search [Bot]'		=> false, +			'Nutch/CVS [Bot]'		=> false, +			'OmniExplorer [Bot]'	=> false, +			'Seekport [Bot]'		=> false, +			'Synoo [Bot]'			=> false, +			'WiseNut [Bot]'			=> false, + +			// Bot Updates +			// Bot name to bot user agent map +			'Baidu [Spider]'	=> 'Baiduspider', +			'Exabot [Bot]'		=> 'Exabot', +			'Voyager [Bot]'		=> 'voyager/', +			'W3C [Validator]'	=> 'W3C_Validator', +		); + +		foreach ($bots_updates as $bot_name => $bot_agent) +		{ +			$sql = 'SELECT user_id +				FROM ' . USERS_TABLE . ' +				WHERE user_type = ' . USER_IGNORE . " +					AND username_clean = '" . $this->db->sql_escape(utf8_clean_string($bot_name)) . "'"; +			$result = $this->db->sql_query($sql); +			$bot_user_id = (int) $this->db->sql_fetchfield('user_id'); +			$this->db->sql_freeresult($result); + +			if ($bot_user_id) +			{ +				if ($bot_agent === false) +				{ +					$sql = 'DELETE FROM ' . BOTS_TABLE . " +						WHERE user_id = $bot_user_id"; +					$this->sql_query($sql); + +					user_delete('remove', $bot_user_id); +				} +				else +				{ +					$sql = 'UPDATE ' . BOTS_TABLE . " +						SET bot_agent = '" .  $this->db->sql_escape($bot_agent) . "' +						WHERE user_id = $bot_user_id"; +					$this->sql_query($sql); +				} +			} +		} +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_1_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_1_rc1.php new file mode 100644 index 0000000000..562ccf077c --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_1_rc1.php @@ -0,0 +1,108 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_1_rc1 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.1-rc1', '>='); +	} + +	public function update_schema() +	{ +		return array( +			'add_columns' => array( +				$this->table_prefix . 'forums' => array( +					'display_subforum_list' => array('BOOL', 1), +				), +				$this->table_prefix . 'sessions' => array( +					'session_forum_id' => array('UINT', 0), +				), +			), +			'drop_keys' => array( +				$this->table_prefix . 'groups' => array( +					'group_legend', +				), +			), +			'add_index' => array( +				$this->table_prefix . 'sessions' => array( +					'session_forum_id' => array('session_forum_id'), +				), +				$this->table_prefix . 'groups' => array( +					'group_legend_name' => array('group_legend', 'group_name'), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'drop_columns' => array( +				$this->table_prefix . 'forums' => array( +					'display_subforum_list', +				), +				$this->table_prefix . 'sessions' => array( +					'session_forum_id', +				), +			), +			'add_index' => array( +				$this->table_prefix . 'groups' => array( +					'group_legend' => array('group_legend'), +				), +			), +			'drop_keys' => array( +				$this->table_prefix . 'sessions' => array( +					'session_forum_id', +				), +				$this->table_prefix . 'groups' => array( +					'group_legend_name', +				), +			), +		); +	} + +	public function update_data() +	{ +		return array( +			array('custom', array(array(&$this, 'fix_unset_last_view_time'))), +			array('custom', array(array(&$this, 'reset_smiley_size'))), + +			array('config.update', array('version', '3.0.1-rc1')), +		); +	} + +	public function fix_unset_last_view_time() +	{ +		$sql = 'UPDATE ' . $this->table_prefix . "topics +			SET topic_last_view_time = topic_last_post_time +			WHERE topic_last_view_time = 0"; +		$this->sql_query($sql); +	} + +	public function reset_smiley_size() +	{ +		// Update smiley sizes +		$smileys = array('icon_e_surprised.gif', 'icon_eek.gif', 'icon_cool.gif', 'icon_lol.gif', 'icon_mad.gif', 'icon_razz.gif', 'icon_redface.gif', 'icon_cry.gif', 'icon_evil.gif', 'icon_twisted.gif', 'icon_rolleyes.gif', 'icon_exclaim.gif', 'icon_question.gif', 'icon_idea.gif', 'icon_arrow.gif', 'icon_neutral.gif', 'icon_mrgreen.gif', 'icon_e_ugeek.gif'); + +		foreach ($smileys as $smiley) +		{ +			if (file_exists($this->phpbb_root_path . 'images/smilies/' . $smiley)) +			{ +				list($width, $height) = getimagesize($this->phpbb_root_path . 'images/smilies/' . $smiley); + +				$sql = 'UPDATE ' . SMILIES_TABLE . ' +					SET smiley_width = ' . $width . ', smiley_height = ' . $height . " +					WHERE smiley_url = '" . $this->db->sql_escape($smiley) . "'"; + +				$this->sql_query($sql); +			} +		} +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_2.php b/phpBB/phpbb/db/migration/data/30x/3_0_2.php new file mode 100644 index 0000000000..eed5acef82 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_2.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_2 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.2', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_2_rc2'); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.0.2')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_2_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_2_rc1.php new file mode 100644 index 0000000000..a960e90765 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_2_rc1.php @@ -0,0 +1,32 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_2_rc1 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.2-rc1', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_1'); +	} + +	public function update_data() +	{ +		return array( +			array('config.add', array('referer_validation', '1')), +			array('config.add', array('check_attachment_content', '1')), +			array('config.add', array('mime_triggers', 'body|head|html|img|plaintext|a href|pre|script|table|title')), + +			array('config.update', array('version', '3.0.2-rc1')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_2_rc2.php b/phpBB/phpbb/db/migration/data/30x/3_0_2_rc2.php new file mode 100644 index 0000000000..8917dfea77 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_2_rc2.php @@ -0,0 +1,80 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_2_rc2 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.2-rc2', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_2_rc1'); +	} + +	public function update_schema() +	{ +		return array( +			'change_columns' => array( +				$this->table_prefix . 'drafts' => array( +					'draft_subject' => array('STEXT_UNI', ''), +				), +				$this->table_prefix . 'forums' => array( +					'forum_last_post_subject' => array('STEXT_UNI', ''), +				), +				$this->table_prefix . 'posts' => array( +					'post_subject' => array('STEXT_UNI', '', 'true_sort'), +				), +				$this->table_prefix . 'privmsgs' => array( +					'message_subject' => array('STEXT_UNI', ''), +				), +				$this->table_prefix . 'topics' => array( +					'topic_title' => array('STEXT_UNI', '', 'true_sort'), +					'topic_last_post_subject' => array('STEXT_UNI', ''), +				), +			), +			'drop_keys' => array( +				$this->table_prefix . 'sessions' => array( +					'session_forum_id', +				), +			), +			'add_index' => array( +				$this->table_prefix . 'sessions' => array( +					'session_fid' => array('session_forum_id'), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'add_index' => array( +				$this->table_prefix . 'sessions' => array( +					'session_forum_id' => array( +						'session_forum_id', +					), +				), +			), +			'drop_keys' => array( +				$this->table_prefix . 'sessions' => array( +					'session_fid', +				), +			), +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.0.2-rc2')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_3.php b/phpBB/phpbb/db/migration/data/30x/3_0_3.php new file mode 100644 index 0000000000..8984cf7b76 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_3.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_3 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.3', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_3_rc1'); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.0.3')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_3_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_3_rc1.php new file mode 100644 index 0000000000..4b102e1a2e --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_3_rc1.php @@ -0,0 +1,83 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_3_rc1 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.3-rc1', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_2'); +	} + +	public function update_schema() +	{ +		return array( +			'add_columns' => array( +				$this->table_prefix . 'styles_template' => array( +					'template_inherits_id' => array('UINT:4', 0), +					'template_inherit_path' => array('VCHAR', ''), +				), +				$this->table_prefix . 'groups' => array( +					'group_max_recipients' => array('UINT', 0), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'drop_columns' => array( +				$this->table_prefix . 'styles_template' => array( +					'template_inherits_id', +					'template_inherit_path', +				), +				$this->table_prefix . 'groups' => array( +					'group_max_recipients', +				), +			), +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.add', array('enable_queue_trigger', '0')), +			array('config.add', array('queue_trigger_posts', '3')), +			array('config.add', array('pm_max_recipients', '0')), +			array('custom', array(array(&$this, 'set_group_default_max_recipients'))), +			array('config.add', array('dbms_version', $this->db->sql_server_info(true))), +			array('permission.add', array('u_masspm_group', true, 'u_masspm')), +			array('custom', array(array(&$this, 'correct_acp_email_permissions'))), + +			array('config.update', array('version', '3.0.3-rc1')), +		); +	} + +	public function correct_acp_email_permissions() +	{ +		$sql = 'UPDATE ' . $this->table_prefix . 'modules +			SET module_auth = \'acl_a_email && cfg_email_enable\' +			WHERE module_class = \'acp\' +				AND module_basename = \'email\''; +		$this->sql_query($sql); +	} + +	public function set_group_default_max_recipients() +	{ +		// Set maximum number of recipients for the registered users, bots, guests group +		$sql = 'UPDATE ' . GROUPS_TABLE . ' SET group_max_recipients = 5 +			WHERE ' . $this->db->sql_in_set('group_name', array('GUESTS', 'REGISTERED', 'REGISTERED_COPPA', 'BOTS')); +		$this->sql_query($sql); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_4.php b/phpBB/phpbb/db/migration/data/30x/3_0_4.php new file mode 100644 index 0000000000..9a0c132e78 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_4.php @@ -0,0 +1,49 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_4 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.4', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_4_rc1'); +	} + +	public function update_data() +	{ +		return array( +			array('custom', array(array(&$this, 'rename_log_delete_topic'))), + +			array('config.update', array('version', '3.0.4')), +		); +	} + +	public function rename_log_delete_topic() +	{ +		if ($this->db->sql_layer == 'oracle') +		{ +			// log_operation is CLOB - but we can change this later +			$sql = 'UPDATE ' . $this->table_prefix . "log +				SET log_operation = 'LOG_DELETE_TOPIC' +				WHERE log_operation LIKE 'LOG_TOPIC_DELETED'"; +			$this->sql_query($sql); +		} +		else +		{ +			$sql = 'UPDATE ' . $this->table_prefix . "log +				SET log_operation = 'LOG_DELETE_TOPIC' +				WHERE log_operation = 'LOG_TOPIC_DELETED'"; +			$this->sql_query($sql); +		} +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_4_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_4_rc1.php new file mode 100644 index 0000000000..8ad75a557b --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_4_rc1.php @@ -0,0 +1,123 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_4_rc1 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.4-rc1', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_3'); +	} + +	public function update_schema() +	{ +		return array( +			'add_columns' => array( +				$this->table_prefix . 'profile_fields' => array( +					'field_show_profile' => array('BOOL', 0), +				), +			), +			'change_columns' => array( +				$this->table_prefix . 'styles' => array( +					'style_id' => array('UINT', NULL, 'auto_increment'), +					'template_id' => array('UINT', 0), +					'theme_id' => array('UINT', 0), +					'imageset_id' => array('UINT', 0), +				), +				$this->table_prefix . 'styles_imageset' => array( +					'imageset_id' => array('UINT', NULL, 'auto_increment'), +				), +				$this->table_prefix . 'styles_imageset_data' => array( +					'image_id' => array('UINT', NULL, 'auto_increment'), +					'imageset_id' => array('UINT', 0), +				), +				$this->table_prefix . 'styles_theme' => array( +					'theme_id' => array('UINT', NULL, 'auto_increment'), +				), +				$this->table_prefix . 'styles_template' => array( +					'template_id' => array('UINT', NULL, 'auto_increment'), +				), +				$this->table_prefix . 'styles_template_data' => array( +					'template_id' => array('UINT', 0), +				), +				$this->table_prefix . 'forums' => array( +					'forum_style' => array('UINT', 0), +				), +				$this->table_prefix . 'users' => array( +					'user_style' => array('UINT', 0), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'drop_columns' => array( +				$this->table_prefix . 'profile_fields' => array( +					'field_show_profile', +				), +			), +		); +	} + +	public function update_data() +	{ +		return array( +			array('custom', array(array(&$this, 'update_custom_profile_fields'))), + +			array('config.update', array('version', '3.0.4-rc1')), +		); +	} + +	public function update_custom_profile_fields() +	{ +		// Update the Custom Profile Fields based on previous settings to the new format +		$sql = 'SELECT field_id, field_required, field_show_on_reg, field_hide +				FROM ' . PROFILE_FIELDS_TABLE; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$sql_ary = array( +				'field_required'	=> 0, +				'field_show_on_reg'	=> 0, +				'field_hide'		=> 0, +				'field_show_profile'=> 0, +			); + +			if ($row['field_required']) +			{ +				$sql_ary['field_required'] = $sql_ary['field_show_on_reg'] = $sql_ary['field_show_profile'] = 1; +			} +			else if ($row['field_show_on_reg']) +			{ +				$sql_ary['field_show_on_reg'] = $sql_ary['field_show_profile'] = 1; +			} +			else if ($row['field_hide']) +			{ +				// Only administrators and moderators can see this CPF, if the view is enabled, they can see it, otherwise just admins in the acp_users module +				$sql_ary['field_hide'] = 1; +			} +			else +			{ +				// equivelant to "none", which is the "Display in user control panel" option +				$sql_ary['field_show_profile'] = 1; +			} + +			$this->sql_query('UPDATE ' . $this->table_prefix . 'profile_fields SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' WHERE field_id = ' . $row['field_id'], $errored, $error_ary); +		} + +		$this->db->sql_freeresult($result); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_5.php b/phpBB/phpbb/db/migration/data/30x/3_0_5.php new file mode 100644 index 0000000000..16d2dee457 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_5.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_5 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.5', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_5_rc1part2'); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.0.5')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1.php new file mode 100644 index 0000000000..ea17cc1e31 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1.php @@ -0,0 +1,124 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_5_rc1 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.5-rc1', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_4'); +	} + +	public function update_schema() +	{ +		return array( +			'change_columns' => array( +				$this->table_prefix . 'forums' => array( +					'forum_style' => array('UINT', 0), +				), +			), +		); +	} + +	public function update_data() +	{ +		$search_indexing_state = $this->config['search_indexing_state']; + +		return array( +			array('config.add', array('captcha_gd_wave', 0)), +			array('config.add', array('captcha_gd_3d_noise', 1)), +			array('config.add', array('captcha_gd_fonts', 1)), +			array('config.add', array('confirm_refresh', 1)), +			array('config.add', array('max_num_search_keywords', 10)), +			array('config.remove', array('search_indexing_state')), +			array('config.add', array('search_indexing_state', $search_indexing_state, true)), +			array('custom', array(array(&$this, 'hash_old_passwords'))), +			array('custom', array(array(&$this, 'update_ichiro_bot'))), +		); +	} + +	public function hash_old_passwords() +	{ +		$sql = 'SELECT user_id, user_password +				FROM ' . $this->table_prefix . 'users +				WHERE user_pass_convert = 1'; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if (strlen($row['user_password']) == 32) +			{ +				$sql_ary = array( +					'user_password'	=> phpbb_hash($row['user_password']), +				); + +				$this->sql_query('UPDATE ' . $this->table_prefix . 'users SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' WHERE user_id = ' . $row['user_id']); +			} +		} +		$this->db->sql_freeresult($result); +	} + +	public function update_ichiro_bot() +	{ +		// Adjust bot entry +		$sql = 'UPDATE ' . $this->table_prefix . "bots +			SET bot_agent = 'ichiro/' +			WHERE bot_agent = 'ichiro/2'"; +		$this->sql_query($sql); +	} + +	public function remove_duplicate_auth_options() +	{ +		// Before we are able to add a unique key to auth_option, we need to remove duplicate entries +		$sql = 'SELECT auth_option +			FROM ' . $this->table_prefix . 'acl_options +			GROUP BY auth_option +			HAVING COUNT(*) >= 2'; +		$result = $this->db->sql_query($sql); + +		$auth_options = array(); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$auth_options[] = $row['auth_option']; +		} +		$this->db->sql_freeresult($result); + +		// Remove specific auth options +		if (!empty($auth_options)) +		{ +			foreach ($auth_options as $option) +			{ +				// Select auth_option_ids... the largest id will be preserved +				$sql = 'SELECT auth_option_id +					FROM ' . ACL_OPTIONS_TABLE . " +					WHERE auth_option = '" . $db->sql_escape($option) . "' +					ORDER BY auth_option_id DESC"; +				// sql_query_limit not possible here, due to bug in postgresql layer +				$result = $this->db->sql_query($sql); + +				// Skip first row, this is our original auth option we want to preserve +				$row = $this->db->sql_fetchrow($result); + +				while ($row = $this->db->sql_fetchrow($result)) +				{ +					// Ok, remove this auth option... +					$this->sql_query('DELETE FROM ' . ACL_OPTIONS_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id']); +					$this->sql_query('DELETE FROM ' . ACL_ROLES_DATA_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id']); +					$this->sql_query('DELETE FROM ' . ACL_GROUPS_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id']); +					$this->sql_query('DELETE FROM ' . ACL_USERS_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id']); +				} +				$this->db->sql_freeresult($result); +			} +		} +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1part2.php b/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1part2.php new file mode 100644 index 0000000000..8538347b1a --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_5_rc1part2.php @@ -0,0 +1,42 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_5_rc1part2 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.5-rc1', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_5_rc1'); +	} + +	public function update_schema() +	{ +		return array( +			'drop_keys'			=> array( +				$this->table_prefix . 'acl_options'		=> array('auth_option'), +			), +			'add_unique_index'	=> array( +				$this->table_prefix . 'acl_options'		=> array( +					'auth_option'		=> array('auth_option'), +				), +			), +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.0.5-rc1')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_6.php b/phpBB/phpbb/db/migration/data/30x/3_0_6.php new file mode 100644 index 0000000000..bb651dc7cd --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_6.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_6 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.6', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_6_rc4'); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.0.6')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_6_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc1.php new file mode 100644 index 0000000000..38c282ebf0 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc1.php @@ -0,0 +1,324 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_6_rc1 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.6-rc1', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_5'); +	} + +	public function update_schema() +	{ +		return array( +			'add_columns' => array( +				$this->table_prefix . 'confirm' => array( +					'attempts' => array('UINT', 0), +				), +				$this->table_prefix . 'users' => array( +					'user_new' => array('BOOL', 1), +					'user_reminded' => array('TINT:4', 0), +					'user_reminded_time' => array('TIMESTAMP', 0), +				), +				$this->table_prefix . 'groups' => array( +					'group_skip_auth' => array('BOOL', 0, 'after' => 'group_founder_manage'), +				), +				$this->table_prefix . 'privmsgs' => array( +					'message_reported' => array('BOOL', 0), +				), +				$this->table_prefix . 'reports' => array( +					'pm_id' => array('UINT', 0), +				), +				$this->table_prefix . 'profile_fields'	=> array( +					'field_show_on_vt' => array('BOOL', 0), +				), +				$this->table_prefix . 'forums' => array( +					'forum_options' => array('UINT:20', 0), +				), +			), +			'change_columns' => array( +				$this->table_prefix . 'users' => array( +					'user_options' => array('UINT:11', 230271), +				), +			), +			'add_index' => array( +				$this->table_prefix . 'reports' => array( +					'post_id' => array('post_id'), +					'pm_id' => array('pm_id'), +				), +				$this->table_prefix . 'posts' => array( +					'post_username' => array('post_username:255'), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'drop_columns' => array( +				$this->table_prefix . 'confirm' => array( +					'attempts', +				), +				$this->table_prefix . 'users' => array( +					'user_new', +					'user_reminded', +					'user_reminded_time', +				), +				$this->table_prefix . 'groups' => array( +					'group_skip_auth', +				), +				$this->table_prefix . 'privmsgs' => array( +					'message_reported', +				), +				$this->table_prefix . 'reports' => array( +					'pm_id', +				), +				$this->table_prefix . 'profile_fields'	=> array( +					'field_show_on_vt', +				), +				$this->table_prefix . 'forums' => array( +					'forum_options', +				), +			), +			'drop_keys' => array( +				$this->table_prefix . 'reports' => array( +					'post_id', +					'pm_id', +				), +				$this->table_prefix . 'posts' => array( +					'post_username', +				), +			), +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.add', array('captcha_plugin', 'phpbb_captcha_nogd')), +			array('if', array( +				($this->config['captcha_gd']), +				array('config.update', array('captcha_plugin', 'phpbb_captcha_gd')), +			)), + +			array('config.add', array('feed_enable', 0)), +			array('config.add', array('feed_limit', 10)), +			array('config.add', array('feed_overall_forums', 1)), +			array('config.add', array('feed_overall_forums_limit', 15)), +			array('config.add', array('feed_overall_topics', 0)), +			array('config.add', array('feed_overall_topics_limit', 15)), +			array('config.add', array('feed_forum', 1)), +			array('config.add', array('feed_topic', 1)), +			array('config.add', array('feed_item_statistics', 1)), + +			array('config.add', array('smilies_per_page', 50)), +			array('config.add', array('allow_pm_report', 1)), +			array('config.add', array('min_post_chars', 1)), +			array('config.add', array('allow_quick_reply', 1)), +			array('config.add', array('new_member_post_limit', 0)), +			array('config.add', array('new_member_group_default', 0)), +			array('config.add', array('delete_time', $this->config['edit_time'])), + +			array('config.add', array('allow_avatar', 0)), +			array('if', array( +				($this->config['allow_avatar_upload'] || $this->config['allow_avatar_local'] || $this->config['allow_avatar_remote']), +				array('config.update', array('allow_avatar', 1)), +			)), +			array('config.add', array('allow_avatar_remote_upload', 0)), +			array('if', array( +				($this->config['allow_avatar_remote'] && $this->config['allow_avatar_upload']), +				array('config.update', array('allow_avatar_remote_upload', 1)), +			)), + +			array('module.add', array( +				'acp', +				'ACP_BOARD_CONFIGURATION', +				array( +					'module_basename'	=> 'acp_board', +					'modes'				=> array('feed'), +				), +			)), +			array('module.add', array( +				'acp', +				'ACP_CAT_USERS', +				array( +					'module_basename'	=> 'acp_users', +					'modes'				=> array('warnings'), +				), +			)), +			array('module.add', array( +				'acp', +				'ACP_SERVER_CONFIGURATION', +				array( +					'module_basename'	=> 'acp_send_statistics', +					'modes'				=> array('send_statistics'), +				), +			)), +			array('module.add', array( +				'acp', +				'ACP_FORUM_BASED_PERMISSIONS', +				array( +					'module_basename'	=> 'acp_permissions', +					'modes'				=> array('setting_forum_copy'), +				), +			)), +			array('module.add', array( +				'mcp', +				'MCP_REPORTS', +				array( +					'module_basename'	=> 'mcp_pm_reports', +					'modes'				=> array('pm_reports','pm_reports_closed','pm_report_details'), +				), +			)), +			array('custom', array(array(&$this, 'add_newly_registered_group'))), +			array('custom', array(array(&$this, 'set_user_options_default'))), + +			array('config.update', array('version', '3.0.6-rc1')), +		); +	} + +	public function set_user_options_default() +	{ +		// 229376 is the added value to enable all three signature options +		$sql = 'UPDATE ' . USERS_TABLE . ' SET user_options = user_options + 229376'; +		$this->sql_query($sql); +	} + +	public function add_newly_registered_group() +	{ +		// Add newly_registered group... but check if it already exists (we always supported running the updater on any schema) +		$sql = 'SELECT group_id +			FROM ' . GROUPS_TABLE . " +			WHERE group_name = 'NEWLY_REGISTERED'"; +		$result = $this->db->sql_query($sql); +		$group_id = (int) $this->db->sql_fetchfield('group_id'); +		$this->db->sql_freeresult($result); + +		if (!$group_id) +		{ +			$sql = 'INSERT INTO ' .  GROUPS_TABLE . " (group_name, group_type, group_founder_manage, group_colour, group_legend, group_avatar, group_desc, group_desc_uid, group_max_recipients) VALUES ('NEWLY_REGISTERED', 3, 0, '', 0, '', '', '', 5)"; +			$this->sql_query($sql); + +			$group_id = $this->db->sql_nextid(); +		} + +		// Insert new user role... at the end of the chain +		$sql = 'SELECT role_id +			FROM ' . ACL_ROLES_TABLE . " +			WHERE role_name = 'ROLE_USER_NEW_MEMBER' +				AND role_type = 'u_'"; +		$result = $this->db->sql_query($sql); +		$u_role = (int) $this->db->sql_fetchfield('role_id'); +		$this->db->sql_freeresult($result); + +		if (!$u_role) +		{ +			$sql = 'SELECT MAX(role_order) as max_order_id +				FROM ' . ACL_ROLES_TABLE . " +				WHERE role_type = 'u_'"; +			$result = $this->db->sql_query($sql); +			$next_order_id = (int) $this->db->sql_fetchfield('max_order_id'); +			$this->db->sql_freeresult($result); + +			$next_order_id++; + +			$sql = 'INSERT INTO ' . ACL_ROLES_TABLE . " (role_name, role_description, role_type, role_order) VALUES ('ROLE_USER_NEW_MEMBER', 'ROLE_DESCRIPTION_USER_NEW_MEMBER', 'u_', $next_order_id)"; +			$this->sql_query($sql); +			$u_role = $this->db->sql_nextid(); + +			// Now add the correct data to the roles... +			// The standard role says that new users are not able to send a PM, Mass PM, are not able to PM groups +			$sql = 'INSERT INTO ' . ACL_ROLES_DATA_TABLE . " (role_id, auth_option_id, auth_setting) SELECT $u_role, auth_option_id, 0 FROM " . ACL_OPTIONS_TABLE . " WHERE auth_option LIKE 'u_%' AND auth_option IN ('u_sendpm', 'u_masspm', 'u_masspm_group')"; +			$this->sql_query($sql); + +			// Add user role to group +			$sql = 'INSERT INTO ' . ACL_GROUPS_TABLE . " (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES ($group_id, 0, 0, $u_role, 0)"; +			$this->sql_query($sql); +		} + +		// Insert new forum role +		$sql = 'SELECT role_id +			FROM ' . ACL_ROLES_TABLE . " +			WHERE role_name = 'ROLE_FORUM_NEW_MEMBER' +				AND role_type = 'f_'"; +		$result = $this->db->sql_query($sql); +		$f_role = (int) $this->db->sql_fetchfield('role_id'); +		$this->db->sql_freeresult($result); + +		if (!$f_role) +		{ +			$sql = 'SELECT MAX(role_order) as max_order_id +				FROM ' . ACL_ROLES_TABLE . " +				WHERE role_type = 'f_'"; +			$result = $this->db->sql_query($sql); +			$next_order_id = (int) $this->db->sql_fetchfield('max_order_id'); +			$this->db->sql_freeresult($result); + +			$next_order_id++; + +			$sql = 'INSERT INTO ' . ACL_ROLES_TABLE . " (role_name, role_description, role_type, role_order) VALUES  ('ROLE_FORUM_NEW_MEMBER', 'ROLE_DESCRIPTION_FORUM_NEW_MEMBER', 'f_', $next_order_id)"; +			$this->sql_query($sql); +			$f_role = $this->db->sql_nextid(); + +			$sql = 'INSERT INTO ' . ACL_ROLES_DATA_TABLE . " (role_id, auth_option_id, auth_setting) SELECT $f_role, auth_option_id, 0 FROM " . ACL_OPTIONS_TABLE . " WHERE auth_option LIKE 'f_%' AND auth_option IN ('f_noapprove')"; +			$this->sql_query($sql); +		} + +		// Set every members user_new column to 0 (old users) only if there is no one yet (this makes sure we do not execute this more than once) +		$sql = 'SELECT 1 +			FROM ' . USERS_TABLE . ' +			WHERE user_new = 0'; +		$result = $this->db->sql_query_limit($sql, 1); +		$row = $this->db->sql_fetchrow($result); +		$this->db->sql_freeresult($result); + +		if (!$row) +		{ +			$sql = 'UPDATE ' . USERS_TABLE . ' SET user_new = 0'; +			$this->sql_query($sql); +		} + +		// To mimick the old "feature" we will assign the forum role to every forum, regardless of the setting (this makes sure there are no "this does not work!!!! YUO!!!" posts... +		// Check if the role is already assigned... +		$sql = 'SELECT forum_id +			FROM ' . ACL_GROUPS_TABLE . ' +			WHERE group_id = ' . $group_id . ' +				AND auth_role_id = ' . $f_role; +		$result = $this->db->sql_query($sql); +		$is_options = (int) $this->db->sql_fetchfield('forum_id'); +		$this->db->sql_freeresult($result); + +		// Not assigned at all... :/ +		if (!$is_options) +		{ +			// Get postable forums +			$sql = 'SELECT forum_id +				FROM ' . FORUMS_TABLE . ' +				WHERE forum_type != ' . FORUM_LINK; +			$result = $this->db->sql_query($sql); + +			while ($row = $this->db->sql_fetchrow($result)) +			{ +				$this->sql_query('INSERT INTO ' . ACL_GROUPS_TABLE . ' (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (' . $group_id . ', ' . (int) $row['forum_id'] . ', 0, ' . $f_role . ', 0)'); +			} +			$this->db->sql_freeresult($result); +		} + +		// Clear permissions... +		include_once($this->phpbb_root_path . 'includes/acp/auth.' . $this->php_ext); +		$auth_admin = new auth_admin(); +		$auth_admin->acl_clear_prefetch(); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_6_rc2.php b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc2.php new file mode 100644 index 0000000000..a939dbd489 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc2.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_6_rc2 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.6-rc2', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_6_rc1'); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.0.6-rc2')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_6_rc3.php b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc3.php new file mode 100644 index 0000000000..b3f09d8ab8 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc3.php @@ -0,0 +1,40 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_6_rc3 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.6-rc3', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_6_rc2'); +	} + +	public function update_data() +	{ +		return array( +			array('custom', array(array(&$this, 'update_cp_fields'))), + +			array('config.update', array('version', '3.0.6-rc3')), +		); +	} + +	public function update_cp_fields() +	{ +		// Update the Custom Profile Fields based on previous settings to the new format +		$sql = 'UPDATE ' . PROFILE_FIELDS_TABLE . ' +			SET field_show_on_vt = 1 +			WHERE field_hide = 0 +				AND (field_required = 1 OR field_show_on_reg = 1 OR field_show_profile = 1)'; +		$this->sql_query($sql); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_6_rc4.php b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc4.php new file mode 100644 index 0000000000..fc2923f99b --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_6_rc4.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_6_rc4 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.6-rc4', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_6_rc3'); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.0.6-rc4')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_7.php b/phpBB/phpbb/db/migration/data/30x/3_0_7.php new file mode 100644 index 0000000000..9ff2e9e4ab --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_7.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_7 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.7', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_7_rc2'); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.0.7')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_7_pl1.php b/phpBB/phpbb/db/migration/data/30x/3_0_7_pl1.php new file mode 100644 index 0000000000..c9cc9d19ac --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_7_pl1.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_7_pl1 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.7-pl1', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_7'); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.0.7-pl1')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_7_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_7_rc1.php new file mode 100644 index 0000000000..ffebf66f2d --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_7_rc1.php @@ -0,0 +1,76 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_7_rc1 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.7-rc1', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_6'); +	} + +	public function update_schema() +	{ +		return array( +			'drop_keys' => array( +				$this->table_prefix . 'log' => array( +					'log_time', +				), +			), +			'add_index' => array( +				$this->table_prefix . 'topics_track' => array( +					'topic_id' => array('topic_id'), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'add_index' => array( +				$this->table_prefix . 'log' => array( +					'log_time'	=> array('log_time'), +				), +			), +			'drop_keys' => array( +				$this->table_prefix . 'topics_track' => array( +					'topic_id', +				), +			), +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.add', array('feed_overall', 1)), +			array('config.add', array('feed_http_auth', 0)), +			array('config.add', array('feed_limit_post', $this->config['feed_limit'])), +			array('config.add', array('feed_limit_topic', $this->config['feed_overall_topics_limit'])), +			array('config.add', array('feed_topics_new', $this->config['feed_overall_topics'])), +			array('config.add', array('feed_topics_active', $this->config['feed_overall_topics'])), +			array('custom', array(array(&$this, 'delete_text_templates'))), + +			array('config.update', array('version', '3.0.7-rc1')), +		); +	} + +	public function delete_text_templates() +	{ +		// Delete all text-templates from the template_data +		$sql = 'DELETE FROM ' . STYLES_TEMPLATE_DATA_TABLE . ' +			WHERE template_filename ' . $this->db->sql_like_expression($this->db->any_char . '.txt'); +		$this->sql_query($sql); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_7_rc2.php b/phpBB/phpbb/db/migration/data/30x/3_0_7_rc2.php new file mode 100644 index 0000000000..55bc2bc679 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_7_rc2.php @@ -0,0 +1,73 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_7_rc2 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.7-rc2', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_7_rc1'); +	} + +	public function update_data() +	{ +		return array( +			array('custom', array(array(&$this, 'update_email_hash'))), + +			array('config.update', array('version', '3.0.7-rc2')), +		); +	} + +	public function update_email_hash($start = 0) +	{ +		$limit = 1000; + +		$sql = 'SELECT user_id, user_email, user_email_hash +			FROM ' . USERS_TABLE . ' +			WHERE user_type <> ' . USER_IGNORE . " +				AND user_email <> ''"; +		$result = $this->db->sql_query_limit($sql, $limit, $start); + +		$i = 0; +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$i++; + +			// Snapshot of the phpbb_email_hash() function +			// We cannot call it directly because the auto updater updates the DB first. :/ +			$user_email_hash = sprintf('%u', crc32(strtolower($row['user_email']))) . strlen($row['user_email']); + +			if ($user_email_hash != $row['user_email_hash']) +			{ +				$sql_ary = array( +					'user_email_hash'	=> $user_email_hash, +				); + +				$sql = 'UPDATE ' . USERS_TABLE . ' +					SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' +					WHERE user_id = ' . (int) $row['user_id']; +				$this->sql_query($sql); +			} +		} +		$this->db->sql_freeresult($result); + +		if ($i < $limit) +		{ +			// Completed +			return; +		} + +		// Return the next start, will be sent to $start when this function is called again +		return $start + $limit; +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_8.php b/phpBB/phpbb/db/migration/data/30x/3_0_8.php new file mode 100644 index 0000000000..8998ef9627 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_8.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_8 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.8', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_8_rc1'); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.0.8')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_8_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_8_rc1.php new file mode 100644 index 0000000000..aeff35333e --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_8_rc1.php @@ -0,0 +1,221 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_8_rc1 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.8-rc1', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_7_pl1'); +	} + +	public function update_data() +	{ +		return array( +			array('custom', array(array(&$this, 'update_file_extension_group_names'))), +			array('custom', array(array(&$this, 'update_module_auth'))), +			array('custom', array(array(&$this, 'update_bots'))), +			array('custom', array(array(&$this, 'delete_orphan_shadow_topics'))), +			array('module.add', array( +				'acp', +				'ACP_MESSAGES', +				array( +					'module_basename'	=> 'acp_board', +					'modes'				=> array('post'), +				), +			)), +			array('config.add', array('load_unreads_search', 1)), +			array('config.update_if_equals', array(600, 'queue_interval', 60)), +			array('config.update_if_equals', array(50, 'email_package_size', 20)), + +			array('config.update', array('version', '3.0.8-rc1')), +		); +	} + +	public function update_file_extension_group_names() +	{ +		// Update file extension group names to use language strings. +		$sql = 'SELECT lang_dir +			FROM ' . LANG_TABLE; +		$result = $this->db->sql_query($sql); + +		$extension_groups_updated = array(); +		while ($lang_dir = $this->db->sql_fetchfield('lang_dir')) +		{ +			$lang_dir = basename($lang_dir); + +			// The language strings we need are either in language/.../acp/attachments.php +			// in the update package if we're updating to 3.0.8-RC1 or later, +			// or they are in language/.../install.php when we're updating from 3.0.7-PL1 or earlier. +			// On an already updated board, they can also already be in language/.../acp/attachments.php +			// in the board root. +			$lang_files = array( +				"{$this->phpbb_root_path}install/update/new/language/$lang_dir/acp/attachments.{$this->php_ext}", +				"{$this->phpbb_root_path}language/$lang_dir/install.{$this->php_ext}", +				"{$this->phpbb_root_path}language/$lang_dir/acp/attachments.{$this->php_ext}", +			); + +			foreach ($lang_files as $lang_file) +			{ +				if (!file_exists($lang_file)) +				{ +					continue; +				} + +				$lang = array(); +				include($lang_file); + +				foreach($lang as $lang_key => $lang_val) +				{ +					if (isset($extension_groups_updated[$lang_key]) || strpos($lang_key, 'EXT_GROUP_') !== 0) +					{ +						continue; +					} + +					$sql_ary = array( +						'group_name'	=> substr($lang_key, 10), // Strip off 'EXT_GROUP_' +					); + +					$sql = 'UPDATE ' . EXTENSION_GROUPS_TABLE . ' +						SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . " +						WHERE group_name = '" . $this->db->sql_escape($lang_val) . "'"; +					$this->sql_query($sql); + +					$extension_groups_updated[$lang_key] = true; +				} +			} +		} +		$this->db->sql_freeresult($result); +	} + +	public function update_module_auth() +	{ +		$sql = 'UPDATE ' . MODULES_TABLE . ' +			SET module_auth = \'cfg_allow_avatar && (cfg_allow_avatar_local || cfg_allow_avatar_remote || cfg_allow_avatar_upload || cfg_allow_avatar_remote_upload)\' +			WHERE module_class = \'ucp\' +				AND module_basename = \'profile\' +				AND module_mode = \'avatar\''; +		$this->sql_query($sql); +	} + +	public function update_bots() +	{ +		$bot_name = 'Bing [Bot]'; +		$bot_name_clean = utf8_clean_string($bot_name); + +		$sql = 'SELECT user_id +			FROM ' . USERS_TABLE . " +			WHERE username_clean = '" . $this->db->sql_escape($bot_name_clean) . "'"; +		$result = $this->db->sql_query($sql); +		$bing_already_added = (bool) $this->db->sql_fetchfield('user_id'); +		$this->db->sql_freeresult($result); + +		if (!$bing_already_added) +		{ +			$bot_agent = 'bingbot/'; +			$bot_ip = ''; +			$sql = 'SELECT group_id, group_colour +				FROM ' . GROUPS_TABLE . " +				WHERE group_name = 'BOTS'"; +			$result = $this->db->sql_query($sql); +			$group_row = $this->db->sql_fetchrow($result); +			$this->db->sql_freeresult($result); + +			if (!$group_row) +			{ +				// default fallback, should never get here +				$group_row['group_id'] = 6; +				$group_row['group_colour'] = '9E8DA7'; +			} + +			if (!function_exists('user_add')) +			{ +				include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); +			} + +			$user_row = array( +				'user_type'				=> USER_IGNORE, +				'group_id'				=> $group_row['group_id'], +				'username'				=> $bot_name, +				'user_regdate'			=> time(), +				'user_password'			=> '', +				'user_colour'			=> $group_row['group_colour'], +				'user_email'			=> '', +				'user_lang'				=> $this->config['default_lang'], +				'user_style'			=> $this->config['default_style'], +				'user_timezone'			=> 0, +				'user_dateformat'		=> $this->config['default_dateformat'], +				'user_allow_massemail'	=> 0, +			); + +			$user_id = user_add($user_row); + +			$sql = 'INSERT INTO ' . BOTS_TABLE . ' ' . $this->db->sql_build_array('INSERT', array( +				'bot_active'	=> 1, +				'bot_name'		=> (string) $bot_name, +				'user_id'		=> (int) $user_id, +				'bot_agent'		=> (string) $bot_agent, +				'bot_ip'		=> (string) $bot_ip, +			)); + +			$this->sql_query($sql); +		} +	} + +	public function delete_orphan_shadow_topics() +	{ +		// Delete shadow topics pointing to not existing topics +		$batch_size = 500; + +		// Set of affected forums we have to resync +		$sync_forum_ids = array(); + +		$sql_array = array( +			'SELECT'	=> 't1.topic_id, t1.forum_id', +			'FROM'		=> array( +				TOPICS_TABLE	=> 't1', +			), +			'LEFT_JOIN'	=> array( +				array( +					'FROM'	=> array(TOPICS_TABLE	=> 't2'), +					'ON'	=> 't1.topic_moved_id = t2.topic_id', +				), +			), +			'WHERE'		=> 't1.topic_moved_id <> 0 +						AND t2.topic_id IS NULL', +		); +		$sql = $this->db->sql_build_query('SELECT', $sql_array); +		$result = $this->db->sql_query_limit($sql, $batch_size); + +		$topic_ids = array(); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$topic_ids[] = (int) $row['topic_id']; + +			$sync_forum_ids[(int) $row['forum_id']] = (int) $row['forum_id']; +		} +		$this->db->sql_freeresult($result); + +		if (!empty($topic_ids)) +		{ +			$sql = 'DELETE FROM ' . TOPICS_TABLE . ' +				WHERE ' . $this->db->sql_in_set('topic_id', $topic_ids); +			$this->db->sql_query($sql); + +			// Sync the forums we have deleted shadow topics from. +			sync('forum', 'forum_id', $sync_forum_ids, true, true); + +			return false; +		} +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_9.php b/phpBB/phpbb/db/migration/data/30x/3_0_9.php new file mode 100644 index 0000000000..d5269ea6f0 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_9.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_9 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.9', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_9_rc4'); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.0.9')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_9_rc1.php b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc1.php new file mode 100644 index 0000000000..4c345b429b --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc1.php @@ -0,0 +1,124 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_9_rc1 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.9-rc1', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_8'); +	} + +	public function update_schema() +	{ +		return array( +			'add_tables' => array( +				$this->table_prefix . 'login_attempts' => array( +					'COLUMNS' => array( +						// this column was removed from the database updater +						// after 3.0.9-RC3 was released. It might still exist +						// in 3.0.9-RCX installations and has to be dropped as +						// soon as the db_tools class is capable of properly +						// removing a primary key. +						// 'attempt_id'			=> array('UINT', NULL, 'auto_increment'), +						'attempt_ip'			=> array('VCHAR:40', ''), +						'attempt_browser'		=> array('VCHAR:150', ''), +						'attempt_forwarded_for'	=> array('VCHAR:255', ''), +						'attempt_time'			=> array('TIMESTAMP', 0), +						'user_id'				=> array('UINT', 0), +						'username'				=> array('VCHAR_UNI:255', 0), +						'username_clean'		=> array('VCHAR_CI', 0), +					), +					//'PRIMARY_KEY' => 'attempt_id', +					'KEYS' => array( +						'att_ip' => array('INDEX', array('attempt_ip', 'attempt_time')), +						'att_for' => array('INDEX', array('attempt_forwarded_for', 'attempt_time')), +						'att_time' => array('INDEX', array('attempt_time')), +						'user_id' => array('INDEX', 'user_id'), +					), +				), +			), +			'change_columns' => array( +				$this->table_prefix . 'bbcodes' => array( +					'bbcode_id' => array('USINT', 0), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'drop_tables' => array( +				$this->table_prefix . 'login_attempts', +			), +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.add', array('ip_login_limit_max', 50)), +			array('config.add', array('ip_login_limit_time', 21600)), +			array('config.add', array('ip_login_limit_use_forwarded', 0)), +			array('custom', array(array(&$this, 'update_file_extension_group_names'))), +			array('custom', array(array(&$this, 'fix_firebird_qa_captcha'))), + +			array('config.update', array('version', '3.0.9-rc1')), +		); +	} + +	public function update_file_extension_group_names() +	{ +		// Update file extension group names to use language strings, again. +		$sql = 'SELECT group_id, group_name +			FROM ' . EXTENSION_GROUPS_TABLE . ' +			WHERE group_name ' . $this->db->sql_like_expression('EXT_GROUP_' . $this->db->any_char); +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$sql_ary = array( +				'group_name'	=> substr($row['group_name'], 10), // Strip off 'EXT_GROUP_' +			); + +			$sql = 'UPDATE ' . EXTENSION_GROUPS_TABLE . ' +				SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' +				WHERE group_id = ' . $row['group_id']; +			$this->sql_query($sql); +		} +		$this->db->sql_freeresult($result); +	} + +	public function fix_firebird_qa_captcha() +	{ +		// Recover from potentially broken Q&A CAPTCHA table on firebird +		// Q&A CAPTCHA was uninstallable, so it's safe to remove these +		// without data loss +		if ($this->db_tools->sql_layer == 'firebird') +		{ +			$tables = array( +				$this->table_prefix . 'captcha_questions', +				$this->table_prefix . 'captcha_answers', +				$this->table_prefix . 'qa_confirm', +			); +			foreach ($tables as $table) +			{ +				if ($this->db_tools->sql_table_exists($table)) +				{ +					$this->db_tools->sql_table_drop($table); +				} +			} +		} +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_9_rc2.php b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc2.php new file mode 100644 index 0000000000..c0e662aa45 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc2.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_9_rc2 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.9-rc2', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_9_rc1'); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.0.9-rc2')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_9_rc3.php b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc3.php new file mode 100644 index 0000000000..d6d1f14b2e --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc3.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_9_rc3 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.9-rc3', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_9_rc2'); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.0.9-rc3')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/3_0_9_rc4.php b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc4.php new file mode 100644 index 0000000000..e673249343 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/3_0_9_rc4.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_3_0_9_rc4 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.0.9-rc4', '>='); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_9_rc3'); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.0.9-rc4')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/30x/local_url_bbcode.php b/phpBB/phpbb/db/migration/data/30x/local_url_bbcode.php new file mode 100644 index 0000000000..f324b8880d --- /dev/null +++ b/phpBB/phpbb/db/migration/data/30x/local_url_bbcode.php @@ -0,0 +1,57 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_local_url_bbcode extends phpbb_db_migration +{ +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_12_rc1'); +	} + +	public function update_data() +	{ +		return array( +			array('custom', array(array($this, 'update_local_url_bbcode'))), +		); +	} + +	/** +	* Update BBCodes that currently use the LOCAL_URL tag +	* +	* To fix http://tracker.phpbb.com/browse/PHPBB3-8319 we changed +	* the second_pass_replace value, so that needs updating for existing ones +	*/ +	public function update_local_url_bbcode() +	{ +		$sql = 'SELECT * +			FROM ' . BBCODES_TABLE . ' +			WHERE bbcode_match ' . $this->db->sql_like_expression($this->db->any_char . 'LOCAL_URL' . $this->db->any_char); +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if (!class_exists('acp_bbcodes')) +			{ +				global $phpEx; +				phpbb_require_updated('includes/acp/acp_bbcodes.' . $phpEx); +			} +			$bbcode_match = $row['bbcode_match']; +			$bbcode_tpl = $row['bbcode_tpl']; + +			$acp_bbcodes = new acp_bbcodes(); +			$sql_ary = $acp_bbcodes->build_regexp($bbcode_match, $bbcode_tpl); + +			$sql = 'UPDATE ' . BBCODES_TABLE . ' +				SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' +				WHERE bbcode_id = ' . (int) $row['bbcode_id']; +			$this->sql_query($sql); +		} +		$this->db->sql_freeresult($result); +	} +} diff --git a/phpBB/phpbb/db/migration/data/310/avatars.php b/phpBB/phpbb/db/migration/data/310/avatars.php new file mode 100644 index 0000000000..79547337f7 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/avatars.php @@ -0,0 +1,67 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_avatars extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return isset($this->config['allow_avatar_gravatar']); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_11'); +	} + +	public function update_schema() +	{ +		return array( +			'change_columns'	=> array( +				$this->table_prefix . 'users'			=> array( +					'user_avatar_type'		=> array('VCHAR:255', ''), +				), +				$this->table_prefix . 'groups'			=> array( +					'group_avatar_type'		=> array('VCHAR:255', ''), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'change_columns'	=> array( +				$this->table_prefix . 'users'			=> array( +					'user_avatar_type'		=> array('TINT:2', ''), +				), +				$this->table_prefix . 'groups'			=> array( +					'group_avatar_type'		=> array('TINT:2', ''), +				), +			), +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.add', array('allow_avatar_gravatar', 0)), +			array('custom', array(array($this, 'update_module_auth'))), +		); +	} + +	public function update_module_auth() +	{ +		$sql = 'UPDATE ' . $this->table_prefix . "modules +			SET module_auth = 'cfg_allow_avatar' +			WHERE module_class = 'ucp' +				AND module_basename = 'ucp_profile' +				AND module_mode = 'avatar'"; +		$this->db->sql_query($sql); +	} +} diff --git a/phpBB/phpbb/db/migration/data/310/boardindex.php b/phpBB/phpbb/db/migration/data/310/boardindex.php new file mode 100644 index 0000000000..965e32c15c --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/boardindex.php @@ -0,0 +1,23 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_boardindex extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return isset($this->config['board_index_text']); +	} + +	public function update_data() +	{ +		return array( +			array('config.add', array('board_index_text', '')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/310/config_db_text.php b/phpBB/phpbb/db/migration/data/310/config_db_text.php new file mode 100644 index 0000000000..89f211adda --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/config_db_text.php @@ -0,0 +1,45 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_config_db_text extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return $this->db_tools->sql_table_exists($this->table_prefix . 'config_text'); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_11'); +	} + +	public function update_schema() +	{ +		return array( +			'add_tables' => array( +				$this->table_prefix . 'config_text' => array( +					'COLUMNS' => array( +						'config_name'	=> array('VCHAR', ''), +						'config_value'	=> array('MTEXT', ''), +					), +					'PRIMARY_KEY' => 'config_name', +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'drop_tables' => array( +				$this->table_prefix . 'config_text', +			), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/310/dev.php b/phpBB/phpbb/db/migration/data/310/dev.php new file mode 100644 index 0000000000..0fc2950987 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/dev.php @@ -0,0 +1,408 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_dev extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.1.0-dev', '>='); +	} + +	static public function depends_on() +	{ +		return array( +			'phpbb_db_migration_data_310_extensions', +			'phpbb_db_migration_data_310_style_update_p2', +			'phpbb_db_migration_data_310_timezone_p2', +			'phpbb_db_migration_data_310_reported_posts_display', +		); +	} + +	public function update_schema() +	{ +		return array( +			'add_columns'		=> array( +				$this->table_prefix . 'groups'		=> array( +					'group_teampage'	=> array('UINT', 0, 'after' => 'group_legend'), +				), +				$this->table_prefix . 'profile_fields'	=> array( +					'field_show_on_pm'		=> array('BOOL', 0), +				), +				$this->table_prefix . 'styles'		=> array( +					'style_path'			=> array('VCHAR:100', ''), +					'bbcode_bitfield'		=> array('VCHAR:255', 'kNg='), +					'style_parent_id'		=> array('UINT:4', 0), +					'style_parent_tree'		=> array('TEXT', ''), +				), +				$this->table_prefix . 'reports'		=> array( +					'reported_post_text'		=> array('MTEXT_UNI', ''), +					'reported_post_uid'			=> array('VCHAR:8', ''), +					'reported_post_bitfield'	=> array('VCHAR:255', ''), +				), +			), +			'change_columns'	=> array( +				$this->table_prefix . 'groups'		=> array( +					'group_legend'		=> array('UINT', 0), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'drop_columns'		=> array( +				$this->table_prefix . 'groups'		=> array( +					'group_teampage', +				), +				$this->table_prefix . 'profile_fields'	=> array( +					'field_show_on_pm', +				), +				$this->table_prefix . 'styles'		=> array( +					'style_path', +					'bbcode_bitfield', +					'style_parent_id', +					'style_parent_tree', +				), +				$this->table_prefix . 'reports'		=> array( +					'reported_post_text', +					'reported_post_uid', +					'reported_post_bitfield', +				), +			), +		); +	} + +	public function update_data() +	{ +		return array( +			array('if', array( +				(strpos('phpbb_search_', $this->config['search_type']) !== 0), +				array('config.update', array('search_type', 'phpbb_search_' . $this->config['search_type'])), +			)), + +			array('config.add', array('fulltext_postgres_ts_name', 'simple')), +			array('config.add', array('fulltext_postgres_min_word_len', 4)), +			array('config.add', array('fulltext_postgres_max_word_len', 254)), +			array('config.add', array('fulltext_sphinx_stopwords', 0)), +			array('config.add', array('fulltext_sphinx_indexer_mem_limit', 512)), + +			array('config.add', array('load_jquery_cdn', 0)), +			array('config.add', array('load_jquery_url', '//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js')), + +			array('config.add', array('use_system_cron', 0)), + +			array('config.add', array('legend_sort_groupname', 0)), +			array('config.add', array('teampage_forums', 1)), +			array('config.add', array('teampage_memberships', 1)), + +			array('config.add', array('load_cpf_pm', 0)), + +			array('config.add', array('display_last_subject', 1)), + +			array('config.add', array('assets_version', 1)), + +			array('config.add', array('site_home_url', '')), +			array('config.add', array('site_home_text', '')), + +			array('permission.add', array('u_chgprofileinfo', true, 'u_sig')), + +			array('module.add', array( +				'acp', +				'ACP_GROUPS', +				array( +					'module_basename'	=> 'acp_groups', +					'modes'				=> array('position'), +				), +			)), +			array('module.add', array( +				'acp', +				'ACP_ATTACHMENTS', +				array( +					'module_basename'	=> 'acp_attachments', +					'modes'				=> array('manage'), +				), +			)), +			array('module.add', array( +				'acp', +				'ACP_STYLE_MANAGEMENT', +				array( +					'module_basename'	=> 'acp_styles', +					'modes'				=> array('install', 'cache'), +				), +			)), +			array('module.add', array( +				'ucp', +				'UCP_PROFILE', +				array( +					'module_basename'	=> 'ucp_profile', +					'modes'				=> array('autologin_keys'), +				), +			)), +			// Module will be renamed later +			array('module.add', array( +				'acp', +				'ACP_CAT_STYLES', +				'ACP_LANGUAGE' +			)), + +			array('module.remove', array( +				'acp', +				false, +				'ACP_TEMPLATES', +			)), +			array('module.remove', array( +				'acp', +				false, +				'ACP_THEMES', +			)), +			array('module.remove', array( +				'acp', +				false, +				'ACP_IMAGESETS', +			)), + +			array('custom', array(array($this, 'rename_module_basenames'))), +			array('custom', array(array($this, 'rename_styles_module'))), +			array('custom', array(array($this, 'add_group_teampage'))), +			array('custom', array(array($this, 'update_group_legend'))), +			array('custom', array(array($this, 'localise_global_announcements'))), +			array('custom', array(array($this, 'update_ucp_pm_basename'))), +			array('custom', array(array($this, 'update_ucp_profile_auth'))), +			array('custom', array(array($this, 'move_customise_modules'))), + +			array('config.update', array('version', '3.1.0-dev')), +		); +	} + +	public function move_customise_modules() +	{ +		// Move language management to new location in the Customise tab +		// First get language module id +		$sql = 'SELECT module_id FROM ' . MODULES_TABLE . " +			WHERE module_basename = 'acp_language'"; +		$result = $this->db->sql_query($sql); +		$language_module_id = $this->db->sql_fetchfield('module_id'); +		$this->db->sql_freeresult($result); +		// Next get language management module id of the one just created +		$sql = 'SELECT module_id FROM ' . MODULES_TABLE . " +			WHERE module_langname = 'ACP_LANGUAGE'"; +		$result = $this->db->sql_query($sql); +		$language_management_module_id = $this->db->sql_fetchfield('module_id'); +		$this->db->sql_freeresult($result); + +		if (!class_exists('acp_modules')) +		{ +			include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); +		} +		// acp_modules calls adm_back_link, which is undefined at this point +		if (!function_exists('adm_back_link')) +		{ +			include($this->phpbb_root_path . 'includes/functions_acp.' . $this->php_ext); +		} +		$module_manager = new acp_modules(); +		$module_manager->module_class = 'acp'; +		$module_manager->move_module($language_module_id, $language_management_module_id); +	} + +	public function update_ucp_pm_basename() +	{ +		$sql = 'SELECT module_id, module_basename +			FROM ' . MODULES_TABLE . " +			WHERE module_basename <> 'ucp_pm' AND +				module_langname='UCP_PM'"; +		$result = $this->db->sql_query_limit($sql, 1); + +		if ($row = $this->db->sql_fetchrow($result)) +		{ +			// This update is still not applied. Applying it + +			$sql = 'UPDATE ' . MODULES_TABLE . " +				SET module_basename = 'ucp_pm' +				WHERE  module_id = " . (int) $row['module_id']; + +			$this->sql_query($sql); +		} +		$this->db->sql_freeresult($result); +	} + +	public function update_ucp_profile_auth() +	{ +		// Update the auth setting for the module +		$sql = 'UPDATE ' . MODULES_TABLE . " +			SET module_auth = 'acl_u_chgprofileinfo' +			WHERE module_class = 'ucp' +				AND module_basename = 'ucp_profile' +				AND module_mode = 'profile_info'"; +		$this->sql_query($sql); +	} + +	public function rename_styles_module() +	{ +		// Rename styles module to Customise +		$sql = 'UPDATE ' . MODULES_TABLE . " +			SET module_langname = 'ACP_CAT_CUSTOMISE' +			WHERE module_langname = 'ACP_CAT_STYLES'"; +		$this->sql_query($sql); +	} + +	public function rename_module_basenames() +	{ +		// rename all module basenames to full classname +		$sql = 'SELECT module_id, module_basename, module_class +			FROM ' . MODULES_TABLE; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$module_id = (int) $row['module_id']; +			unset($row['module_id']); + +			if (!empty($row['module_basename']) && !empty($row['module_class'])) +			{ +				// all the class names start with class name or with phpbb_ for auto loading +				if (strpos($row['module_basename'], $row['module_class'] . '_') !== 0 && +					strpos($row['module_basename'], 'phpbb_') !== 0) +				{ +					$row['module_basename'] = $row['module_class'] . '_' . $row['module_basename']; + +					$sql_update = $this->db->sql_build_array('UPDATE', $row); + +					$sql = 'UPDATE ' . MODULES_TABLE . ' +						SET ' . $sql_update . ' +						WHERE module_id = ' . $module_id; +					$this->sql_query($sql); +				} +			} +		} + +		$this->db->sql_freeresult($result); +	} + +	public function add_group_teampage() +	{ +		$sql = 'UPDATE ' . GROUPS_TABLE . ' +			SET group_teampage = 1 +			WHERE group_type = ' . GROUP_SPECIAL . " +				AND group_name = 'ADMINISTRATORS'"; +		$this->sql_query($sql); + +		$sql = 'UPDATE ' . GROUPS_TABLE . ' +			SET group_teampage = 2 +			WHERE group_type = ' . GROUP_SPECIAL . " +				AND group_name = 'GLOBAL_MODERATORS'"; +		$this->sql_query($sql); +	} + +	public function update_group_legend() +	{ +		$sql = 'SELECT group_id +			FROM ' . GROUPS_TABLE . ' +			WHERE group_legend = 1 +			ORDER BY group_name ASC'; +		$result = $this->db->sql_query($sql); + +		$next_legend = 1; +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$sql = 'UPDATE ' . GROUPS_TABLE . ' +				SET group_legend = ' . $next_legend . ' +				WHERE group_id = ' . (int) $row['group_id']; +			$this->sql_query($sql); + +			$next_legend++; +		} +		$this->db->sql_freeresult($result); +	} + +	public function localise_global_announcements() +	{ +		// Localise Global Announcements +		$sql = 'SELECT topic_id, topic_approved, (topic_replies + 1) AS topic_posts, topic_last_post_id, topic_last_post_subject, topic_last_post_time, topic_last_poster_id, topic_last_poster_name, topic_last_poster_colour +			FROM ' . TOPICS_TABLE . ' +			WHERE forum_id = 0 +				AND topic_type = ' . POST_GLOBAL; +		$result = $this->db->sql_query($sql); + +		$global_announcements = $update_lastpost_data = array(); +		$update_lastpost_data['forum_last_post_time'] = 0; +		$update_forum_data = array( +			'forum_posts'		=> 0, +			'forum_topics'		=> 0, +			'forum_topics_real'	=> 0, +		); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$global_announcements[] = (int) $row['topic_id']; + +			$update_forum_data['forum_posts'] += (int) $row['topic_posts']; +			$update_forum_data['forum_topics_real']++; +			if ($row['topic_approved']) +			{ +				$update_forum_data['forum_topics']++; +			} + +			if ($update_lastpost_data['forum_last_post_time'] < $row['topic_last_post_time']) +			{ +				$update_lastpost_data = array( +					'forum_last_post_id'		=> (int) $row['topic_last_post_id'], +					'forum_last_post_subject'	=> $row['topic_last_post_subject'], +					'forum_last_post_time'		=> (int) $row['topic_last_post_time'], +					'forum_last_poster_id'		=> (int) $row['topic_last_poster_id'], +					'forum_last_poster_name'	=> $row['topic_last_poster_name'], +					'forum_last_poster_colour'	=> $row['topic_last_poster_colour'], +				); +			} +		} +		$this->db->sql_freeresult($result); + +		if (!empty($global_announcements)) +		{ +			// Update the post/topic-count for the forum and the last-post if needed +			$sql = 'SELECT forum_id +				FROM ' . FORUMS_TABLE . ' +				WHERE forum_type = ' . FORUM_POST; +			$result = $this->db->sql_query_limit($sql, 1); +			$ga_forum_id = $this->db->sql_fetchfield('forum_id'); +			$this->db->sql_freeresult($result); + +			$sql = 'SELECT forum_last_post_time +				FROM ' . FORUMS_TABLE . ' +				WHERE forum_id = ' . $ga_forum_id; +			$result = $this->db->sql_query($sql); +			$lastpost = (int) $this->db->sql_fetchfield('forum_last_post_time'); +			$this->db->sql_freeresult($result); + +			$sql_update = 'forum_posts = forum_posts + ' . $update_forum_data['forum_posts'] . ', '; +			$sql_update .= 'forum_topics_real = forum_topics_real + ' . $update_forum_data['forum_topics_real'] . ', '; +			$sql_update .= 'forum_topics = forum_topics + ' . $update_forum_data['forum_topics']; +			if ($lastpost < $update_lastpost_data['forum_last_post_time']) +			{ +				$sql_update .= ', ' . $this->db->sql_build_array('UPDATE', $update_lastpost_data); +			} + +			$sql = 'UPDATE ' . FORUMS_TABLE . ' +				SET ' . $sql_update . ' +				WHERE forum_id = ' . $ga_forum_id; +			$this->sql_query($sql); + +			// Update some forum_ids +			$table_ary = array(TOPICS_TABLE, POSTS_TABLE, LOG_TABLE, DRAFTS_TABLE, TOPICS_TRACK_TABLE); +			foreach ($table_ary as $table) +			{ +				$sql = "UPDATE $table +					SET forum_id = $ga_forum_id +					WHERE " . $this->db->sql_in_set('topic_id', $global_announcements); +				$this->sql_query($sql); +			} +			unset($table_ary); +		} +	} +} diff --git a/phpBB/phpbb/db/migration/data/310/extensions.php b/phpBB/phpbb/db/migration/data/310/extensions.php new file mode 100644 index 0000000000..6a9caa1cfc --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/extensions.php @@ -0,0 +1,69 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_extensions extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return $this->db_tools->sql_table_exists($this->table_prefix . 'ext'); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_11'); +	} + +	public function update_schema() +	{ +		return array( +			'add_tables'		=> array( +				$this->table_prefix . 'ext'	=> array( +					'COLUMNS'			=> array( +						'ext_name'		=> array('VCHAR', ''), +						'ext_active'	=> array('BOOL', 0), +						'ext_state'		=> array('TEXT', ''), +					), +					'KEYS'				=> array( +						'ext_name'		=> array('UNIQUE', 'ext_name'), +					), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'drop_tables'		=> array( +				$this->table_prefix . 'ext', +			), +		); +	} + +	public function update_data() +	{ +		return array( +			// Module will be renamed later +			array('module.add', array( +				'acp', +				'ACP_CAT_STYLES', +				'ACP_EXTENSION_MANAGEMENT' +			)), +			array('module.add', array( +				'acp', +				'ACP_EXTENSION_MANAGEMENT', +				array( +					'module_basename'	=> 'acp_extensions', +					'modes'				=> array('main'), +				), +			)), +			array('permission.add', array('a_extensions', true, 'a_styles')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/310/forgot_password.php b/phpBB/phpbb/db/migration/data/310/forgot_password.php new file mode 100644 index 0000000000..a553e51f35 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/forgot_password.php @@ -0,0 +1,28 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_forgot_password extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return isset($this->config['allow_password_reset']); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_11'); +	} + +	public function update_data() +	{ +		return array( +			array('config.add', array('allow_password_reset', 1)), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/310/jquery_update.php b/phpBB/phpbb/db/migration/data/310/jquery_update.php new file mode 100644 index 0000000000..dc49f74fcb --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/jquery_update.php @@ -0,0 +1,31 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_jquery_update extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return $this->config['load_jquery_url'] !== '//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js'; +	} + +	static public function depends_on() +	{ +		return array( +			'phpbb_db_migration_data_310_dev', +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('load_jquery_url', '//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js')), +		); +	} + +} diff --git a/phpBB/phpbb/db/migration/data/310/notification_options_reconvert.php b/phpBB/phpbb/db/migration/data/310/notification_options_reconvert.php new file mode 100644 index 0000000000..d994d7ec5f --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/notification_options_reconvert.php @@ -0,0 +1,118 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +class phpbb_db_migration_data_310_notification_options_reconvert extends phpbb_db_migration +{ +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_310_notifications_schema_fix'); +	} + +	public function update_data() +	{ +		return array( +			array('custom', array(array($this, 'convert_notifications'))), +		); +	} + +	public function convert_notifications() +	{ +		$insert_table = $this->table_prefix . 'user_notifications'; +		$insert_buffer = new phpbb_db_sql_insert_buffer($this->db, $insert_table); + +		$this->perform_conversion($insert_buffer, $insert_table); +	} + +	/** +	* Perform the conversion (separate for testability) +	* +	* @param phpbb_db_sql_insert_buffer $insert_buffer +	* @param string $insert_table +	*/ +	public function perform_conversion(phpbb_db_sql_insert_buffer $insert_buffer, $insert_table) +	{ +		$sql = 'DELETE FROM ' . $insert_table; +		$this->db->sql_query($sql); + +		$sql = 'SELECT user_id, user_notify_type, user_notify_pm +			FROM ' . USERS_TABLE; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$notification_methods = array(); + +			// In-board notification +			$notification_methods[] = ''; + +			if ($row['user_notify_type'] == NOTIFY_EMAIL || $row['user_notify_type'] == NOTIFY_BOTH) +			{ +				$notification_methods[] = 'email'; +			} + +			if ($row['user_notify_type'] == NOTIFY_IM || $row['user_notify_type'] == NOTIFY_BOTH) +			{ +				$notification_methods[] = 'jabber'; +			} + +			// Notifications for posts +			foreach (array('post', 'topic') as $item_type) +			{ +				$this->add_method_rows( +					$insert_buffer, +					$item_type, +					0, +					$row['user_id'], +					$notification_methods +				); +			} + +			if ($row['user_notify_pm']) +			{ +				// Notifications for private messages +				// User either gets all methods or no method +				$this->add_method_rows( +					$insert_buffer, +					'pm', +					0, +					$row['user_id'], +					$notification_methods +				); +			} +		} +		$this->db->sql_freeresult($result); + +		$insert_buffer->flush(); +	} + +	/** +	* Insert method rows to DB +	* +	* @param phpbb_db_sql_insert_buffer $insert_buffer +	* @param string $item_type +	* @param int $item_id +	* @param int $user_id +	* @param string $methods +	*/ +	protected function add_method_rows(phpbb_db_sql_insert_buffer $insert_buffer, $item_type, $item_id, $user_id, array $methods) +	{ +		$row_base = array( +			'item_type'		=> $item_type, +			'item_id'		=> (int) $item_id, +			'user_id'		=> (int) $user_id, +			'notify'		=> 1 +		); + +		foreach ($methods as $method) +		{ +			$row_base['method'] = $method; +			$insert_buffer->insert($row_base); +		} +	} +} diff --git a/phpBB/phpbb/db/migration/data/310/notifications.php b/phpBB/phpbb/db/migration/data/310/notifications.php new file mode 100644 index 0000000000..17c939d95a --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/notifications.php @@ -0,0 +1,96 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_notifications extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return $this->db_tools->sql_table_exists($this->table_prefix . 'notifications'); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_310_dev'); +	} + +	public function update_schema() +	{ +		return array( +			'add_tables'		=> array( +				$this->table_prefix . 'notification_types'	=> array( +					'COLUMNS'			=> array( +						'notification_type'			=> array('VCHAR:255', ''), +						'notification_type_enabled'	=> array('BOOL', 1), +					), +					'PRIMARY_KEY'		=> array('notification_type', 'notification_type_enabled'), +				), +				$this->table_prefix . 'notifications'		=> array( +					'COLUMNS'			=> array( +						'notification_id'  				=> array('UINT', NULL, 'auto_increment'), +						'item_type'			   			=> array('VCHAR:255', ''), +						'item_id'		  				=> array('UINT', 0), +						'item_parent_id'   				=> array('UINT', 0), +						'user_id'						=> array('UINT', 0), +						'notification_read'				=> array('BOOL', 0), +						'notification_time'				=> array('TIMESTAMP', 1), +						'notification_data'			   	=> array('TEXT_UNI', ''), +					), +					'PRIMARY_KEY'		=> 'notification_id', +					'KEYS'				=> array( +						'item_ident'		=> array('INDEX', array('item_type', 'item_id')), +						'user'				=> array('INDEX', array('user_id', 'notification_read')), +					), +				), +				$this->table_prefix . 'user_notifications'	=> array( +					'COLUMNS'			=> array( +						'item_type'			=> array('VCHAR:255', ''), +						'item_id'			=> array('UINT', 0), +						'user_id'			=> array('UINT', 0), +						'method'			=> array('VCHAR:255', ''), +						'notify'			=> array('BOOL', 1), +					), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'drop_tables'	=> array( +				$this->table_prefix . 'notification_types', +				$this->table_prefix . 'notifications', +				$this->table_prefix . 'user_notifications', +			), +		); +	} + +	public function update_data() +	{ +		return array( +			array('module.add', array( +				'ucp', +				'UCP_MAIN', +				array( +					'module_basename'	=> 'ucp_notifications', +					'modes'				=> array('notification_list'), +				), +			)), +			array('module.add', array( +				'ucp', +				'UCP_PREFS', +				array( +					'module_basename'	=> 'ucp_notifications', +					'modes'				=> array('notification_options'), +				), +			)), +			array('config.add', array('load_notifications', 1)), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/310/notifications_schema_fix.php b/phpBB/phpbb/db/migration/data/310/notifications_schema_fix.php new file mode 100644 index 0000000000..27e63e10d0 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/notifications_schema_fix.php @@ -0,0 +1,92 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_notifications_schema_fix extends phpbb_db_migration +{ +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_310_notifications'); +	} + +	public function update_schema() +	{ +		return array( +			'drop_tables'		=> array( +				$this->table_prefix . 'notification_types', +				$this->table_prefix . 'notifications', +			), +			'add_tables'		=> array( +				$this->table_prefix . 'notification_types'	=> array( +					'COLUMNS'			=> array( +						'notification_type_id'		=> array('USINT', NULL, 'auto_increment'), +						'notification_type_name'	=> array('VCHAR:255', ''), +						'notification_type_enabled'	=> array('BOOL', 1), +					), +					'PRIMARY_KEY'		=> array('notification_type_id'), +					'KEYS'				=> array( +						'type'			=> array('UNIQUE', array('notification_type_name')), +					), +				), +				$this->table_prefix . 'notifications'		=> array( +					'COLUMNS'			=> array( +						'notification_id'				=> array('UINT:10', NULL, 'auto_increment'), +						'notification_type_id'			=> array('USINT', 0), +						'item_id'						=> array('UINT', 0), +						'item_parent_id'				=> array('UINT', 0), +						'user_id'						=> array('UINT', 0), +						'notification_read'				=> array('BOOL', 0), +						'notification_time'				=> array('TIMESTAMP', 1), +						'notification_data'				=> array('TEXT_UNI', ''), +					), +					'PRIMARY_KEY'		=> 'notification_id', +					'KEYS'				=> array( +						'item_ident'		=> array('INDEX', array('notification_type_id', 'item_id')), +						'user'				=> array('INDEX', array('user_id', 'notification_read')), +					), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'drop_tables'	=> array( +				$this->table_prefix . 'notification_types', +				$this->table_prefix . 'notifications', +			), +			'add_tables'		=> array( +				$this->table_prefix . 'notification_types'	=> array( +					'COLUMNS'			=> array( +						'notification_type'			=> array('VCHAR:255', ''), +						'notification_type_enabled'	=> array('BOOL', 1), +					), +					'PRIMARY_KEY'		=> array('notification_type', 'notification_type_enabled'), +				), +				$this->table_prefix . 'notifications'		=> array( +					'COLUMNS'			=> array( +						'notification_id'  				=> array('UINT', NULL, 'auto_increment'), +						'item_type'			   			=> array('VCHAR:255', ''), +						'item_id'		  				=> array('UINT', 0), +						'item_parent_id'   				=> array('UINT', 0), +						'user_id'						=> array('UINT', 0), +						'notification_read'				=> array('BOOL', 0), +						'notification_time'				=> array('TIMESTAMP', 1), +						'notification_data'			   	=> array('TEXT_UNI', ''), +					), +					'PRIMARY_KEY'		=> 'notification_id', +					'KEYS'				=> array( +						'item_ident'		=> array('INDEX', array('item_type', 'item_id')), +						'user'				=> array('INDEX', array('user_id', 'notification_read')), +					), +				), +			), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/310/reported_posts_display.php b/phpBB/phpbb/db/migration/data/310/reported_posts_display.php new file mode 100644 index 0000000000..80a0a0e43f --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/reported_posts_display.php @@ -0,0 +1,47 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_reported_posts_display extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return $this->db_tools->sql_column_exists($this->table_prefix . 'reports', 'reported_post_enable_bbcode'); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_11'); +	} + +	public function update_schema() +	{ +		return array( +			'add_columns'		=> array( +				$this->table_prefix . 'reports'		=> array( +					'reported_post_enable_bbcode'		=> array('BOOL', 1), +					'reported_post_enable_smilies'		=> array('BOOL', 1), +					'reported_post_enable_magic_url'	=> array('BOOL', 1), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'drop_columns'		=> array( +				$this->table_prefix . 'reports'		=> array( +					'reported_post_enable_bbcode', +					'reported_post_enable_smilies', +					'reported_post_enable_magic_url', +				), +			), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/310/signature_module_auth.php b/phpBB/phpbb/db/migration/data/310/signature_module_auth.php new file mode 100644 index 0000000000..e4fbb27bcb --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/signature_module_auth.php @@ -0,0 +1,51 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_signature_module_auth extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		$sql = 'SELECT module_auth +			FROM ' . MODULES_TABLE . " +			WHERE module_class = 'ucp' +				AND module_basename = 'ucp_profile' +				AND module_mode = 'signature'"; +		$result = $this->db->sql_query($sql); +		$module_auth = $this->db_sql_fetchfield('module_auth'); +		$this->db->sql_freeresult($result); + +		return $module_auth === 'acl_u_sig' || $module_auth === false; +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_31x_dev'); +	} + +	public function update_data() +	{ +		return array( +			array('custom', array( +					array($this, 'update_signature_module_auth'), +				), +			), +		); +	} + +	public function update_signature_module_auth() +	{ +		$sql = 'UPDATE ' . MODULES_TABLE . " +			SET module_auth = 'acl_u_sig' +			WHERE module_class = 'ucp' +				AND module_basename = 'ucp_profile' +				AND module_mode = 'signature' +				AND module_auth = ''"; +		$this->db->sql_query($sql); +	} +} diff --git a/phpBB/phpbb/db/migration/data/310/softdelete_p1.php b/phpBB/phpbb/db/migration/data/310/softdelete_p1.php new file mode 100644 index 0000000000..84f8eebd4a --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/softdelete_p1.php @@ -0,0 +1,171 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_softdelete_p1 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return $this->db_tools->sql_column_exists($this->table_prefix . 'posts', 'post_visibility'); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_310_dev'); +	} + +	public function update_schema() +	{ +		return array( +			'add_columns'		=> array( +				$this->table_prefix . 'forums'		=> array( +					'forum_posts_approved'		=> array('UINT', 0), +					'forum_posts_unapproved'	=> array('UINT', 0), +					'forum_posts_softdeleted'	=> array('UINT', 0), +					'forum_topics_approved'		=> array('UINT', 0), +					'forum_topics_unapproved'	=> array('UINT', 0), +					'forum_topics_softdeleted'	=> array('UINT', 0), +				), +				$this->table_prefix . 'posts'		=> array( +					'post_visibility'		=> array('TINT:3', 0), +					'post_delete_time'		=> array('TIMESTAMP', 0), +					'post_delete_reason'	=> array('STEXT_UNI', ''), +					'post_delete_user'		=> array('UINT', 0), +				), +				$this->table_prefix . 'topics'		=> array( +					'topic_visibility'		=> array('TINT:3', 0), +					'topic_delete_time'		=> array('TIMESTAMP', 0), +					'topic_delete_reason'	=> array('STEXT_UNI', ''), +					'topic_delete_user'		=> array('UINT', 0), +					'topic_posts_approved'		=> array('UINT', 0), +					'topic_posts_unapproved'	=> array('UINT', 0), +					'topic_posts_softdeleted'	=> array('UINT', 0), +				), +			), +			'add_index'		=> array( +				$this->table_prefix . 'posts'		=> array( +					'post_visibility'		=> array('post_visibility'), +				), +				$this->table_prefix . 'topics'		=> array( +					'topic_visibility'		=> array('topic_visibility'), +					'forum_vis_last'		=> array('forum_id', 'topic_visibility', 'topic_last_post_id'), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'drop_columns'		=> array( +				$this->table_prefix . 'forums'		=> array( +					'forum_posts_approved', +					'forum_posts_unapproved', +					'forum_posts_softdeleted', +					'forum_topics_approved', +					'forum_topics_unapproved', +					'forum_topics_softdeleted', +				), +				$this->table_prefix . 'posts'		=> array( +					'post_visibility', +					'post_delete_time', +					'post_delete_reason', +					'post_delete_user', +				), +				$this->table_prefix . 'topics'		=> array( +					'topic_visibility', +					'topic_delete_time', +					'topic_delete_reason', +					'topic_delete_user', +					'topic_posts_approved', +					'topic_posts_unapproved', +					'topic_posts_softdeleted', +				), +			), +			'drop_keys'		=> array( +				$this->table_prefix . 'posts'		=> array('post_visibility'), +				$this->table_prefix . 'topics'	=> array('topic_visibility', 'forum_vis_last'), +			), +		); +	} + +	public function update_data() +	{ +		return array( +			array('custom', array(array($this, 'update_post_visibility'))), +			array('custom', array(array($this, 'update_topic_visibility'))), +			array('custom', array(array($this, 'update_topic_forum_counts'))), + +			array('permission.add', array('f_softdelete', false)), +			array('permission.add', array('m_softdelete', false)), +		); +	} + +	public function update_post_visibility() +	{ +		$sql = 'UPDATE ' . $this->table_prefix . 'posts +			SET post_visibility = post_approved'; +		$this->sql_query($sql); +	} + +	public function update_topic_visibility() +	{ +		$sql = 'UPDATE ' . $this->table_prefix . 'topics +			SET topic_visibility = topic_approved'; +		$this->sql_query($sql); +	} + +	public function update_topic_forum_counts() +	{ +		$sql = 'UPDATE ' . $this->table_prefix . 'topics +			SET topic_posts_approved = topic_replies + 1, +				topic_posts_unapproved = topic_replies_real - topic_replies +			WHERE topic_visibility = ' . ITEM_APPROVED; +		$this->sql_query($sql); + +		$sql = 'UPDATE ' . $this->table_prefix . 'topics +			SET topic_posts_approved = 0, +				topic_posts_unapproved = (topic_replies_real - topic_replies) + 1 +			WHERE topic_visibility = ' . ITEM_UNAPPROVED; +		$this->sql_query($sql); + +		$sql = 'SELECT forum_id, topic_visibility, COUNT(topic_id) AS sum_topics, SUM(topic_posts_approved) AS sum_posts_approved, SUM(topic_posts_unapproved) AS sum_posts_unapproved +			FROM ' . $this->table_prefix . 'topics +			GROUP BY forum_id, topic_visibility'; +		$result = $this->db->sql_query($sql); + +		$update_forums = array(); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$forum_id = (int) $row['forum_id']; +			if (!isset($update_forums[$forum_id])) +			{ +				$update_forums[$forum_id] = array( +					'forum_posts_approved'		=> 0, +					'forum_posts_unapproved'	=> 0, +					'forum_topics_approved'		=> 0, +					'forum_topics_unapproved'	=> 0, +				); +			} + +			$update_forums[$forum_id]['forum_posts_approved'] += (int) $row['sum_posts_approved']; +			$update_forums[$forum_id]['forum_posts_unapproved'] += (int) $row['sum_posts_unapproved']; + +			$update_forums[$forum_id][(($row['topic_visibility'] == ITEM_APPROVED) ? 'forum_topics_approved' : 'forum_topics_unapproved')] += (int) $row['sum_topics']; +		} +		$this->db->sql_freeresult($result); + +		foreach ($update_forums as $forum_id => $forum_data) +		{ +			$sql = 'UPDATE ' . FORUMS_TABLE . ' +				SET ' . $this->db->sql_build_array('UPDATE', $forum_data) . ' +				WHERE forum_id = ' . $forum_id; +			$this->sql_query($sql); +		} +	} +} diff --git a/phpBB/phpbb/db/migration/data/310/softdelete_p2.php b/phpBB/phpbb/db/migration/data/310/softdelete_p2.php new file mode 100644 index 0000000000..7320a2c2bf --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/softdelete_p2.php @@ -0,0 +1,68 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_softdelete_p2 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return !$this->db_tools->sql_column_exists($this->table_prefix . 'posts', 'post_approved'); +	} + +	static public function depends_on() +	{ +		return array( +			'phpbb_db_migration_data_310_dev', +			'phpbb_db_migration_data_310_softdelete_p1', +		); +	} + +	public function update_schema() +	{ +		return array( +			'drop_columns'		=> array( +				$this->table_prefix . 'forums'			=> array('forum_posts', 'forum_topics', 'forum_topics_real'), +				$this->table_prefix . 'posts'			=> array('post_approved'), +				$this->table_prefix . 'topics'			=> array('topic_approved', 'topic_replies', 'topic_replies_real'), +			), +			'drop_keys'		=> array( +				$this->table_prefix . 'posts'			=> array('post_approved'), +				$this->table_prefix . 'topics'			=> array('forum_appr_last'), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'add_columns'		=> array( +				$this->table_prefix . 'forums'			=> array( +					'forum_posts'			=> array('UINT', 0), +					'forum_topics'			=> array('UINT', 0), +					'forum_topics_real'		=> array('UINT', 0), +				), +				$this->table_prefix . 'posts'			=> array( +					'post_approved'			=> array('BOOL', 1), +				), +				$this->table_prefix . 'topics'		=> array( +					'topic_approved'			=> array('BOOL', 1), +					'topic_replies'				=> array('UINT', 0), +					'topic_replies_real'		=> array('UINT', 0), +				), +			), +			'add_index'		=> array( +				$this->table_prefix . 'posts'			=> array( +					'post_approved'		=> array('post_approved'), +				), +				$this->table_prefix . 'topics'		=> array( +					'forum_appr_last'	=> array('forum_id', 'topic_approved', 'topic_last_post_id'), +				), +			), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/310/style_update_p1.php b/phpBB/phpbb/db/migration/data/310/style_update_p1.php new file mode 100644 index 0000000000..d43537559d --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/style_update_p1.php @@ -0,0 +1,185 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_style_update_p1 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return !$this->db_tools->sql_table_exists($this->table_prefix . 'styles_imageset'); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_11'); +	} + +	public function update_schema() +	{ +		return array( +			'add_columns'	=> array( +				$this->table_prefix . 'styles'		=> array( +					'style_path'			=> array('VCHAR:100', ''), +					'bbcode_bitfield'		=> array('VCHAR:255', 'kNg='), +					'style_parent_id'		=> array('UINT', 0), +					'style_parent_tree'		=> array('TEXT', ''), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'drop_columns'	=> array( +				$this->table_prefix . 'styles'		=> array( +					'style_path', +					'bbcode_bitfield', +					'style_parent_id', +					'style_parent_tree', +				), +			), +		); +	} + +	public function update_data() +	{ +		return array( +			array('custom', array(array($this, 'styles_update'))), +		); +	} + +	public function styles_update() +	{ +		// Get list of valid 3.1 styles +		$available_styles = array('prosilver'); + +		$iterator = new DirectoryIterator($this->phpbb_root_path . 'styles'); +		$skip_dirs = array('.', '..', 'prosilver'); +		foreach ($iterator as $fileinfo) +		{ +			if ($fileinfo->isDir() && !in_array($fileinfo->getFilename(), $skip_dirs) && file_exists($fileinfo->getPathname() . '/style.cfg')) +			{ +				$style_cfg = parse_cfg_file($fileinfo->getPathname() . '/style.cfg'); +				if (isset($style_cfg['phpbb_version']) && version_compare($style_cfg['phpbb_version'], '3.1.0-dev', '>=')) +				{ +					// 3.1 style +					$available_styles[] = $fileinfo->getFilename(); +				} +			} +		} + +		// Get all installed styles +		if ($this->db_tools->sql_table_exists($this->table_prefix . 'styles_imageset')) +		{ +			$sql = 'SELECT s.style_id, t.template_path, t.template_id, t.bbcode_bitfield, t.template_inherits_id, t.template_inherit_path, c.theme_path, c.theme_id, i.imageset_path +				FROM ' . STYLES_TABLE . ' s, ' . $this->table_prefix . 'styles_template t, ' . $this->table_prefix . 'styles_theme c, ' . $this->table_prefix . "styles_imageset i +				WHERE t.template_id = s.template_id +					AND c.theme_id = s.theme_id +					AND i.imageset_id = s.imageset_id"; +		} +		else +		{ +			$sql = 'SELECT s.style_id, t.template_path, t.template_id, t.bbcode_bitfield, t.template_inherits_id, t.template_inherit_path, c.theme_path, c.theme_id +				FROM ' . STYLES_TABLE . ' s, ' . $this->table_prefix . 'styles_template t, ' . $this->table_prefix . "stles_theme c +				WHERE t.template_id = s.template_id +					AND c.theme_id = s.theme_id"; +		} +		$result = $this->db->sql_query($sql); + +		$styles = array(); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$styles[] = $row; +		} +		$this->db->sql_freeresult($result); + +		// Decide which styles to keep, all others will be deleted +		$valid_styles = array(); +		foreach ($styles as $style_row) +		{ +			if ( +				// Delete styles with parent style (not supported yet) +				$style_row['template_inherits_id'] == 0 && +				// Check if components match +				$style_row['template_path'] == $style_row['theme_path'] && (!isset($style_row['imageset_path']) || $style_row['template_path'] == $style_row['imageset_path']) && +				// Check if components are valid +				in_array($style_row['template_path'], $available_styles) +				) +			{ +				// Valid style. Keep it +				$sql_ary = array( +					'style_path'	=> $style_row['template_path'], +					'bbcode_bitfield'	=> $style_row['bbcode_bitfield'], +					'style_parent_id'	=> 0, +					'style_parent_tree'	=> '', +				); +				$this->sql_query('UPDATE ' . STYLES_TABLE . ' +					SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' +					WHERE style_id = ' . $style_row['style_id']); +				$valid_styles[] = (int) $style_row['style_id']; +			} +		} + +		// Remove old entries from styles table +		if (!sizeof($valid_styles)) +		{ +			// No valid styles: remove everything and add prosilver +			$this->sql_query('DELETE FROM ' . STYLES_TABLE, $errored, $error_ary); + +			$sql_ary = array( +				'style_name'		=> 'prosilver', +				'style_copyright'	=> '© phpBB Group', +				'style_active'		=> 1, +				'style_path'		=> 'prosilver', +				'bbcode_bitfield'	=> 'lNg=', +				'style_parent_id'	=> 0, +				'style_parent_tree'	=> '', + +				// Will be removed in the next step +				'imageset_id'		=> 0, +				'template_id'		=> 0, +				'theme_id'			=> 0, +			); + +			$sql = 'INSERT INTO ' . STYLES_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); +			$this->sql_query($sql); + +			$sql = 'SELECT style_id +				FROM ' . $table . " +				WHERE style_name = 'prosilver'"; +			$result = $this->sql_query($sql); +			$default_style = $this->db->sql_fetchfield($result); +			$this->db->sql_freeresult($result); + +			set_config('default_style', $default_style); + +			$sql = 'UPDATE ' . USERS_TABLE . ' SET user_style = 0'; +			$this->sql_query($sql); +		} +		else +		{ +			// There are valid styles in styles table. Remove styles that are outdated +			$this->sql_query('DELETE FROM ' . STYLES_TABLE . ' +				WHERE ' . $this->db->sql_in_set('style_id', $valid_styles, true)); + +			// Change default style +			if (!in_array($this->config['default_style'], $valid_styles)) +			{ +				$this->sql_query('UPDATE ' . CONFIG_TABLE . " +					SET config_value = '" . $valid_styles[0] . "' +					WHERE config_name = 'default_style'"); +			} + +			// Reset styles for users +			$this->sql_query('UPDATE ' . USERS_TABLE . ' +				SET user_style = 0 +				WHERE ' . $this->db->sql_in_set('user_style', $valid_styles, true)); +		} +	} +} diff --git a/phpBB/phpbb/db/migration/data/310/style_update_p2.php b/phpBB/phpbb/db/migration/data/310/style_update_p2.php new file mode 100644 index 0000000000..7b10518a66 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/style_update_p2.php @@ -0,0 +1,129 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_style_update_p2 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return !$this->db_tools->sql_table_exists($this->table_prefix . 'styles_imageset'); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_310_style_update_p1'); +	} + +	public function update_schema() +	{ +		return array( +			'drop_columns'	=> array( +				$this->table_prefix . 'styles'		=> array( +					'imageset_id', +					'template_id', +					'theme_id', +				), +			), + +			'drop_tables'	=> array( +				$this->table_prefix . 'styles_imageset', +				$this->table_prefix . 'styles_imageset_data', +				$this->table_prefix . 'styles_template', +				$this->table_prefix . 'styles_template_data', +				$this->table_prefix . 'styles_theme', +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'add_columns'	=> array( +				$this->table_prefix . 'styles'		=> array( +					'imageset_id'	=> array('UINT', 0), +					'template_id'	=> array('UINT', 0), +					'theme_id'		=> array('UINT', 0), +				), +			), + +			'add_tables'	=> array( +				$this->table_prefix . 'styles_imageset'		=> array( +					'COLUMNS'		=> array( +						'imageset_id'				=> array('UINT', NULL, 'auto_increment'), +						'imageset_name'				=> array('VCHAR_UNI:255', ''), +						'imageset_copyright'		=> array('VCHAR_UNI', ''), +						'imageset_path'				=> array('VCHAR:100', ''), +					), +					'PRIMARY_KEY'		=> 'imageset_id', +					'KEYS'				=> array( +						'imgset_nm'			=> array('UNIQUE', 'imageset_name'), +					), +				), +				$this->table_prefix . 'styles_imageset_data'	=> array( +					'COLUMNS'		=> array( +						'image_id'				=> array('UINT', NULL, 'auto_increment'), +						'image_name'			=> array('VCHAR:200', ''), +						'image_filename'		=> array('VCHAR:200', ''), +						'image_lang'			=> array('VCHAR:30', ''), +						'image_height'			=> array('USINT', 0), +						'image_width'			=> array('USINT', 0), +						'imageset_id'			=> array('UINT', 0), +					), +					'PRIMARY_KEY'		=> 'image_id', +					'KEYS'				=> array( +						'i_d'			=> array('INDEX', 'imageset_id'), +					), +				), +				$this->table_prefix . 'styles_template'		=> array( +					'COLUMNS'		=> array( +						'template_id'			=> array('UINT', NULL, 'auto_increment'), +						'template_name'			=> array('VCHAR_UNI:255', ''), +						'template_copyright'	=> array('VCHAR_UNI', ''), +						'template_path'			=> array('VCHAR:100', ''), +						'bbcode_bitfield'		=> array('VCHAR:255', 'kNg='), +						'template_storedb'		=> array('BOOL', 0), +						'template_inherits_id'		=> array('UINT:4', 0), +						'template_inherit_path'		=> array('VCHAR', ''), +					), +					'PRIMARY_KEY'	=> 'template_id', +					'KEYS'			=> array( +						'tmplte_nm'				=> array('UNIQUE', 'template_name'), +					), +				), +				$this->table_prefix . 'styles_template_data'	=> array( +					'COLUMNS'		=> array( +						'template_id'			=> array('UINT', 0), +						'template_filename'		=> array('VCHAR:100', ''), +						'template_included'		=> array('TEXT', ''), +						'template_mtime'		=> array('TIMESTAMP', 0), +						'template_data'			=> array('MTEXT_UNI', ''), +					), +					'KEYS'			=> array( +						'tid'					=> array('INDEX', 'template_id'), +						'tfn'					=> array('INDEX', 'template_filename'), +					), +				), +				$this->table_prefix . 'styles_theme'			=> array( +					'COLUMNS'		=> array( +						'theme_id'				=> array('UINT', NULL, 'auto_increment'), +						'theme_name'			=> array('VCHAR_UNI:255', ''), +						'theme_copyright'		=> array('VCHAR_UNI', ''), +						'theme_path'			=> array('VCHAR:100', ''), +						'theme_storedb'			=> array('BOOL', 0), +						'theme_mtime'			=> array('TIMESTAMP', 0), +						'theme_data'			=> array('MTEXT_UNI', ''), +					), +					'PRIMARY_KEY'	=> 'theme_id', +					'KEYS'			=> array( +						'theme_name'		=> array('UNIQUE', 'theme_name'), +					), +				), +			), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/310/teampage.php b/phpBB/phpbb/db/migration/data/310/teampage.php new file mode 100644 index 0000000000..4e77da17b7 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/teampage.php @@ -0,0 +1,104 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_teampage extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return $this->db_tools->sql_table_exists($this->table_prefix . 'teampage'); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_310_dev'); +	} + +	public function update_schema() +	{ +		return array( +			'add_tables'		=> array( +				$this->table_prefix . 'teampage'	=> array( +					'COLUMNS'		=> array( +						'teampage_id'		=> array('UINT', NULL, 'auto_increment'), +						'group_id'			=> array('UINT', 0), +						'teampage_name'		=> array('VCHAR_UNI:255', ''), +						'teampage_position'	=> array('UINT', 0), +						'teampage_parent'	=> array('UINT', 0), +					), +					'PRIMARY_KEY'		=> 'teampage_id', +				), +			), +			'drop_columns'		=> array( +				$this->table_prefix . 'groups'		=> array( +					'group_teampage', +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'drop_tables'		=> array( +				$this->table_prefix . 'teampage', +			), +			'add_columns'		=> array( +				$this->table_prefix . 'groups'		=> array( +					'group_teampage'	=> array('UINT', 0, 'after' => 'group_legend'), +				), +			), +		); +	} + +	public function update_data() +	{ +		return array( +			array('custom', array(array($this, 'add_groups_teampage'))), +		); +	} + +	public function add_groups_teampage() +	{ +		$sql = 'SELECT teampage_id +			FROM ' . TEAMPAGE_TABLE; +		$result = $this->db->sql_query_limit($sql, 1); +		$added_groups_teampage = (bool) $this->db->sql_fetchfield('teampage_id'); +		$this->db->sql_freeresult($result); + +		if (!$added_groups_teampage) +		{ +			$sql = 'SELECT * +				FROM ' . GROUPS_TABLE . ' +				WHERE group_type = ' . GROUP_SPECIAL . " +					AND (group_name = 'ADMINISTRATORS' +						OR group_name = 'GLOBAL_MODERATORS') +				ORDER BY group_name ASC"; +			$result = $this->db->sql_query($sql); + +			$teampage_entries = array(); +			while ($row = $this->db->sql_fetchrow($result)) +			{ +				$teampage_entries[] = array( +					'group_id'			=> (int) $row['group_id'], +					'teampage_name'		=> '', +					'teampage_position'	=> sizeof($teampage_entries) + 1, +					'teampage_parent'	=> 0, +				); +			} +			$this->db->sql_freeresult($result); + +			if (sizeof($teampage_entries)) +			{ +				$this->db->sql_multi_insert(TEAMPAGE_TABLE, $teampage_entries); +			} +			unset($teampage_entries); +		} + +	} +} diff --git a/phpBB/phpbb/db/migration/data/310/timezone.php b/phpBB/phpbb/db/migration/data/310/timezone.php new file mode 100644 index 0000000000..6e50cbe45f --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/timezone.php @@ -0,0 +1,163 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_timezone extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return !$this->db_tools->sql_column_exists($this->table_prefix . 'users', 'user_dst'); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_30x_3_0_11'); +	} + +	public function update_schema() +	{ +		return array( +			'change_columns'	=> array( +				$this->table_prefix . 'users'			=> array( +					'user_timezone'		=> array('VCHAR:100', ''), +				), +			), +		); +	} + +	public function update_data() +	{ +		return array( +			array('custom', array(array($this, 'update_timezones'))), +		); +	} + +	public function update_timezones() +	{ +		// Update user timezones +		$sql = 'SELECT user_dst, user_timezone +			FROM ' . $this->table_prefix . 'users +			GROUP BY user_timezone, user_dst'; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$sql = 'UPDATE ' . $this->table_prefix . "users +				SET user_timezone = '" . $this->db->sql_escape($this->convert_phpbb30_timezone($row['user_timezone'], $row['user_dst'])) . "' +				WHERE user_timezone = '" . $this->db->sql_escape($row['user_timezone']) . "' +					AND user_dst = " . (int) $row['user_dst']; +			$this->sql_query($sql); +		} +		$this->db->sql_freeresult($result); + +		// Update board default timezone +		$sql = 'UPDATE ' . $this->table_prefix . "config +			SET config_value = '" . $this->convert_phpbb30_timezone($this->config['board_timezone'], $this->config['board_dst']) . "' +			WHERE config_name = 'board_timezone'"; +		$this->sql_query($sql); +	} + +	/** +	* Determine the new timezone for a given phpBB 3.0 timezone and +	* "Daylight Saving Time" option +	* +	*	@param	$timezone	float	Users timezone in 3.0 +	*	@param	$dst		int		Users daylight saving time +	*	@return		string		Users new php Timezone which is used since 3.1 +	*/ +	public function convert_phpbb30_timezone($timezone, $dst) +	{ +		$offset = $timezone + $dst; + +		switch ($timezone) +		{ +			case '-12': +				return 'Etc/GMT+' . abs($offset);	//'[UTC - 12] Baker Island Time' +			case '-11': +				return 'Etc/GMT+' . abs($offset);	//'[UTC - 11] Niue Time, Samoa Standard Time' +			case '-10': +				return 'Etc/GMT+' . abs($offset);	//'[UTC - 10] Hawaii-Aleutian Standard Time, Cook Island Time' +			case '-9.5': +				return 'Pacific/Marquesas';			//'[UTC - 9:30] Marquesas Islands Time' +			case '-9': +				return 'Etc/GMT+' . abs($offset);	//'[UTC - 9] Alaska Standard Time, Gambier Island Time' +			case '-8': +				return 'Etc/GMT+' . abs($offset);	//'[UTC - 8] Pacific Standard Time' +			case '-7': +				return 'Etc/GMT+' . abs($offset);	//'[UTC - 7] Mountain Standard Time' +			case '-6': +				return 'Etc/GMT+' . abs($offset);	//'[UTC - 6] Central Standard Time' +			case '-5': +				return 'Etc/GMT+' . abs($offset);	//'[UTC - 5] Eastern Standard Time' +			case '-4.5': +				return 'America/Caracas';			//'[UTC - 4:30] Venezuelan Standard Time' +			case '-4': +				return 'Etc/GMT+' . abs($offset);	//'[UTC - 4] Atlantic Standard Time' +			case '-3.5': +				return 'America/St_Johns';			//'[UTC - 3:30] Newfoundland Standard Time' +			case '-3': +				return 'Etc/GMT+' . abs($offset);	//'[UTC - 3] Amazon Standard Time, Central Greenland Time' +			case '-2': +				return 'Etc/GMT+' . abs($offset);	//'[UTC - 2] Fernando de Noronha Time, South Georgia & the South Sandwich Islands Time' +			case '-1': +				return 'Etc/GMT+' . abs($offset);	//'[UTC - 1] Azores Standard Time, Cape Verde Time, Eastern Greenland Time' +			case '0': +				return (!$dst) ? 'UTC' : 'Etc/GMT-1';	//'[UTC] Western European Time, Greenwich Mean Time' +			case '1': +				return 'Etc/GMT-' . $offset;		//'[UTC + 1] Central European Time, West African Time' +			case '2': +				return 'Etc/GMT-' . $offset;		//'[UTC + 2] Eastern European Time, Central African Time' +			case '3': +				return 'Etc/GMT-' . $offset;		//'[UTC + 3] Moscow Standard Time, Eastern African Time' +			case '3.5': +				return 'Asia/Tehran';				//'[UTC + 3:30] Iran Standard Time' +			case '4': +				return 'Etc/GMT-' . $offset;		//'[UTC + 4] Gulf Standard Time, Samara Standard Time' +			case '4.5': +				return 'Asia/Kabul';				//'[UTC + 4:30] Afghanistan Time' +			case '5': +				return 'Etc/GMT-' . $offset;		//'[UTC + 5] Pakistan Standard Time, Yekaterinburg Standard Time' +			case '5.5': +				return 'Asia/Kolkata';				//'[UTC + 5:30] Indian Standard Time, Sri Lanka Time' +			case '5.75': +				return 'Asia/Kathmandu';			//'[UTC + 5:45] Nepal Time' +			case '6': +				return 'Etc/GMT-' . $offset;		//'[UTC + 6] Bangladesh Time, Bhutan Time, Novosibirsk Standard Time' +			case '6.5': +				return 'Indian/Cocos';				//'[UTC + 6:30] Cocos Islands Time, Myanmar Time' +			case '7': +				return 'Etc/GMT-' . $offset;		//'[UTC + 7] Indochina Time, Krasnoyarsk Standard Time' +			case '8': +				return 'Etc/GMT-' . $offset;		//'[UTC + 8] Chinese Standard Time, Australian Western Standard Time, Irkutsk Standard Time' +			case '8.75': +				return 'Australia/Eucla';			//'[UTC + 8:45] Southeastern Western Australia Standard Time' +			case '9': +				return 'Etc/GMT-' . $offset;		//'[UTC + 9] Japan Standard Time, Korea Standard Time, Chita Standard Time' +			case '9.5': +				return 'Australia/ACT';				//'[UTC + 9:30] Australian Central Standard Time' +			case '10': +				return 'Etc/GMT-' . $offset;		//'[UTC + 10] Australian Eastern Standard Time, Vladivostok Standard Time' +			case '10.5': +				return 'Australia/Lord_Howe';		//'[UTC + 10:30] Lord Howe Standard Time' +			case '11': +				return 'Etc/GMT-' . $offset;		//'[UTC + 11] Solomon Island Time, Magadan Standard Time' +			case '11.5': +				return 'Pacific/Norfolk';			//'[UTC + 11:30] Norfolk Island Time' +			case '12': +				return 'Etc/GMT-12';				//'[UTC + 12] New Zealand Time, Fiji Time, Kamchatka Standard Time' +			case '12.75': +				return 'Pacific/Chatham';			//'[UTC + 12:45] Chatham Islands Time' +			case '13': +				return 'Pacific/Tongatapu';			//'[UTC + 13] Tonga Time, Phoenix Islands Time' +			case '14': +				return 'Pacific/Kiritimati';		//'[UTC + 14] Line Island Time' +			default: +				return 'UTC'; +		} +	} +} diff --git a/phpBB/phpbb/db/migration/data/310/timezone_p2.php b/phpBB/phpbb/db/migration/data/310/timezone_p2.php new file mode 100644 index 0000000000..113b979e4f --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/timezone_p2.php @@ -0,0 +1,43 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_timezone_p2 extends phpbb_db_migration +{ +	public function effectively_installed() +	{ +		return !$this->db_tools->sql_column_exists($this->table_prefix . 'users', 'user_dst'); +	} + +	static public function depends_on() +	{ +		return array('phpbb_db_migration_data_310_timezone'); +	} + +	public function update_schema() +	{ +		return array( +			'drop_columns'	=> array( +				$this->table_prefix . 'users'			=> array( +					'user_dst', +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'add_columns'	=> array( +				$this->table_prefix . 'users'			=> array( +					'user_dst'		=> array('BOOL', 0), +				), +			), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/exception.php b/phpBB/phpbb/db/migration/exception.php new file mode 100644 index 0000000000..e84330dd71 --- /dev/null +++ b/phpBB/phpbb/db/migration/exception.php @@ -0,0 +1,79 @@ +<?php +/** +* +* @package db +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* The migrator is responsible for applying new migrations in the correct order. +* +* @package db +*/ +class phpbb_db_migration_exception extends \Exception +{ +	/** +	* Extra parameters sent to exception to aid in debugging +	* @var array +	*/ +	protected $parameters; + +	/** +	* Throw an exception. +	* +	* First argument is the error message. +	* Additional arguments will be output with the error message. +	*/ +	public function __construct() +	{ +		$parameters = func_get_args(); +		$message = array_shift($parameters); +		parent::__construct($message); + +		$this->parameters = $parameters; +	} + +	/** +	* Output the error as a string +	* +	* @return string +	*/ +	public function __toString() +	{ +		return $this->message . ': ' . var_export($this->parameters, true); +	} + +	/** +	* Get the parameters +	* +	* @return array +	*/ +	public function getParameters() +	{ +		return $this->parameters; +	} + +	/** +	* Get localised message (with $user->lang())  +	*  +	* @param phpbb_user $user +	* @return string +	*/ +	public function getLocalisedMessage(phpbb_user $user) +	{ +		$parameters = $this->getParameters(); +		array_unshift($parameters, $this->getMessage()); + +		return call_user_func_array(array($user, 'lang'), $parameters); +	} +} diff --git a/phpBB/phpbb/db/migration/migration.php b/phpBB/phpbb/db/migration/migration.php new file mode 100644 index 0000000000..0ffa96fd14 --- /dev/null +++ b/phpBB/phpbb/db/migration/migration.php @@ -0,0 +1,190 @@ +<?php +/** +* +* @package db +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* Abstract base class for database migrations +* +* Each migration consists of a set of schema and data changes to be implemented +* in a subclass. This class provides various utility methods to simplify editing +* a phpBB. +* +* @package db +*/ +abstract class phpbb_db_migration +{ +	/** @var phpbb_config */ +	protected $config; + +	/** @var phpbb_db_driver */ +	protected $db; + +	/** @var phpbb_db_tools */ +	protected $db_tools; + +	/** @var string */ +	protected $table_prefix; + +	/** @var string */ +	protected $phpbb_root_path; + +	/** @var string */ +	protected $php_ext; + +	/** @var array Errors, if any occurred */ +	protected $errors; + +	/** @var array List of queries executed through $this->sql_query() */ +	protected $queries = array(); + +	/** +	* Constructor +	* +	* @param phpbb_config $config +	* @param phpbb_db_driver $db +	* @param phpbb_db_tools $db_tools +	* @param string $phpbb_root_path +	* @param string $php_ext +	* @param string $table_prefix +	*/ +	public function __construct(phpbb_config $config, phpbb_db_driver $db, phpbb_db_tools $db_tools, $phpbb_root_path, $php_ext, $table_prefix) +	{ +		$this->config = $config; +		$this->db = $db; +		$this->db_tools = $db_tools; +		$this->table_prefix = $table_prefix; + +		$this->phpbb_root_path = $phpbb_root_path; +		$this->php_ext = $php_ext; + +		$this->errors = array(); +	} + +	/** +	* Defines other migrations to be applied first +	* +	* @return array An array of migration class names +	*/ +	static public function depends_on() +	{ +		return array(); +	} + +	/** +	* Allows you to check if the migration is effectively installed (entirely optional) +	* +	* This is checked when a migration is installed. If true is returned, the migration will be set as +	* installed without performing the database changes. +	* This function is intended to help moving to migrations from a previous database updater, where some +	* migrations may have been installed already even though they are not yet listed in the migrations table. +	* +	* @return bool True if this migration is installed, False if this migration is not installed (checked on install) +	*/ +	public function effectively_installed() +	{ +		return false; +	} + +	/** +	* Updates the database schema by providing a set of change instructions +	* +	* @return array Array of schema changes (compatible with db_tools->perform_schema_changes()) +	*/ +	public function update_schema() +	{ +		return array(); +	} + +	/** +	* Reverts the database schema by providing a set of change instructions +	* +	* @return array Array of schema changes (compatible with db_tools->perform_schema_changes()) +	*/ +	public function revert_schema() +	{ +		return array(); +	} + +	/** +	* Updates data by returning a list of instructions to be executed +	* +	* @return array Array of data update instructions +	*/ +	public function update_data() +	{ +		return array(); +	} + +	/** +	* Reverts data by returning a list of instructions to be executed +	* +	* @return array Array of data instructions that will be performed on revert +	* 	NOTE: calls to tools (such as config.add) are automatically reverted when +	* 		possible, so you should not attempt to revert those, this is mostly for +	* 		otherwise unrevertable calls (custom functions for example) +	*/ +	public function revert_data() +	{ +		return array(); +	} + +	/** +	* Wrapper for running queries to generate user feedback on updates +	* +	* @param string $sql SQL query to run on the database +	* @return mixed Query result from db->sql_query() +	*/ +	protected function sql_query($sql) +	{ +		$this->queries[] = $sql; + +		$this->db->sql_return_on_error(true); + +		if ($sql === 'begin') +		{ +			$result = $this->db->sql_transaction('begin'); +		} +		else if ($sql === 'commit') +		{ +			$result = $this->db->sql_transaction('commit'); +		} +		else +		{ +			$result = $this->db->sql_query($sql); +			if ($this->db->sql_error_triggered) +			{ +				$this->errors[] = array( +					'sql'	=> $this->db->sql_error_sql, +					'code'	=> $this->db->sql_error_returned, +				); +			} +		} + +		$this->db->sql_return_on_error(false); + +		return $result; +	} + +	/** +	* Get the list of queries run +	* +	* @return array +	*/ +	public function get_queries() +	{ +		return $this->queries; +	} +} diff --git a/phpBB/phpbb/db/migration/tool/config.php b/phpBB/phpbb/db/migration/tool/config.php new file mode 100644 index 0000000000..0b626bf455 --- /dev/null +++ b/phpBB/phpbb/db/migration/tool/config.php @@ -0,0 +1,150 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +/** +* Migration config tool +* +* @package db +*/ +class phpbb_db_migration_tool_config implements phpbb_db_migration_tool_interface +{ +	/** @var phpbb_config */ +	protected $config; + +	/** +	* Constructor +	* +	* @param phpbb_config $config +	*/ +	public function __construct(phpbb_config $config) +	{ +		$this->config = $config; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function get_name() +	{ +		return 'config'; +	} + +	/** +	* Add a config setting. +	* +	* @param string $config_name The name of the config setting +	* 	you would like to add +	* @param mixed $config_value The value of the config setting +	* @param bool $is_dynamic True if it is dynamic (changes very often) +	* 	and should not be stored in the cache, false if not. +	* @return null +	*/ +	public function add($config_name, $config_value, $is_dynamic = false) +	{ +		if (isset($this->config[$config_name])) +		{ +			return; +		} + +		$this->config->set($config_name, $config_value, !$is_dynamic); +	} + +	/** +	* Update an existing config setting. +	* +	* @param string $config_name The name of the config setting you would +	* 	like to update +	* @param mixed $config_value The value of the config setting +	* @return null +	*/ +	public function update($config_name, $config_value) +	{ +		if (!isset($this->config[$config_name])) +		{ +			throw new phpbb_db_migration_exception('CONFIG_NOT_EXIST', $config_name); +		} + +		$this->config->set($config_name, $config_value); +	} + +	/** +	* Update a config setting if the first argument equal to the +	* current config value +	* +	* @param string $compare If equal to the current config value, will be +	* 	updated to the new config value, otherwise not +	* @param string $config_name The name of the config setting you would +	* 	like to update +	* @param mixed $config_value The value of the config setting +	* @return null +	*/ +	public function update_if_equals($compare, $config_name, $config_value) +	{ +		if (!isset($this->config[$config_name])) +		{ +			throw new phpbb_db_migration_exception('CONFIG_NOT_EXIST', $config_name); +		} + +		$this->config->set_atomic($config_name, $compare, $config_value); +	} + +	/** +	* Remove an existing config setting. +	* +	* @param string $config_name The name of the config setting you would +	* 	like to remove +	* @return null +	*/ +	public function remove($config_name) +	{ +		if (!isset($this->config[$config_name])) +		{ +			return; +		} + +		$this->config->delete($config_name); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function reverse() +	{ +		$arguments = func_get_args(); +		$original_call = array_shift($arguments); + +		$call = false; +		switch ($original_call) +		{ +			case 'add': +				$call = 'remove'; +			break; + +			case 'remove': +				$call = 'add'; +			break; + +			case 'update_if_equals': +				$call = 'update_if_equals'; + +				// Set to the original value if the current value is what we compared to originally +				$arguments = array( +					$arguments[2], +					$arguments[1], +					$arguments[0], +				); +			break; +		} + +		if ($call) +		{ +			return call_user_func_array(array(&$this, $call), $arguments); +		} +	} +} diff --git a/phpBB/phpbb/db/migration/tool/interface.php b/phpBB/phpbb/db/migration/tool/interface.php new file mode 100644 index 0000000000..ced53b2023 --- /dev/null +++ b/phpBB/phpbb/db/migration/tool/interface.php @@ -0,0 +1,33 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +/** +* Migration tool interface +* +* @package db +*/ +interface phpbb_db_migration_tool_interface +{ +	/** +	* Retrieve a short name used for commands in migrations. +	* +	* @return string short name +	*/ +	public function get_name(); + +	/** +	* Reverse an original install action +	* +	* First argument is the original call to the class (e.g. add, remove) +	* After the first argument, send the original arguments to the function in the original call +	* +	* @return null +	*/ +	public function reverse(); +} diff --git a/phpBB/phpbb/db/migration/tool/module.php b/phpBB/phpbb/db/migration/tool/module.php new file mode 100644 index 0000000000..ac4d2c9bd7 --- /dev/null +++ b/phpBB/phpbb/db/migration/tool/module.php @@ -0,0 +1,501 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +/** +* Migration module management tool +* +* @package db +*/ +class phpbb_db_migration_tool_module implements phpbb_db_migration_tool_interface +{ +	/** @var phpbb_cache_service */ +	protected $cache; + +	/** @var dbal */ +	protected $db; + +	/** @var phpbb_user */ +	protected $user; + +	/** @var string */ +	protected $phpbb_root_path; + +	/** @var string */ +	protected $php_ext; + +	/** @var string */ +	protected $modules_table; + +	/** +	* Constructor +	* +	* @param phpbb_db_driver $db +	* @param mixed $cache +	* @param phpbb_user $user +	* @param string $phpbb_root_path +	* @param string $php_ext +	* @param string $modules_table +	*/ +	public function __construct(phpbb_db_driver $db, phpbb_cache_service $cache, phpbb_user $user, $phpbb_root_path, $php_ext, $modules_table) +	{ +		$this->db = $db; +		$this->cache = $cache; +		$this->user = $user; +		$this->phpbb_root_path = $phpbb_root_path; +		$this->php_ext = $php_ext; +		$this->modules_table = $modules_table; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function get_name() +	{ +		return 'module'; +	} + +	/** +	* Module Exists +	* +	* Check if a module exists +	* +	* @param string $class The module class(acp|mcp|ucp) +	* @param int|string|bool $parent The parent module_id|module_langname (0 for no parent). +	*		Use false to ignore the parent check and check class wide. +	* @param int|string $module The module_id|module_langname you would like to +	* 		check for to see if it exists +	* @return bool true/false if module exists +	*/ +	public function exists($class, $parent, $module) +	{ +		// the main root directory should return true +		if (!$module) +		{ +			return true; +		} + +		$parent_sql = ''; +		if ($parent !== false) +		{ +			// Allows '' to be sent as 0 +			$parent = $parent ?: 0; + +			if (!is_numeric($parent)) +			{ +				$sql = 'SELECT module_id +					FROM ' . $this->modules_table . " +					WHERE module_langname = '" . $this->db->sql_escape($parent) . "' +						AND module_class = '" . $this->db->sql_escape($class) . "'"; +				$result = $this->db->sql_query($sql); +				$module_id = $this->db->sql_fetchfield('module_id'); +				$this->db->sql_freeresult($result); + +				if (!$module_id) +				{ +					return false; +				} + +				$parent_sql = 'AND parent_id = ' . (int) $module_id; +			} +			else +			{ +				$parent_sql = 'AND parent_id = ' . (int) $parent; +			} +		} + +		$sql = 'SELECT module_id +			FROM ' . $this->modules_table . " +			WHERE module_class = '" . $this->db->sql_escape($class) . "' +				$parent_sql +				AND " . ((is_numeric($module)) ? 'module_id = ' . (int) $module : "module_langname = '" . $this->db->sql_escape($module) . "'"); +		$result = $this->db->sql_query($sql); +		$module_id = $this->db->sql_fetchfield('module_id'); +		$this->db->sql_freeresult($result); + +		if ($module_id) +		{ +			return true; +		} + +		return false; +	} + +	/** +	* Module Add +	* +	* Add a new module +	* +	* @param string $class The module class(acp|mcp|ucp) +	* @param int|string $parent The parent module_id|module_langname (0 for no parent) +	* @param array $data an array of the data on the new module. +	* 	This can be setup in two different ways. +	*	1. The "manual" way.  For inserting a category or one at a time. +	*		It will be merged with the base array shown a bit below, +	*			but at the least requires 'module_langname' to be sent, and, +	*			if you want to create a module (instead of just a category) you must +	*			send module_basename and module_mode. +	*		array( +	*			'module_enabled'	=> 1, +	*			'module_display'	=> 1, +	*	   		'module_basename'	=> '', +	*			'module_class'		=> $class, +	*	   		'parent_id'			=> (int) $parent, +	*			'module_langname'	=> '', +	*	   		'module_mode'		=> '', +	*	   		'module_auth'		=> '', +	*		) +	*	2. The "automatic" way.  For inserting multiple at a time based on the +	*			specs in the info file for the module(s).  For this to work the +	*			modules must be correctly setup in the info file. +	*		An example follows (this would insert the settings, log, and flag +	*			modes from the includes/acp/info/acp_asacp.php file): +	* 		array( +	* 			'module_basename'	=> 'asacp', +	* 			'modes'				=> array('settings', 'log', 'flag'), +	* 		) +	* 		Optionally you may not send 'modes' and it will insert all of the +	* 			modules in that info file. +	* @param string|bool $include_path If you would like to use a custom include +	* 	path, specify that here +	* @return null +	*/ +	public function add($class, $parent = 0, $data = array(), $include_path = false) +	{ +		// Allows '' to be sent as 0 +		$parent = $parent ?: 0; + +		// allow sending the name as a string in $data to create a category +		if (!is_array($data)) +		{ +			$data = array('module_langname' => $data); +		} + +		if (!isset($data['module_langname'])) +		{ +			// The "automatic" way +			$basename = (isset($data['module_basename'])) ? $data['module_basename'] : ''; +			$basename = str_replace(array('/', '\\'), '', $basename); +			$class = str_replace(array('/', '\\'), '', $class); + +			$module = $this->get_module_info($class, $basename); + +			$result = ''; +			foreach ($module['modes'] as $mode => $module_info) +			{ +				if (!isset($data['modes']) || in_array($mode, $data['modes'])) +				{ +					$new_module = array( +						'module_basename'	=> $basename, +						'module_langname'	=> $module_info['title'], +						'module_mode'		=> $mode, +						'module_auth'		=> $module_info['auth'], +						'module_display'	=> (isset($module_info['display'])) ? $module_info['display'] : true, +						'before'			=> (isset($module_info['before'])) ? $module_info['before'] : false, +						'after'				=> (isset($module_info['after'])) ? $module_info['after'] : false, +					); + +					// Run the "manual" way with the data we've collected. +					$this->add($class, $parent, $new_module); +				} +			} + +			return; +		} + +		// The "manual" way +		if (!is_numeric($parent)) +		{ +			$sql = 'SELECT module_id +				FROM ' . $this->modules_table . " +				WHERE module_langname = '" . $this->db->sql_escape($parent) . "' +					AND module_class = '" . $this->db->sql_escape($class) . "'"; +			$result = $this->db->sql_query($sql); +			$module_id = $this->db->sql_fetchfield('module_id'); +			$this->db->sql_freeresult($result); + +			if (!$module_id) +			{ +				throw new phpbb_db_migration_exception('MODULE_NOT_EXIST', $parent); +			} + +			$parent = $data['parent_id'] = $module_id; +		} +		else if (!$this->exists($class, false, $parent)) +		{ +			throw new phpbb_db_migration_exception('MODULE_NOT_EXIST', $parent); +		} + +		if ($this->exists($class, $parent, $data['module_langname'])) +		{ +			return; +		} + +		if (!class_exists('acp_modules')) +		{ +			include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); +			$this->user->add_lang('acp/modules'); +		} +		$acp_modules = new acp_modules(); + +		$module_data = array( +			'module_enabled'	=> (isset($data['module_enabled'])) ? $data['module_enabled'] : 1, +			'module_display'	=> (isset($data['module_display'])) ? $data['module_display'] : 1, +			'module_basename'	=> (isset($data['module_basename'])) ? $data['module_basename'] : '', +			'module_class'		=> $class, +			'parent_id'			=> (int) $parent, +			'module_langname'	=> (isset($data['module_langname'])) ? $data['module_langname'] : '', +			'module_mode'		=> (isset($data['module_mode'])) ? $data['module_mode'] : '', +			'module_auth'		=> (isset($data['module_auth'])) ? $data['module_auth'] : '', +		); +		$result = $acp_modules->update_module_data($module_data, true); + +		// update_module_data can either return a string or an empty array... +		if (is_string($result)) +		{ +			// Error +			throw new phpbb_db_migration_exception('MODULE_ERROR', $result); +		} +		else +		{ +			// Success +			$module_log_name = ((isset($this->user->lang[$data['module_langname']])) ? $this->user->lang[$data['module_langname']] : $data['module_langname']); +			add_log('admin', 'LOG_MODULE_ADD', $module_log_name); + +			// Move the module if requested above/below an existing one +			if (isset($data['before']) && $data['before']) +			{ +				$sql = 'SELECT left_id +					FROM ' . $this->modules_table . " +					WHERE module_class = '" . $this->db->sql_escape($class) . "' +						AND parent_id = " . (int) $parent . " +						AND module_langname = '" . $this->db->sql_escape($data['before']) . "'"; +				$this->db->sql_query($sql); +				$to_left = (int) $this->db->sql_fetchfield('left_id'); + +				$sql = 'UPDATE ' . $this->modules_table . " +					SET left_id = left_id + 2, right_id = right_id + 2 +					WHERE module_class = '" . $this->db->sql_escape($class) . "' +						AND left_id >= $to_left +						AND left_id < {$module_data['left_id']}"; +				$this->db->sql_query($sql); + +				$sql = 'UPDATE ' . $this->modules_table . " +					SET left_id = $to_left, right_id = " . ($to_left + 1) . " +					WHERE module_class = '" . $this->db->sql_escape($class) . "' +						AND module_id = {$module_data['module_id']}"; +				$this->db->sql_query($sql); +			} +			else if (isset($data['after']) && $data['after']) +			{ +				$sql = 'SELECT right_id +					FROM ' . $this->modules_table . " +					WHERE module_class = '" . $this->db->sql_escape($class) . "' +						AND parent_id = " . (int) $parent . " +						AND module_langname = '" . $this->db->sql_escape($data['after']) . "'"; +				$this->db->sql_query($sql); +				$to_right = (int) $this->db->sql_fetchfield('right_id'); + +				$sql = 'UPDATE ' . $this->modules_table . " +					SET left_id = left_id + 2, right_id = right_id + 2 +					WHERE module_class = '" . $this->db->sql_escape($class) . "' +						AND left_id >= $to_right +						AND left_id < {$module_data['left_id']}"; +				$this->db->sql_query($sql); + +				$sql = 'UPDATE ' . $this->modules_table . ' +					SET left_id = ' . ($to_right + 1) . ', right_id = ' . ($to_right + 2) . " +					WHERE module_class = '" . $this->db->sql_escape($class) . "' +						AND module_id = {$module_data['module_id']}"; +				$this->db->sql_query($sql); +			} +		} + +		// Clear the Modules Cache +		$this->cache->destroy("_modules_$class"); +	} + +	/** +	* Module Remove +	* +	* Remove a module +	* +	* @param string $class The module class(acp|mcp|ucp) +	* @param int|string|bool $parent The parent module_id|module_langname(0 for no parent). +	* 	Use false to ignore the parent check and check class wide. +	* @param int|string $module The module id|module_langname +	* @param string|bool $include_path If you would like to use a custom include path, +	* 	specify that here +	* @return null +	*/ +	public function remove($class, $parent = 0, $module = '', $include_path = false) +	{ +		// Imitation of module_add's "automatic" and "manual" method so the uninstaller works from the same set of instructions for umil_auto +		if (is_array($module)) +		{ +			if (isset($module['module_langname'])) +			{ +				// Manual Method +				return $this->remove($class, $parent, $module['module_langname'], $include_path); +			} + +			// Failed. +			if (!isset($module['module_basename'])) +			{ +				throw new phpbb_db_migration_exception('MODULE_NOT_EXIST'); +			} + +			// Automatic method +			$basename = str_replace(array('/', '\\'), '', $module['module_basename']); +			$class = str_replace(array('/', '\\'), '', $class); + +			$module_info = $this->get_module_info($class, $basename); + +			foreach ($module_info['modes'] as $mode => $info) +			{ +				if (!isset($module['modes']) || in_array($mode, $module['modes'])) +				{ +					$this->remove($class, $parent, $info['title']); +				} +			} +		} +		else +		{ +			if (!$this->exists($class, $parent, $module)) +			{ +				return; +			} + +			$parent_sql = ''; +			if ($parent !== false) +			{ +				// Allows '' to be sent as 0 +				$parent = ($parent) ?: 0; + +				if (!is_numeric($parent)) +				{ +					$sql = 'SELECT module_id +						FROM ' . $this->modules_table . " +						WHERE module_langname = '" . $this->db->sql_escape($parent) . "' +							AND module_class = '" . $this->db->sql_escape($class) . "'"; +					$result = $this->db->sql_query($sql); +					$module_id = $this->db->sql_fetchfield('module_id'); +					$this->db->sql_freeresult($result); + +					// we know it exists from the module_exists check +					$parent_sql = 'AND parent_id = ' . (int) $module_id; +				} +				else +				{ +					$parent_sql = 'AND parent_id = ' . (int) $parent; +				} +			} + +			$module_ids = array(); +			if (!is_numeric($module)) +			{ +				$sql = 'SELECT module_id +					FROM ' . $this->modules_table . " +					WHERE module_langname = '" . $this->db->sql_escape($module) . "' +						AND module_class = '" . $this->db->sql_escape($class) . "' +						$parent_sql"; +				$result = $this->db->sql_query($sql); +				while ($module_id = $this->db->sql_fetchfield('module_id')) +				{ +					$module_ids[] = (int) $module_id; +				} +				$this->db->sql_freeresult($result); + +				$module_name = $module; +			} +			else +			{ +				$module = (int) $module; +				$sql = 'SELECT module_langname +					FROM ' . $this->modules_table . " +					WHERE module_id = $module +						AND module_class = '" . $this->db->sql_escape($class) . "' +						$parent_sql"; +				$result = $this->db->sql_query($sql); +				$module_name = $this->db->sql_fetchfield('module_id'); +				$this->db->sql_freeresult($result); + +				$module_ids[] = $module; +			} + +			if (!class_exists('acp_modules')) +			{ +				include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); +				$this->user->add_lang('acp/modules'); +			} +			$acp_modules = new acp_modules(); +			$acp_modules->module_class = $class; + +			foreach ($module_ids as $module_id) +			{ +				$result = $acp_modules->delete_module($module_id); +				if (!empty($result)) +				{ +					return; +				} +			} + +			$this->cache->destroy("_modules_$class"); +		} +	} + +	/** +	* {@inheritdoc} +	*/ +	public function reverse() +	{ +		$arguments = func_get_args(); +		$original_call = array_shift($arguments); + +		$call = false; +		switch ($original_call) +		{ +			case 'add': +				$call = 'remove'; +			break; + +			case 'remove': +				$call = 'add'; +			break; +		} + +		if ($call) +		{ +			return call_user_func_array(array(&$this, $call), $arguments); +		} +	} + +	/** +	* Wrapper for acp_modules::get_module_infos() +	* +	* @param string $class Module Class +	* @param string $basename Module Basename +	* @return array Module Information +	*/ +	protected function get_module_info($class, $basename) +	{ +		if (!class_exists('acp_modules')) +		{ +			include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); +		} +		$acp_modules = new acp_modules(); +		$module = $acp_modules->get_module_infos($basename, $class, true); + +		if (empty($module)) +		{ +			throw new phpbb_db_migration_exception('MODULE_INFO_FILE_NOT_EXIST', $class, $basename); +		} + +		return array_pop($module); +	} +} diff --git a/phpBB/phpbb/db/migration/tool/permission.php b/phpBB/phpbb/db/migration/tool/permission.php new file mode 100644 index 0000000000..2f09c0ac72 --- /dev/null +++ b/phpBB/phpbb/db/migration/tool/permission.php @@ -0,0 +1,622 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2012 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +/** +* Migration permission management tool +* +* @package db +*/ +class phpbb_db_migration_tool_permission implements phpbb_db_migration_tool_interface +{ +	/** @var phpbb_auth */ +	protected $auth; + +	/** @var phpbb_cache_service */ +	protected $cache; + +	/** @var dbal */ +	protected $db; + +	/** @var string */ +	protected $phpbb_root_path; + +	/** @var string */ +	protected $php_ext; + +	/** +	* Constructor +	* +	* @param phpbb_db_driver $db +	* @param mixed $cache +	* @param phpbb_auth $auth +	* @param string $phpbb_root_path +	* @param string $php_ext +	*/ +	public function __construct(phpbb_db_driver $db, phpbb_cache_service $cache, phpbb_auth $auth, $phpbb_root_path, $php_ext) +	{ +		$this->db = $db; +		$this->cache = $cache; +		$this->auth = $auth; +		$this->phpbb_root_path = $phpbb_root_path; +		$this->php_ext = $php_ext; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function get_name() +	{ +		return 'permission'; +	} + +	/** +	* Permission Exists +	* +	* Check if a permission (auth) setting exists +	* +	* @param string $auth_option The name of the permission (auth) option +	* @param bool $global True for checking a global permission setting, +	* 	False for a local permission setting +	* @return bool true if it exists, false if not +	*/ +	public function exists($auth_option, $global = true) +	{ +		if ($global) +		{ +			$type_sql = ' AND is_global = 1'; +		} +		else +		{ +			$type_sql = ' AND is_local = 1'; +		} + +		$sql = 'SELECT auth_option_id +			FROM ' . ACL_OPTIONS_TABLE . " +			WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'" +				. $type_sql; +		$result = $this->db->sql_query($sql); + +		$row = $this->db->sql_fetchrow($result); +		$this->db->sql_freeresult($result); + +		if ($row) +		{ +			return true; +		} + +		return false; +	} + +	/** +	* Permission Add +	* +	* Add a permission (auth) option +	* +	* @param string $auth_option The name of the permission (auth) option +	* @param bool $global True for checking a global permission setting, +	* 	False for a local permission setting +	* @return null +	*/ +	public function add($auth_option, $global = true, $copy_from = false) +	{ +		if ($this->exists($auth_option, $global)) +		{ +			return; +		} + +		// We've added permissions, so set to true to notify the user. +		$this->permissions_added = true; + +		if (!class_exists('auth_admin')) +		{ +			include($this->phpbb_root_path . 'includes/acp/auth.' . $this->php_ext); +		} +		$auth_admin = new auth_admin(); + +		// We have to add a check to see if the !$global (if global, local, and if local, global) permission already exists.  If it does, acl_add_option currently has a bug which would break the ACL system, so we are having a work-around here. +		if ($this->exists($auth_option, !$global)) +		{ +			$sql_ary = array( +				'is_global'	=> 1, +				'is_local'	=> 1, +			); +			$sql = 'UPDATE ' . ACL_OPTIONS_TABLE . ' +				SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . " +				WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'"; +			$this->db->sql_query($sql); +		} +		else +		{ +			if ($global) +			{ +				$auth_admin->acl_add_option(array('global' => array($auth_option))); +			} +			else +			{ +				$auth_admin->acl_add_option(array('local' => array($auth_option))); +			} +		} + +		// The permission has been added, now we can copy it if needed +		if ($copy_from && isset($auth_admin->acl_options['id'][$copy_from])) +		{ +			$old_id = $auth_admin->acl_options['id'][$copy_from]; +			$new_id = $auth_admin->acl_options['id'][$auth_option]; + +			$tables = array(ACL_GROUPS_TABLE, ACL_ROLES_DATA_TABLE, ACL_USERS_TABLE); + +			foreach ($tables as $table) +			{ +				$sql = 'SELECT * +					FROM ' . $table . ' +					WHERE auth_option_id = ' . $old_id; +				$result = $this->db->sql_query($sql); + +				$sql_ary = array(); +				while ($row = $this->db->sql_fetchrow($result)) +				{ +					$row['auth_option_id'] = $new_id; +					$sql_ary[] = $row; +				} +				$this->db->sql_freeresult($result); + +				if (!empty($sql_ary)) +				{ +					$this->db->sql_multi_insert($table, $sql_ary); +				} +			} + +			$auth_admin->acl_clear_prefetch(); +		} +	} + +	/** +	* Permission Remove +	* +	* Remove a permission (auth) option +	* +	* @param string $auth_option The name of the permission (auth) option +	* @param bool $global True for checking a global permission setting, +	* 	False for a local permission setting +	* @return null +	*/ +	public function remove($auth_option, $global = true) +	{ +		if (!$this->exists($auth_option, $global)) +		{ +			return; +		} + +		if ($global) +		{ +			$type_sql = ' AND is_global = 1'; +		} +		else +		{ +			$type_sql = ' AND is_local = 1'; +		} +		$sql = 'SELECT auth_option_id, is_global, is_local +			FROM ' . ACL_OPTIONS_TABLE . " +			WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'" . +				$type_sql; +		$result = $this->db->sql_query($sql); +		$row = $this->db->sql_fetchrow($result); +		$this->db->sql_freeresult($result); + +		$id = (int) $row['auth_option_id']; + +		// If it is a local and global permission, do not remove the row! :P +		if ($row['is_global'] && $row['is_local']) +		{ +			$sql = 'UPDATE ' . ACL_OPTIONS_TABLE . ' +				SET ' . (($global) ? 'is_global = 0' : 'is_local = 0') . ' +				WHERE auth_option_id = ' . $id; +			$this->db->sql_query($sql); +		} +		else +		{ +			// Delete time +			$tables = array(ACL_GROUPS_TABLE, ACL_ROLES_DATA_TABLE, ACL_USERS_TABLE, ACL_OPTIONS_TABLE); +			foreach ($tables as $table) +			{ +				$this->db->sql_query('DELETE FROM ' . $table . ' +					WHERE auth_option_id = ' . $id); +			} +		} + +		// Purge the auth cache +		$this->cache->destroy('_acl_options'); +		$this->auth->acl_clear_prefetch(); +	} + +	/** +	* Add a new permission role +	* +	* @param string $role_name The new role name +	* @param sting $role_type The type (u_, m_, a_) +	* @return null +	*/ +	public function role_add($role_name, $role_type, $role_description = '') +	{ +		$sql = 'SELECT role_id +			FROM ' . ACL_ROLES_TABLE . " +			WHERE role_name = '" . $this->db->sql_escape($role_name) . "'"; +		$this->db->sql_query($sql); +		$role_id = (int) $this->db->sql_fetchfield('role_id'); + +		if ($role_id) +		{ +			return; +		} + +		$sql = 'SELECT MAX(role_order) AS max_role_order +			FROM ' . ACL_ROLES_TABLE . " +			WHERE role_type = '" . $this->db->sql_escape($role_type) . "'"; +		$this->db->sql_query($sql); +		$role_order = (int) $this->db->sql_fetchfield('max_role_order'); +		$role_order = (!$role_order) ? 1 : $role_order + 1; + +		$sql_ary = array( +			'role_name'			=> $role_name, +			'role_description'	=> $role_description, +			'role_type'			=> $role_type, +			'role_order'		=> $role_order, +		); + +		$sql = 'INSERT INTO ' . ACL_ROLES_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); +		$this->db->sql_query($sql); +	} + +	/** +	* Update the name on a permission role +	* +	* @param string $old_role_name The old role name +	* @param string $new_role_name The new role name +	* @return null +	*/ +	public function role_update($old_role_name, $new_role_name) +	{ +		$sql = 'SELECT role_id +			FROM ' . ACL_ROLES_TABLE . " +			WHERE role_name = '" . $this->db->sql_escape($old_role_name) . "'"; +		$this->db->sql_query($sql); +		$role_id = (int) $this->db->sql_fetchfield('role_id'); + +		if (!$role_id) +		{ +			throw new phpbb_db_migration_exception('ROLE_NOT_EXIST', $old_role_name); +		} + +		$sql = 'UPDATE ' . ACL_ROLES_TABLE . " +			SET role_name = '" . $this->db->sql_escape($new_role_name) . "' +			WHERE role_name = '" . $this->db->sql_escape($old_role_name) . "'"; +		$this->db->sql_query($sql); +	} + +	/** +	* Remove a permission role +	* +	* @param string $role_name The role name to remove +	* @return null +	*/ +	public function role_remove($role_name) +	{ +		$sql = 'SELECT role_id +			FROM ' . ACL_ROLES_TABLE . " +			WHERE role_name = '" . $this->db->sql_escape($role_name) . "'"; +		$this->db->sql_query($sql); +		$role_id = (int) $this->db->sql_fetchfield('role_id'); + +		if (!$role_id) +		{ +			return; +		} + +		$sql = 'DELETE FROM ' . ACL_ROLES_DATA_TABLE . ' +			WHERE role_id = ' . $role_id; +		$this->db->sql_query($sql); + +		$sql = 'DELETE FROM ' . ACL_ROLES_TABLE . ' +			WHERE role_id = ' . $role_id; +		$this->db->sql_query($sql); + +		$this->auth->acl_clear_prefetch(); +	} + +	/** +	* Permission Set +	* +	* Allows you to set permissions for a certain group/role +	* +	* @param string $name The name of the role/group +	* @param string|array $auth_option The auth_option or array of +	* 	auth_options you would like to set +	* @param string $type The type (role|group) +	* @param bool $has_permission True if you want to give them permission, +	* 	false if you want to deny them permission +	* @return null +	*/ +	public function permission_set($name, $auth_option, $type = 'role', $has_permission = true) +	{ +		if (!is_array($auth_option)) +		{ +			$auth_option = array($auth_option); +		} + +		$new_auth = array(); +		$sql = 'SELECT auth_option_id +			FROM ' . ACL_OPTIONS_TABLE . ' +			WHERE ' . $this->db->sql_in_set('auth_option', $auth_option); +		$result = $this->db->sql_query($sql); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$new_auth[] = (int) $row['auth_option_id']; +		} +		$this->db->sql_freeresult($result); + +		if (empty($new_auth)) +		{ +			return; +		} + +		$current_auth = array(); + +		$type = (string) $type; // Prevent PHP bug. + +		switch ($type) +		{ +			case 'role': +				$sql = 'SELECT role_id +					FROM ' . ACL_ROLES_TABLE . " +					WHERE role_name = '" . $this->db->sql_escape($name) . "'"; +				$this->db->sql_query($sql); +				$role_id = (int) $this->db->sql_fetchfield('role_id'); + +				if (!$role_id) +				{ +					throw new phpbb_db_migration_exception('ROLE_NOT_EXIST', $name); +				} + +				$sql = 'SELECT auth_option_id, auth_setting +					FROM ' . ACL_ROLES_DATA_TABLE . ' +					WHERE role_id = ' . $role_id; +				$result = $this->db->sql_query($sql); +				while ($row = $this->db->sql_fetchrow($result)) +				{ +					$current_auth[$row['auth_option_id']] = $row['auth_setting']; +				} +				$this->db->sql_freeresult($result); +			break; + +			case 'group': +				$sql = 'SELECT group_id +					FROM ' . GROUPS_TABLE . " +					WHERE group_name = '" . $this->db->sql_escape($name) . "'"; +				$this->db->sql_query($sql); +				$group_id = (int) $this->db->sql_fetchfield('group_id'); + +				if (!$group_id) +				{ +					throw new phpbb_db_migration_exception('GROUP_NOT_EXIST', $name); +				} + +				// If the group has a role set for them we will add the requested permissions to that role. +				$sql = 'SELECT auth_role_id +					FROM ' . ACL_GROUPS_TABLE . ' +					WHERE group_id = ' . $group_id . ' +						AND auth_role_id <> 0 +						AND forum_id = 0'; +				$this->db->sql_query($sql); +				$role_id = (int) $this->db->sql_fetchfield('auth_role_id'); +				if ($role_id) +				{ +					$sql = 'SELECT role_name +						FROM ' . ACL_ROLES_TABLE . ' +						WHERE role_id = ' . $role_id; +					$this->db->sql_query($sql); +					$role_name = $this->db->sql_fetchfield('role_name'); + +					return $this->permission_set($role_name, $auth_option, 'role', $has_permission); +				} + +				$sql = 'SELECT auth_option_id, auth_setting +					FROM ' . ACL_GROUPS_TABLE . ' +					WHERE group_id = ' . $group_id; +				$result = $this->db->sql_query($sql); +				while ($row = $this->db->sql_fetchrow($result)) +				{ +					$current_auth[$row['auth_option_id']] = $row['auth_setting']; +				} +				$this->db->sql_freeresult($result); +			break; +		} + +		$sql_ary = array(); +		switch ($type) +		{ +			case 'role': +				foreach ($new_auth as $auth_option_id) +				{ +					if (!isset($current_auth[$auth_option_id])) +					{ +						$sql_ary[] = array( +							'role_id'			=> $role_id, +							'auth_option_id'	=> $auth_option_id, +							'auth_setting'		=> $has_permission, +						); +					} +				} + +				$this->db->sql_multi_insert(ACL_ROLES_DATA_TABLE, $sql_ary); +			break; + +			case 'group': +				foreach ($new_auth as $auth_option_id) +				{ +					if (!isset($current_auth[$auth_option_id])) +					{ +						$sql_ary[] = array( +							'group_id'			=> $group_id, +							'auth_option_id'	=> $auth_option_id, +							'auth_setting'		=> $has_permission, +						); +					} +				} + +				$this->db->sql_multi_insert(ACL_GROUPS_TABLE, $sql_ary); +			break; +		} + +		$this->auth->acl_clear_prefetch(); +	} + +	/** +	* Permission Unset +	* +	* Allows you to unset (remove) permissions for a certain group/role +	* +	* @param string $name The name of the role/group +	* @param string|array $auth_option The auth_option or array of +	* 	auth_options you would like to set +	* @param string $type The type (role|group) +	* @return null +	*/ +	public function permission_unset($name, $auth_option, $type = 'role') +	{ +		if (!is_array($auth_option)) +		{ +			$auth_option = array($auth_option); +		} + +		$to_remove = array(); +		$sql = 'SELECT auth_option_id +			FROM ' . ACL_OPTIONS_TABLE . ' +			WHERE ' . $this->db->sql_in_set('auth_option', $auth_option); +		$result = $this->db->sql_query($sql); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$to_remove[] = (int) $row['auth_option_id']; +		} +		$this->db->sql_freeresult($result); + +		if (empty($to_remove)) +		{ +			return; +		} + +		$type = (string) $type; // Prevent PHP bug. + +		switch ($type) +		{ +			case 'role': +				$sql = 'SELECT role_id +					FROM ' . ACL_ROLES_TABLE . " +					WHERE role_name = '" . $this->db->sql_escape($name) . "'"; +				$this->db->sql_query($sql); +				$role_id = (int) $this->db->sql_fetchfield('role_id'); + +				if (!$role_id) +				{ +					throw new phpbb_db_migration_exception('ROLE_NOT_EXIST', $name); +				} + +				$sql = 'DELETE FROM ' . ACL_ROLES_DATA_TABLE . ' +					WHERE ' . $this->db->sql_in_set('auth_option_id', $to_remove); +				$this->db->sql_query($sql); +			break; + +			case 'group': +				$sql = 'SELECT group_id +					FROM ' . GROUPS_TABLE . " +					WHERE group_name = '" . $this->db->sql_escape($name) . "'"; +				$this->db->sql_query($sql); +				$group_id = (int) $this->db->sql_fetchfield('group_id'); + +				if (!$group_id) +				{ +					throw new phpbb_db_migration_exception('GROUP_NOT_EXIST', $name); +				} + +				// If the group has a role set for them we will remove the requested permissions from that role. +				$sql = 'SELECT auth_role_id +					FROM ' . ACL_GROUPS_TABLE . ' +					WHERE group_id = ' . $group_id . ' +						AND auth_role_id <> 0'; +				$this->db->sql_query($sql); +				$role_id = (int) $this->db->sql_fetchfield('auth_role_id'); +				if ($role_id) +				{ +					$sql = 'SELECT role_name +						FROM ' . ACL_ROLES_TABLE . ' +						WHERE role_id = ' . $role_id; +					$this->db->sql_query($sql); +					$role_name = $this->db->sql_fetchfield('role_name'); + +					return $this->permission_unset($role_name, $auth_option, 'role'); +				} + +				$sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . ' +					WHERE ' . $this->db->sql_in_set('auth_option_id', $to_remove); +				$this->db->sql_query($sql); +			break; +		} + +		$this->auth->acl_clear_prefetch(); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function reverse() +	{ +		$arguments = func_get_args(); +		$original_call = array_shift($arguments); + +		$call = false; +		switch ($original_call) +		{ +			case 'add': +				$call = 'remove'; +			break; + +			case 'remove': +				$call = 'add'; +			break; + +			case 'permission_set': +				$call = 'permission_unset'; +			break; + +			case 'permission_unset': +				$call = 'permission_set'; +			break; + +			case 'role_add': +				$call = 'role_remove'; +			break; + +			case 'role_remove': +				$call = 'role_add'; +			break; + +			case 'role_update': +				// Set to the original value if the current value is what we compared to originally +				$arguments = array( +					$arguments[1], +					$arguments[0], +				); +			break; +		} + +		if ($call) +		{ +			return call_user_func_array(array(&$this, $call), $arguments); +		} +	} +} diff --git a/phpBB/phpbb/db/migrator.php b/phpBB/phpbb/db/migrator.php new file mode 100644 index 0000000000..ca3ffc8043 --- /dev/null +++ b/phpBB/phpbb/db/migrator.php @@ -0,0 +1,746 @@ +<?php +/** +* +* @package db +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* The migrator is responsible for applying new migrations in the correct order. +* +* @package db +*/ +class phpbb_db_migrator +{ +	/** @var phpbb_config */ +	protected $config; + +	/** @var phpbb_db_driver */ +	protected $db; + +	/** @var phpbb_db_tools */ +	protected $db_tools; + +	/** @var string */ +	protected $table_prefix; + +	/** @var string */ +	protected $phpbb_root_path; + +	/** @var string */ +	protected $php_ext; + +	/** @var string */ +	protected $migrations_table; + +	/** +	* State of all migrations +	* +	* (SELECT * FROM migrations table) +	* +	* @var array +	*/ +	protected $migration_state = array(); + +	/** +	* Array of all migrations available to be run +	* +	* @var array +	*/ +	protected $migrations = array(); + +	/** +	* 'name,' 'class,' and 'state' of the last migration run +	* +	* 'effectively_installed' set and set to true if the migration was effectively_installed +	* +	* @var array +	*/ +	public $last_run_migration = false; + +	/** +	* Constructor of the database migrator +	*/ +	public function __construct(phpbb_config $config, phpbb_db_driver $db, phpbb_db_tools $db_tools, $migrations_table, $phpbb_root_path, $php_ext, $table_prefix, $tools) +	{ +		$this->config = $config; +		$this->db = $db; +		$this->db_tools = $db_tools; + +		$this->migrations_table = $migrations_table; + +		$this->phpbb_root_path = $phpbb_root_path; +		$this->php_ext = $php_ext; + +		$this->table_prefix = $table_prefix; + +		foreach ($tools as $tool) +		{ +			$this->tools[$tool->get_name()] = $tool; +		} + +		$this->load_migration_state(); +	} + +	/** +	* Loads all migrations and their application state from the database. +	* +	* @return null +	*/ +	public function load_migration_state() +	{ +		$this->migration_state = array(); + +		// prevent errors in case the table does not exist yet +		$this->db->sql_return_on_error(true); + +		$sql = "SELECT * +			FROM " . $this->migrations_table; +		$result = $this->db->sql_query($sql); + +		if (!$this->db->sql_error_triggered) +		{ +			while ($migration = $this->db->sql_fetchrow($result)) +			{ +				$this->migration_state[$migration['migration_name']] = $migration; + +				$this->migration_state[$migration['migration_name']]['migration_depends_on'] = unserialize($migration['migration_depends_on']); +			} +		} + +		$this->db->sql_freeresult($result); + +		$this->db->sql_return_on_error(false); +	} + +	/** +	* Sets the list of available migration class names to the given array. +	* +	* @param array $class_names An array of migration class names +	* @return null +	*/ +	public function set_migrations($class_names) +	{ +		$this->migrations = $class_names; +	} + +	/** +	* Runs a single update step from the next migration to be applied. +	* +	* The update step can either be a schema or a (partial) data update. To +	* check if update() needs to be called again use the finished() method. +	* +	* @return null +	*/ +	public function update() +	{ +		foreach ($this->migrations as $name) +		{ +			if (!isset($this->migration_state[$name]) || +				!$this->migration_state[$name]['migration_schema_done'] || +				!$this->migration_state[$name]['migration_data_done']) +			{ +				if (!$this->try_apply($name)) +				{ +					continue; +				} +				else +				{ +					return; +				} +			} +		} +	} + +	/** +	* Attempts to apply a step of the given migration or one of its dependencies +	* +	* @param	string	The class name of the migration +	* @return	bool	Whether any update step was successfully run +	*/ +	protected function try_apply($name) +	{ +		if (!class_exists($name)) +		{ +			return false; +		} + +		$migration = $this->get_migration($name); + +		$state = (isset($this->migration_state[$name])) ? +			$this->migration_state[$name] : +			array( +				'migration_depends_on'	=> $migration->depends_on(), +				'migration_schema_done' => false, +				'migration_data_done'	=> false, +				'migration_data_state'	=> '', +				'migration_start_time'	=> 0, +				'migration_end_time'	=> 0, +			); + +		foreach ($state['migration_depends_on'] as $depend) +		{ +			if (!isset($this->migration_state[$depend]) || +				!$this->migration_state[$depend]['migration_schema_done'] || +				!$this->migration_state[$depend]['migration_data_done']) +			{ +				return $this->try_apply($depend); +			} +		} + +		$this->last_run_migration = array( +			'name'	=> $name, +			'class'	=> $migration, +			'state'	=> $state, +		); + +		if (!isset($this->migration_state[$name])) +		{ +			if ($migration->effectively_installed()) +			{ +				$state = array( +					'migration_depends_on'	=> $migration->depends_on(), +					'migration_schema_done' => true, +					'migration_data_done'	=> true, +					'migration_data_state'	=> '', +					'migration_start_time'	=> 0, +					'migration_end_time'	=> 0, +				); + +				$this->last_run_migration['effectively_installed'] = true; +			} +			else +			{ +				$state['migration_start_time'] = time(); +			} +		} + +		if (!$state['migration_schema_done']) +		{ +			$this->apply_schema_changes($migration->update_schema()); +			$state['migration_schema_done'] = true; +		} +		else if (!$state['migration_data_done']) +		{ +			try +			{ +				$result = $this->process_data_step($migration->update_data(), $state['migration_data_state']); + +				$state['migration_data_state'] = ($result === true) ? '' : $result; +				$state['migration_data_done'] = ($result === true); +				$state['migration_end_time'] = ($result === true) ? time() : 0; +			} +			catch (phpbb_db_migration_exception $e) +			{ +				// Revert the schema changes +				$this->revert($name); + +				// Rethrow exception +				throw $e; +			} +		} + +		$this->set_migration_state($name, $state); + +		return true; +	} + +	/** +	* Runs a single revert step from the last migration installed +	* +	* YOU MUST ADD/SET ALL MIGRATIONS THAT COULD BE DEPENDENT ON THE MIGRATION TO REVERT TO BEFORE CALLING THIS METHOD! +	* The revert step can either be a schema or a (partial) data revert. To +	* check if revert() needs to be called again use the migration_state() method. +	* +	* @param string $migration String migration name to revert (including any that depend on this migration) +	* @return null +	*/ +	public function revert($migration) +	{ +		if (!isset($this->migration_state[$migration])) +		{ +			// Not installed +			return; +		} + +		foreach ($this->migration_state as $name => $state) +		{ +			if (!empty($state['migration_depends_on']) && in_array($migration, $state['migration_depends_on'])) +			{ +				$this->revert($name); +			} +		} + +		$this->try_revert($migration); +	} + +	/** +	* Attempts to revert a step of the given migration or one of its dependencies +	* +	* @param	string	The class name of the migration +	* @return	bool	Whether any update step was successfully run +	*/ +	protected function try_revert($name) +	{ +		if (!class_exists($name)) +		{ +			return false; +		} + +		$migration = $this->get_migration($name); + +		$state = $this->migration_state[$name]; + +		$this->last_run_migration = array( +			'name'	=> $name, +			'class'	=> $migration, +		); + +		if ($state['migration_data_done']) +		{ +			if ($state['migration_data_state'] !== 'revert_data') +			{ +				$result = $this->process_data_step($migration->update_data(), $state['migration_data_state'], true); + +				$state['migration_data_state'] = ($result === true) ? 'revert_data' : $result; +			} +			else +			{ +				$result = $this->process_data_step($migration->revert_data(), '', false); + +				$state['migration_data_state'] = ($result === true) ? '' : $result; +				$state['migration_data_done'] = ($result === true) ? false : true; +			} + +			$this->set_migration_state($name, $state); +		} +		else +		{ +			$this->apply_schema_changes($migration->revert_schema()); + +			$sql = 'DELETE FROM ' . $this->migrations_table . " +				WHERE migration_name = '" . $this->db->sql_escape($name) . "'"; +			$this->db->sql_query($sql); + +			unset($this->migration_state[$name]); +		} + +		return true; +	} + +	/** +	* Apply schema changes from a migration +	* +	* Just calls db_tools->perform_schema_changes +	* +	* @param array $schema_changes from migration +	*/ +	protected function apply_schema_changes($schema_changes) +	{ +		$this->db_tools->perform_schema_changes($schema_changes); +	} + +	/** +	* Process the data step of the migration +	* +	* @param array $steps The steps to run +	* @param bool|string $state Current state of the migration +	* @param bool $revert true to revert a data step +	* @return bool|string migration state. True if completed, serialized array if not finished +	*/ +	protected function process_data_step($steps, $state, $revert = false) +	{ +		$state = ($state) ? unserialize($state) : false; + +		// reverse order of steps if reverting +		if ($revert === true) +		{ +			$steps = array_reverse($steps); +		} + +		foreach ($steps as $step_identifier => $step) +		{ +			$last_result = false; +			if ($state) +			{ +				// Continue until we reach the step that matches the last step called +				if ($state['step'] != $step_identifier) +				{ +					continue; +				} + +				// We send the result from last time to the callable function +				$last_result = $state['result']; + +				// Set state to false since we reached the point we were at +				$state = false; +			} + +			try +			{ +				// Result will be null or true if everything completed correctly +				$result = $this->run_step($step, $last_result, $revert); +				if ($result !== null && $result !== true) +				{ +					return serialize(array( +						'result'	=> $result, +						'step'		=> $step_identifier, +					)); +				} +			} +			catch (phpbb_db_migration_exception $e) +			{ +				// We should try rolling back here +				foreach ($steps as $reverse_step_identifier => $reverse_step) +				{ +					// If we've reached the current step we can break because we reversed everything that was run +					if ($reverse_step_identifier == $step_identifier) +					{ +						break; +					} + +					// Reverse the step that was run +					$result = $this->run_step($reverse_step, false, !$revert); +				} + +				// rethrow the exception +				throw $e; +			} +		} + +		return true; +	} + +	/** +	* Run a single step +	* +	* An exception should be thrown if an error occurs +	* +	* @param mixed $step Data step from migration +	* @param mixed $last_result Result to pass to the callable (only for 'custom' method) +	* @param bool $reverse False to install, True to attempt uninstallation by reversing the call +	* @return null +	*/ +	protected function run_step($step, $last_result = false, $reverse = false) +	{ +		$callable_and_parameters = $this->get_callable_from_step($step, $last_result, $reverse); + +		if ($callable_and_parameters === false) +		{ +			return; +		} + +		$callable = $callable_and_parameters[0]; +		$parameters = $callable_and_parameters[1]; + +		return call_user_func_array($callable, $parameters); +	} + +	/** +	* Get a callable statement from a data step +	* +	* @param array $step Data step from migration +	* @param mixed $last_result Result to pass to the callable (only for 'custom' method) +	* @param bool $reverse False to install, True to attempt uninstallation by reversing the call +	* @return array Array with parameters for call_user_func_array(), 0 is the callable, 1 is parameters +	*/ +	protected function get_callable_from_step(array $step, $last_result = false, $reverse = false) +	{ +		$type = $step[0]; +		$parameters = $step[1]; + +		$parts = explode('.', $type); + +		$class = $parts[0]; +		$method = false; + +		if (isset($parts[1])) +		{ +			$method = $parts[1]; +		} + +		switch ($class) +		{ +			case 'if': +				if (!isset($parameters[0])) +				{ +					throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_MISSING_CONDITION', $step); +				} + +				if (!isset($parameters[1])) +				{ +					throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_MISSING_STEP', $step); +				} + +				$condition = $parameters[0]; + +				if (!$condition) +				{ +					return false; +				} + +				$step = $parameters[1]; + +				return $this->get_callable_from_step($step); +			break; +			case 'custom': +				if (!is_callable($parameters[0])) +				{ +					throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_CUSTOM_NOT_CALLABLE', $step); +				} + +				return array( +					$parameters[0], +					array($last_result), +				); +			break; + +			default: +				if (!$method) +				{ +					throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_UNKNOWN_TYPE', $step); +				} + +				if (!isset($this->tools[$class])) +				{ +					throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_UNDEFINED_TOOL', $step); +				} + +				if (!method_exists(get_class($this->tools[$class]), $method)) +				{ +					throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_UNDEFINED_METHOD', $step); +				} + +				// Attempt to reverse operations +				if ($reverse) +				{ +					array_unshift($parameters, $method); + +					return array( +						array($this->tools[$class], 'reverse'), +						$parameters, +					); +				} + +				return array( +					array($this->tools[$class], $method), +					$parameters, +				); +			break; +		} +	} + +	/** +	* Insert/Update migration row into the database +	* +	* @param string $name Name of the migration +	* @param array $state +	* @return null +	*/ +	protected function set_migration_state($name, $state) +	{ +		$migration_row = $state; +		$migration_row['migration_depends_on'] = serialize($state['migration_depends_on']); + +		if (isset($this->migration_state[$name])) +		{ +			$sql = 'UPDATE ' . $this->migrations_table . ' +				SET ' . $this->db->sql_build_array('UPDATE', $migration_row) . " +				WHERE migration_name = '" . $this->db->sql_escape($name) . "'"; +			$this->db->sql_query($sql); +		} +		else +		{ +			$migration_row['migration_name'] = $name; +			$sql = 'INSERT INTO ' . $this->migrations_table . ' +				' . $this->db->sql_build_array('INSERT', $migration_row); +			$this->db->sql_query($sql); +		} + +		$this->migration_state[$name] = $state; + +		$this->last_run_migration['state'] = $state; +	} + +	/** +	* Checks if a migration's dependencies can even theoretically be satisfied. +	* +	* @param string	$name The class name of the migration +	* @return bool|string False if fulfillable, string of missing migration name if unfulfillable +	*/ +	public function unfulfillable($name) +	{ +		if (isset($this->migration_state[$name])) +		{ +			return false; +		} + +		if (!class_exists($name)) +		{ +			return $name; +		} + +		$migration = $this->get_migration($name); +		$depends = $migration->depends_on(); + +		foreach ($depends as $depend) +		{ +			$unfulfillable = $this->unfulfillable($depend); +			if ($unfulfillable !== false) +			{ +				return $unfulfillable; +			} +		} + +		return false; +	} + +	/** +	* Checks whether all available, fulfillable migrations have been applied. +	* +	* @return bool Whether the migrations have been applied +	*/ +	public function finished() +	{ +		foreach ($this->migrations as $name) +		{ +			if (!isset($this->migration_state[$name])) +			{ +				// skip unfulfillable migrations, but fulfillables mean we +				// are not finished yet +				if ($this->unfulfillable($name) !== false) +				{ +					continue; +				} +				return false; +			} + +			$migration = $this->migration_state[$name]; + +			if (!$migration['migration_schema_done'] || !$migration['migration_data_done']) +			{ +				return false; +			} +		} + +		return true; +	} + +	/** +	* Gets a migration state (whether it is installed and to what extent) +	* +	* @param string $migration String migration name to check if it is installed +	* @return bool|array False if the migration has not at all been installed, array +	*/ +	public function migration_state($migration) +	{ +		if (!isset($this->migration_state[$migration])) +		{ +			return false; +		} + +		return $this->migration_state[$migration]; +	} + +	/** +	* Helper to get a migration +	* +	* @param string $name Name of the migration +	* @return phpbb_db_migration +	*/ +	protected function get_migration($name) +	{ +		return new $name($this->config, $this->db, $this->db_tools, $this->phpbb_root_path, $this->php_ext, $this->table_prefix); +	} + +	/** +	* This function adds all migrations sent to it to the migrations table +	* +	* THIS SHOULD NOT GENERALLY BE USED! THIS IS FOR THE PHPBB INSTALLER. +	* THIS WILL THROW ERRORS IF MIGRATIONS ALREADY EXIST IN THE TABLE, DO NOT CALL MORE THAN ONCE! +	* +	* @param array $migrations Array of migrations (names) to add to the migrations table +	* @return null +	*/ +	public function populate_migrations($migrations) +	{ +		foreach ($migrations as $name) +		{ +			if ($this->migration_state($name) === false) +			{ +				$state = array( +					'migration_depends_on'	=> $name::depends_on(), +					'migration_schema_done' => true, +					'migration_data_done'	=> true, +					'migration_data_state'	=> '', +					'migration_start_time'	=> time(), +					'migration_end_time'	=> time(), +				); +				$this->set_migration_state($name, $state); +			} +		} +	} + +	/** +	* Load migration data files from a directory +	* +	* @param phpbb_extension_finder $finder +	* @param string $path Path to migration data files +	* @param bool $check_fulfillable If TRUE (default), we will check +	* 	if all of the migrations are fulfillable after loading them. +	* 	If FALSE, we will not check. You SHOULD check at least once +	* 	to prevent errors (if including multiple directories, check +	* 	with the last call to prevent throwing errors unnecessarily). +	* @return array Array of migration names +	*/ +	public function load_migrations(phpbb_extension_finder $finder, $path, $check_fulfillable = true) +	{ +		if (!is_dir($path)) +		{ +			throw new phpbb_db_migration_exception('DIRECTORY INVALID', $path); +		} + +		$migrations = array(); + +		$files = $finder +			->extension_directory("/") +			->find_from_paths(array('/' => $path)); +		foreach ($files as $file) +		{ +			$migrations[$file['path'] . $file['filename']] = ''; +		} +		$migrations = $finder->get_classes_from_files($migrations); + +		foreach ($migrations as $migration) +		{ +			if (!in_array($migration, $this->migrations)) +			{ +				$this->migrations[] = $migration; +			} +		} + +		if ($check_fulfillable) +		{ +			foreach ($this->migrations as $name) +			{ +				$unfulfillable = $this->unfulfillable($name); +				if ($unfulfillable !== false) +				{ +					throw new phpbb_db_migration_exception('MIGRATION_NOT_FULFILLABLE', $name, $unfulfillable); +				} +			} +		} + +		return $this->migrations; +	} +} diff --git a/phpBB/phpbb/db/sql_insert_buffer.php b/phpBB/phpbb/db/sql_insert_buffer.php new file mode 100644 index 0000000000..c18f908429 --- /dev/null +++ b/phpBB/phpbb/db/sql_insert_buffer.php @@ -0,0 +1,150 @@ +<?php +/** +* +* @package dbal +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* Collects rows for insert into a database until the buffer size is reached. +* Then flushes the buffer to the database and starts over again. +* +* Benefits over collecting a (possibly huge) insert array and then using +* $db->sql_multi_insert() include: +* +*  - Going over max packet size of the database connection is usually prevented +*    because the data is submitted in batches. +* +*  - Reaching database connection timeout is usually prevented because +*    submission of batches talks to the database every now and then. +* +*  - Usage of less PHP memory because data no longer needed is discarded on +*    buffer flush. +* +* Attention: +* Please note that users of this class have to call flush() to flush the +* remaining rows to the database after their batch insert operation is +* finished. +* +* Usage: +* <code> +*	$buffer = new phpbb_db_sql_insert_buffer($db, 'test_table', 1234); +* +*	while (do_stuff()) +*	{ +*		$buffer->insert(array( +*			'column1' => 'value1', +*			'column2' => 'value2', +*		)); +*	} +* +*	$buffer->flush(); +* </code> +* +* @package dbal +*/ +class phpbb_db_sql_insert_buffer +{ +	/** @var phpbb_db_driver */ +	protected $db; + +	/** @var string */ +	protected $table_name; + +	/** @var int */ +	protected $max_buffered_rows; + +	/** @var array */ +	protected $buffer = array(); + +	/** +	* @param phpbb_db_driver $db +	* @param string          $table_name +	* @param int             $max_buffered_rows +	*/ +	public function __construct(phpbb_db_driver $db, $table_name, $max_buffered_rows = 500) +	{ +		$this->db = $db; +		$this->table_name = $table_name; +		$this->max_buffered_rows = $max_buffered_rows; +	} + +	/** +	* Inserts a single row into the buffer if multi insert is supported by the +	* database (otherwise an insert query is sent immediately). Then flushes +	* the buffer if the number of rows in the buffer is now greater than or +	* equal to $max_buffered_rows. +	* +	* @param array $row +	* +	* @return bool		True when some data was flushed to the database. +	*					False otherwise. +	*/ +	public function insert(array $row) +	{ +		$this->buffer[] = $row; + +		// Flush buffer if it is full or when DB does not support multi inserts. +		// In the later case, the buffer will always only contain one row. +		if (!$this->db->multi_insert || sizeof($this->buffer) >= $this->max_buffered_rows) +		{ +			return $this->flush(); +		} + +		return false; +	} + +	/** +	* Inserts a row set, i.e. an array of rows, by calling insert(). +	* +	* Please note that it is in most cases better to use insert() instead of +	* first building a huge rowset. Or at least sizeof($rows) should be kept +	* small. +	* +	* @param array $rows  +	* +	* @return bool		True when some data was flushed to the database. +	*					False otherwise. +	*/ +	public function insert_all(array $rows) +	{ +		// Using bitwise |= because PHP does not have logical ||= +		$result = 0; + +		foreach ($rows as $row) +		{ +			$result |= (int) $this->insert($row); +		} + +		return (bool) $result; +	} + +	/** +	* Flushes the buffer content to the DB and clears the buffer. +	* +	* @return bool		True when some data was flushed to the database. +	*					False otherwise. +	*/ +	public function flush() +	{ +		if (!empty($this->buffer)) +		{ +			$this->db->sql_multi_insert($this->table_name, $this->buffer); +			$this->buffer = array(); + +			return true; +		} + +		return false; +	} +}  | 
