diff options
Diffstat (limited to 'phpBB/phpbb/db')
61 files changed, 6129 insertions, 1186 deletions
| diff --git a/phpBB/phpbb/db/driver/driver.php b/phpBB/phpbb/db/driver/driver.php index 01dd66cd6e..214c5590e7 100644 --- a/phpBB/phpbb/db/driver/driver.php +++ b/phpBB/phpbb/db/driver/driver.php @@ -66,6 +66,15 @@ abstract class driver implements driver_interface  	*/  	var $sql_server_version = false; +	const LOGICAL_OP = 0; +	const STATEMENTS = 1; +	const LEFT_STMT = 0; +	const COMPARE_OP = 1; +	const RIGHT_STMT = 2; +	const SUBQUERY_OP = 3; +	const SUBQUERY_SELECT_TYPE = 4; +	const SUBQUERY_BUILD = 5; +  	/**  	* Constructor  	*/ @@ -271,7 +280,7 @@ abstract class driver implements driver_interface  			$query_id = $this->query_result;  		} -		if ($query_id !== false) +		if ($query_id)  		{  			$result = array();  			while ($row = $this->sql_fetchrow($query_id)) @@ -302,7 +311,7 @@ abstract class driver implements driver_interface  			return $cache->sql_rowseek($rownum, $query_id);  		} -		if ($query_id === false) +		if (!$query_id)  		{  			return false;  		} @@ -310,7 +319,7 @@ abstract class driver implements driver_interface  		$this->sql_freeresult($query_id);  		$query_id = $this->sql_query($this->last_query_text); -		if ($query_id === false) +		if (!$query_id)  		{  			return false;  		} @@ -339,7 +348,7 @@ abstract class driver implements driver_interface  			$query_id = $this->query_result;  		} -		if ($query_id !== false) +		if ($query_id)  		{  			if ($rownum !== false)  			{ @@ -363,8 +372,8 @@ abstract class driver implements driver_interface  	*/  	function sql_like_expression($expression)  	{ -		$expression = utf8_str_replace(array('_', '%'), array("\_", "\%"), $expression); -		$expression = utf8_str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression); +		$expression = str_replace(array('_', '%'), array("\_", "\%"), $expression); +		$expression = str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression);  		return $this->_sql_like_expression('LIKE \'' . $this->sql_escape($expression) . '\'');  	} @@ -374,8 +383,8 @@ abstract class driver implements driver_interface  	*/  	function sql_not_like_expression($expression)  	{ -		$expression = utf8_str_replace(array('_', '%'), array("\_", "\%"), $expression); -		$expression = utf8_str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression); +		$expression = str_replace(array('_', '%'), array("\_", "\%"), $expression); +		$expression = str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression);  		return $this->_sql_not_like_expression('NOT LIKE \'' . $this->sql_escape($expression) . '\'');  	} @@ -774,7 +783,18 @@ abstract class driver implements driver_interface  				if (!empty($array['WHERE']))  				{ -					$sql .= ' WHERE ' . $this->_sql_custom_build('WHERE', $array['WHERE']); +					$sql .= ' WHERE '; + +					if (is_array($array['WHERE'])) +					{ +						$sql_where = $this->_process_boolean_tree_first($array['WHERE']); +					} +					else +					{ +						$sql_where = $array['WHERE']; +					} + +					$sql .= $this->_sql_custom_build('WHERE', $sql_where);  				}  				if (!empty($array['GROUP_BY'])) @@ -793,6 +813,130 @@ abstract class driver implements driver_interface  		return $sql;  	} + +	protected function _process_boolean_tree_first($operations_ary) +	{ +		// In cases where an array exists but there is no head condition, +		// it should be because there's only 1 WHERE clause. This seems the best way to deal with it. +		if ($operations_ary[self::LOGICAL_OP] !== 'AND' && +			$operations_ary[self::LOGICAL_OP] !== 'OR') +		{ +			$operations_ary = array('AND', array($operations_ary)); +		} +		return $this->_process_boolean_tree($operations_ary) . "\n"; +	} + +	protected function _process_boolean_tree($operations_ary) +	{ +		$operation = $operations_ary[self::LOGICAL_OP]; + +		foreach ($operations_ary[self::STATEMENTS] as &$condition) +		{ +			switch ($condition[self::LOGICAL_OP]) +			{ +				case 'AND': +				case 'OR': + +					$condition = ' ( ' . $this->_process_boolean_tree($condition) . ') '; + +				break; +				case 'NOT': + +					$condition = ' NOT (' . $this->_process_boolean_tree($condition) . ') '; + +				break; + +				default: + +					switch (sizeof($condition)) +					{ +						case 3: + +							// Typical 3 element clause with {left hand} {operator} {right hand} +							switch ($condition[self::COMPARE_OP]) +							{ +								case 'IN': +								case 'NOT_IN': + +									// As this is used with an IN, assume it is a set of elements for sql_in_set() +									$condition = $this->sql_in_set($condition[self::LEFT_STMT], $condition[self::RIGHT_STMT], $condition[self::COMPARE_OP] === 'NOT_IN', true); + +								break; + +								case 'LIKE': + +									$condition = $condition[self::LEFT_STMT] . ' ' . $this->sql_like_expression($condition[self::RIGHT_STMT]) . ' '; + +								break; + +								case 'NOT_LIKE': + +									$condition = $condition[self::LEFT_STMT] . ' ' . $this->sql_not_like_expression($condition[self::RIGHT_STMT]) . ' '; + +								break; + +								case 'IS_NOT': + +									$condition[self::COMPARE_OP] = 'IS NOT'; + +								// no break +								case 'IS': + +									// If the value is NULL, the string of it is the empty string ('') which is not the intended result. +									// this should solve that +									if ($condition[self::RIGHT_STMT] === null) +									{ +										$condition[self::RIGHT_STMT] = 'NULL'; +									} + +									$condition = implode(' ', $condition); + +								break; + +								default: + +									$condition = implode(' ', $condition); + +								break; +							} + +						break; + +						case 5: + +							// Subquery with {left hand} {operator} {compare kind} {SELECT Kind } {Sub Query} + +							$condition = $condition[self::LEFT_STMT] . ' ' . $condition[self::COMPARE_OP] . ' ' . $condition[self::SUBQUERY_OP] . ' ( '; +							$condition .= $this->sql_build_query($condition[self::SUBQUERY_SELECT_TYPE], $condition[self::SUBQUERY_BUILD]); +							$condition .= ' )'; + +						break; + +						default: +							// This is an unpredicted clause setup. Just join all elements. +							$condition = implode(' ', $condition); + +						break; +					} + +				break; +			} + +		} + +		if ($operation === 'NOT') +		{ +			$operations_ary =  implode("", $operations_ary[self::STATEMENTS]); +		} +		else +		{ +			$operations_ary = implode(" \n	$operation ", $operations_ary[self::STATEMENTS]); +		} + +		return $operations_ary; +	} + +  	/**  	* {@inheritDoc}  	*/ @@ -868,7 +1012,7 @@ abstract class driver implements driver_interface  	*/  	function sql_report($mode, $query = '')  	{ -		global $cache, $starttime, $phpbb_root_path, $phpbb_path_helper, $user; +		global $cache, $starttime, $phpbb_root_path, $phpbb_path_helper;  		global $request;  		if (is_object($request) && !$request->variable('explain', false)) diff --git a/phpBB/phpbb/db/driver/mssql.php b/phpBB/phpbb/db/driver/mssql.php index f9ea884ce2..dfdbfe15e6 100644 --- a/phpBB/phpbb/db/driver/mssql.php +++ b/phpBB/phpbb/db/driver/mssql.php @@ -71,8 +71,8 @@ class mssql extends \phpbb\db\driver\driver  			$row = false;  			if ($result_id)  			{ -				$row = @mssql_fetch_assoc($result_id); -				@mssql_free_result($result_id); +				$row = mssql_fetch_assoc($result_id); +				mssql_free_result($result_id);  			}  			$this->sql_server_version = ($row) ? trim(implode(' ', $row)) : 0; @@ -161,12 +161,17 @@ class mssql extends \phpbb\db\driver\driver  					$this->sql_time += microtime(true) - $this->curtime;  				} +				if (!$this->query_result) +				{ +					return false; +				} +  				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) +				else if (strpos($query, 'SELECT') === 0 && $this->query_result !== true)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result;  				} @@ -241,12 +246,12 @@ class mssql extends \phpbb\db\driver\driver  			return $cache->sql_fetchrow($query_id);  		} -		if ($query_id === false) +		if (!$query_id || $query_id === true)  		{  			return false;  		} -		$row = @mssql_fetch_assoc($query_id); +		$row = mssql_fetch_assoc($query_id);  		// I hope i am able to remove this later... hopefully only a PHP or MSSQL bug  		if ($row) @@ -272,12 +277,17 @@ class mssql extends \phpbb\db\driver\driver  			$query_id = $this->query_result;  		} +		if ($query_id === true) +		{ +			return false; +		} +  		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; +		return ($query_id) ? @mssql_data_seek($query_id, $rownum) : false;  	}  	/** @@ -288,12 +298,12 @@ class mssql extends \phpbb\db\driver\driver  		$result_id = @mssql_query('SELECT SCOPE_IDENTITY()', $this->db_connect_id);  		if ($result_id)  		{ -			if ($row = @mssql_fetch_assoc($result_id)) +			if ($row = mssql_fetch_assoc($result_id))  			{ -				@mssql_free_result($result_id); +				mssql_free_result($result_id);  				return $row['computed'];  			} -			@mssql_free_result($result_id); +			mssql_free_result($result_id);  		}  		return false; @@ -311,6 +321,11 @@ class mssql extends \phpbb\db\driver\driver  			$query_id = $this->query_result;  		} +		if ($query_id === true) +		{ +			return false; +		} +  		if ($cache && !is_object($query_id) && $cache->sql_exists($query_id))  		{  			return $cache->sql_freeresult($query_id); @@ -319,7 +334,7 @@ class mssql extends \phpbb\db\driver\driver  		if (isset($this->open_queries[(int) $query_id]))  		{  			unset($this->open_queries[(int) $query_id]); -			return @mssql_free_result($query_id); +			return mssql_free_result($query_id);  		}  		return false; @@ -376,9 +391,9 @@ class mssql extends \phpbb\db\driver\driver  			$result_id = @mssql_query('SELECT @@ERROR as code', $this->db_connect_id);  			if ($result_id)  			{ -				$row = @mssql_fetch_assoc($result_id); +				$row = mssql_fetch_assoc($result_id);  				$error['code'] = $row['code']; -				@mssql_free_result($result_id); +				mssql_free_result($result_id);  			}  			// Get full error message if possible @@ -389,12 +404,12 @@ class mssql extends \phpbb\db\driver\driver  			if ($result_id)  			{ -				$row = @mssql_fetch_assoc($result_id); +				$row = mssql_fetch_assoc($result_id);  				if (!empty($row['message']))  				{  					$error['message'] .= '<br />' . $row['message'];  				} -				@mssql_free_result($result_id); +				mssql_free_result($result_id);  			}  		}  		else @@ -440,13 +455,13 @@ class mssql extends \phpbb\db\driver\driver  				if ($result = @mssql_query($query, $this->db_connect_id))  				{  					@mssql_next_result($result); -					while ($row = @mssql_fetch_row($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); +				mssql_free_result($result);  				if ($html_table)  				{ @@ -459,11 +474,14 @@ class mssql extends \phpbb\db\driver\driver  				$endtime = $endtime[0] + $endtime[1];  				$result = @mssql_query($query, $this->db_connect_id); -				while ($void = @mssql_fetch_assoc($result)) +				if ($result)  				{ -					// Take the time spent on parsing rows into account +					while ($void = mssql_fetch_assoc($result)) +					{ +						// Take the time spent on parsing rows into account +					} +					mssql_free_result($result);  				} -				@mssql_free_result($result);  				$splittime = explode(' ', microtime());  				$splittime = $splittime[0] + $splittime[1]; diff --git a/phpBB/phpbb/db/driver/mssql_odbc.php b/phpBB/phpbb/db/driver/mssql_odbc.php index 8e5d4c7a4c..9d9ad603e0 100644 --- a/phpBB/phpbb/db/driver/mssql_odbc.php +++ b/phpBB/phpbb/db/driver/mssql_odbc.php @@ -98,8 +98,8 @@ class mssql_odbc extends \phpbb\db\driver\mssql_base  			$row = false;  			if ($result_id)  			{ -				$row = @odbc_fetch_array($result_id); -				@odbc_free_result($result_id); +				$row = odbc_fetch_array($result_id); +				odbc_free_result($result_id);  			}  			$this->sql_server_version = ($row) ? trim(implode(' ', $row)) : 0; @@ -181,12 +181,17 @@ class mssql_odbc extends \phpbb\db\driver\mssql_base  					$this->sql_time += microtime(true) - $this->curtime;  				} +				if (!$this->query_result) +				{ +					return false; +				} +  				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) +				else if (strpos($query, 'SELECT') === 0)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result;  				} @@ -261,7 +266,7 @@ class mssql_odbc extends \phpbb\db\driver\mssql_base  			return $cache->sql_fetchrow($query_id);  		} -		return ($query_id !== false) ? @odbc_fetch_array($query_id) : false; +		return ($query_id) ? odbc_fetch_array($query_id) : false;  	}  	/** @@ -273,13 +278,13 @@ class mssql_odbc extends \phpbb\db\driver\mssql_base  		if ($result_id)  		{ -			if (@odbc_fetch_array($result_id)) +			if (odbc_fetch_array($result_id))  			{ -				$id = @odbc_result($result_id, 1); -				@odbc_free_result($result_id); +				$id = odbc_result($result_id, 1); +				odbc_free_result($result_id);  				return $id;  			} -			@odbc_free_result($result_id); +			odbc_free_result($result_id);  		}  		return false; @@ -305,7 +310,7 @@ class mssql_odbc extends \phpbb\db\driver\mssql_base  		if (isset($this->open_queries[(int) $query_id]))  		{  			unset($this->open_queries[(int) $query_id]); -			return @odbc_free_result($query_id); +			return odbc_free_result($query_id);  		}  		return false; @@ -360,11 +365,14 @@ class mssql_odbc extends \phpbb\db\driver\mssql_base  				$endtime = $endtime[0] + $endtime[1];  				$result = @odbc_exec($this->db_connect_id, $query); -				while ($void = @odbc_fetch_array($result)) +				if ($result)  				{ -					// Take the time spent on parsing rows into account +					while ($void = odbc_fetch_array($result)) +					{ +						// Take the time spent on parsing rows into account +					} +					odbc_free_result($result);  				} -				@odbc_free_result($result);  				$splittime = explode(' ', microtime());  				$splittime = $splittime[0] + $splittime[1]; diff --git a/phpBB/phpbb/db/driver/mssqlnative.php b/phpBB/phpbb/db/driver/mssqlnative.php index 46a9b3a477..50dce35baa 100644 --- a/phpBB/phpbb/db/driver/mssqlnative.php +++ b/phpBB/phpbb/db/driver/mssqlnative.php @@ -154,12 +154,17 @@ class mssqlnative extends \phpbb\db\driver\mssql_base  					$this->sql_time += microtime(true) - $this->curtime;  				} +				if (!$this->query_result) +				{ +					return false; +				} +  				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) +				else if (strpos($query, 'SELECT') === 0)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result;  				} @@ -242,12 +247,12 @@ class mssqlnative extends \phpbb\db\driver\mssql_base  			return $cache->sql_fetchrow($query_id);  		} -		if ($query_id === false) +		if (!$query_id)  		{  			return false;  		} -		$row = @sqlsrv_fetch_array($query_id, SQLSRV_FETCH_ASSOC); +		$row = sqlsrv_fetch_array($query_id, SQLSRV_FETCH_ASSOC);  		if ($row)  		{ @@ -272,11 +277,11 @@ class mssqlnative extends \phpbb\db\driver\mssql_base  	{  		$result_id = @sqlsrv_query($this->db_connect_id, 'SELECT @@IDENTITY'); -		if ($result_id !== false) +		if ($result_id)  		{ -			$row = @sqlsrv_fetch_array($result_id); +			$row = sqlsrv_fetch_array($result_id);  			$id = $row[0]; -			@sqlsrv_free_stmt($result_id); +			sqlsrv_free_stmt($result_id);  			return $id;  		}  		else @@ -305,7 +310,7 @@ class mssqlnative extends \phpbb\db\driver\mssql_base  		if (isset($this->open_queries[(int) $query_id]))  		{  			unset($this->open_queries[(int) $query_id]); -			return @sqlsrv_free_stmt($query_id); +			return sqlsrv_free_stmt($query_id);  		}  		return false; @@ -378,14 +383,14 @@ class mssqlnative extends \phpbb\db\driver\mssql_base  				@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)) +					sqlsrv_next_result($result); +					while ($row = sqlsrv_fetch_array($result))  					{  						$html_table = $this->sql_report('add_select_row', $query, $html_table, $row);  					} +					sqlsrv_free_stmt($result);  				}  				@sqlsrv_query($this->db_connect_id, 'SET SHOWPLAN_TEXT OFF;'); -				@sqlsrv_free_stmt($result);  				if ($html_table)  				{ @@ -398,11 +403,14 @@ class mssqlnative extends \phpbb\db\driver\mssql_base  				$endtime = $endtime[0] + $endtime[1];  				$result = @sqlsrv_query($this->db_connect_id, $query); -				while ($void = @sqlsrv_fetch_array($result)) +				if ($result)  				{ -					// Take the time spent on parsing rows into account +					while ($void = sqlsrv_fetch_array($result)) +					{ +						// Take the time spent on parsing rows into account +					} +					sqlsrv_free_stmt($result);  				} -				@sqlsrv_free_stmt($result);  				$splittime = explode(' ', microtime());  				$splittime = $splittime[0] + $splittime[1]; diff --git a/phpBB/phpbb/db/driver/mysql.php b/phpBB/phpbb/db/driver/mysql.php index e93c7239e8..a94e88b331 100644 --- a/phpBB/phpbb/db/driver/mysql.php +++ b/phpBB/phpbb/db/driver/mysql.php @@ -70,9 +70,16 @@ class mysql extends \phpbb\db\driver\mysql_base  					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'])); +						if ($result) +						{ +							$row = mysql_fetch_assoc($result); +							mysql_free_result($result); +							$modes = array_map('trim', explode(',', $row['sql_mode'])); +						} +						else +						{ +							$modes = array(); +						}  						// TRADITIONAL includes STRICT_ALL_TABLES and STRICT_TRANS_TABLES  						if (!in_array('TRADITIONAL', $modes)) @@ -114,14 +121,17 @@ class mysql extends \phpbb\db\driver\mysql_base  		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); +			if ($result) +			{ +				$row = mysql_fetch_assoc($result); +				mysql_free_result($result); -			$this->sql_server_version = $row['version']; +				$this->sql_server_version = $row['version']; -			if (!empty($cache) && $use_cache) -			{ -				$cache->put('mysql_version', $this->sql_server_version); +				if (!empty($cache) && $use_cache) +				{ +					$cache->put('mysql_version', $this->sql_server_version); +				}  			}  		} @@ -190,12 +200,17 @@ class mysql extends \phpbb\db\driver\mysql_base  					$this->sql_time += microtime(true) - $this->curtime;  				} +				if (!$this->query_result) +				{ +					return false; +				} +  				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) +				else if (strpos($query, 'SELECT') === 0)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result;  				} @@ -257,7 +272,7 @@ class mysql extends \phpbb\db\driver\mysql_base  			return $cache->sql_fetchrow($query_id);  		} -		return ($query_id !== false) ? @mysql_fetch_assoc($query_id) : false; +		return ($query_id) ? mysql_fetch_assoc($query_id) : false;  	}  	/** @@ -308,7 +323,7 @@ class mysql extends \phpbb\db\driver\mysql_base  		if (isset($this->open_queries[(int) $query_id]))  		{  			unset($this->open_queries[(int) $query_id]); -			return @mysql_free_result($query_id); +			return mysql_free_result($query_id);  		}  		return false; @@ -411,12 +426,12 @@ class mysql extends \phpbb\db\driver\mysql_base  					if ($result = @mysql_query("EXPLAIN $explain_query", $this->db_connect_id))  					{ -						while ($row = @mysql_fetch_assoc($result)) +						while ($row = mysql_fetch_assoc($result))  						{  							$html_table = $this->sql_report('add_select_row', $query, $html_table, $row);  						} +						mysql_free_result($result);  					} -					@mysql_free_result($result);  					if ($html_table)  					{ @@ -431,7 +446,7 @@ class mysql extends \phpbb\db\driver\mysql_base  						if ($result = @mysql_query('SHOW PROFILE ALL;', $this->db_connect_id))  						{  							$this->html_hold .= '<br />'; -							while ($row = @mysql_fetch_assoc($result)) +							while ($row = mysql_fetch_assoc($result))  							{  								// make <unknown> HTML safe  								if (!empty($row['Source_function'])) @@ -449,8 +464,8 @@ class mysql extends \phpbb\db\driver\mysql_base  								}  								$html_table = $this->sql_report('add_select_row', $query, $html_table, $row);  							} +							mysql_free_result($result);  						} -						@mysql_free_result($result);  						if ($html_table)  						{ @@ -468,11 +483,14 @@ class mysql extends \phpbb\db\driver\mysql_base  				$endtime = $endtime[0] + $endtime[1];  				$result = @mysql_query($query, $this->db_connect_id); -				while ($void = @mysql_fetch_assoc($result)) +				if ($result)  				{ -					// Take the time spent on parsing rows into account +					while ($void = mysql_fetch_assoc($result)) +					{ +						// Take the time spent on parsing rows into account +					} +					mysql_free_result($result);  				} -				@mysql_free_result($result);  				$splittime = explode(' ', microtime());  				$splittime = $splittime[0] + $splittime[1]; diff --git a/phpBB/phpbb/db/driver/mysqli.php b/phpBB/phpbb/db/driver/mysqli.php index c0ddfbf76c..d43e201526 100644 --- a/phpBB/phpbb/db/driver/mysqli.php +++ b/phpBB/phpbb/db/driver/mysqli.php @@ -74,9 +74,10 @@ class mysqli extends \phpbb\db\driver\mysql_base  			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'); -				if ($result !== null) +				if ($result)  				{ -					$row = @mysqli_fetch_assoc($result); +					$row = mysqli_fetch_assoc($result); +					mysqli_free_result($result);  					$modes = array_map('trim', explode(',', $row['sql_mode']));  				} @@ -84,7 +85,6 @@ class mysqli extends \phpbb\db\driver\mysql_base  				{  					$modes = array();  				} -				@mysqli_free_result($result);  				// TRADITIONAL includes STRICT_ALL_TABLES and STRICT_TRANS_TABLES  				if (!in_array('TRADITIONAL', $modes)) @@ -119,9 +119,10 @@ class mysqli extends \phpbb\db\driver\mysql_base  		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'); -			if ($result !== null) +			if ($result)  			{ -				$row = @mysqli_fetch_assoc($result); +				$row = mysqli_fetch_assoc($result); +				mysqli_free_result($result);  				$this->sql_server_version = $row['version']; @@ -130,7 +131,6 @@ class mysqli extends \phpbb\db\driver\mysql_base  					$cache->put('mysqli_version', $this->sql_server_version);  				}  			} -			@mysqli_free_result($result);  		}  		return ($raw) ? $this->sql_server_version : 'MySQL(i) ' . $this->sql_server_version; @@ -202,6 +202,11 @@ class mysqli extends \phpbb\db\driver\mysql_base  					$this->sql_time += microtime(true) - $this->curtime;  				} +				if (!$this->query_result) +				{ +					return false; +				} +  				if ($cache && $cache_ttl)  				{  					$this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); @@ -245,9 +250,9 @@ class mysqli extends \phpbb\db\driver\mysql_base  			return $cache->sql_fetchrow($query_id);  		} -		if ($query_id !== false && $query_id !== null) +		if ($query_id)  		{ -			$result = @mysqli_fetch_assoc($query_id); +			$result = mysqli_fetch_assoc($query_id);  			return $result !== null ? $result : false;  		} @@ -271,7 +276,7 @@ class mysqli extends \phpbb\db\driver\mysql_base  			return $cache->sql_rowseek($rownum, $query_id);  		} -		return ($query_id !== false) ? @mysqli_data_seek($query_id, $rownum) : false; +		return ($query_id) ? @mysqli_data_seek($query_id, $rownum) : false;  	}  	/** @@ -299,7 +304,17 @@ class mysqli extends \phpbb\db\driver\mysql_base  			return $cache->sql_freeresult($query_id);  		} -		return @mysqli_free_result($query_id); +		if (!$query_id) +		{ +			return false; +		} + +		if ($query_id === true) +		{ +			return true; +		} + +		return mysqli_free_result($query_id);  	}  	/** @@ -398,12 +413,12 @@ class mysqli extends \phpbb\db\driver\mysql_base  					if ($result = @mysqli_query($this->db_connect_id, "EXPLAIN $explain_query"))  					{ -						while ($row = @mysqli_fetch_assoc($result)) +						while ($row = mysqli_fetch_assoc($result))  						{  							$html_table = $this->sql_report('add_select_row', $query, $html_table, $row);  						} +						mysqli_free_result($result);  					} -					@mysqli_free_result($result);  					if ($html_table)  					{ @@ -418,7 +433,7 @@ class mysqli extends \phpbb\db\driver\mysql_base  						if ($result = @mysqli_query($this->db_connect_id, 'SHOW PROFILE ALL;'))  						{  							$this->html_hold .= '<br />'; -							while ($row = @mysqli_fetch_assoc($result)) +							while ($row = mysqli_fetch_assoc($result))  							{  								// make <unknown> HTML safe  								if (!empty($row['Source_function'])) @@ -436,8 +451,8 @@ class mysqli extends \phpbb\db\driver\mysql_base  								}  								$html_table = $this->sql_report('add_select_row', $query, $html_table, $row);  							} +							mysqli_free_result($result);  						} -						@mysqli_free_result($result);  						if ($html_table)  						{ @@ -455,14 +470,14 @@ class mysqli extends \phpbb\db\driver\mysql_base  				$endtime = $endtime[0] + $endtime[1];  				$result = @mysqli_query($this->db_connect_id, $query); -				if ($result !== null) +				if ($result)  				{ -					while ($void = @mysqli_fetch_assoc($result)) +					while ($void = mysqli_fetch_assoc($result))  					{  						// Take the time spent on parsing rows into account  					} +					mysqli_free_result($result);  				} -				@mysqli_free_result($result);  				$splittime = explode(' ', microtime());  				$splittime = $splittime[0] + $splittime[1]; diff --git a/phpBB/phpbb/db/driver/oracle.php b/phpBB/phpbb/db/driver/oracle.php index 6dcab5dd7d..54238a15ef 100644 --- a/phpBB/phpbb/db/driver/oracle.php +++ b/phpBB/phpbb/db/driver/oracle.php @@ -84,8 +84,6 @@ class oracle extends \phpbb\db\driver\driver  		* but I assume its because the Oracle extension provides a direct method to access it  		* without a query.  		*/ - -		$use_cache = false;  /*  		global $cache; @@ -439,12 +437,17 @@ class oracle extends \phpbb\db\driver\driver  					$this->sql_time += microtime(true) - $this->curtime;  				} +				if (!$this->query_result) +				{ +					return false; +				} +  				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) +				else if (strpos($query, 'SELECT') === 0)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result;  				} @@ -499,10 +502,10 @@ class oracle extends \phpbb\db\driver\driver  			return $cache->sql_fetchrow($query_id);  		} -		if ($query_id !== false) +		if ($query_id)  		{  			$row = array(); -			$result = @ocifetchinto($query_id, $row, OCI_ASSOC + OCI_RETURN_NULLS); +			$result = ocifetchinto($query_id, $row, OCI_ASSOC + OCI_RETURN_NULLS);  			if (!$result || !$row)  			{ @@ -550,7 +553,7 @@ class oracle extends \phpbb\db\driver\driver  			return $cache->sql_rowseek($rownum, $query_id);  		} -		if ($query_id === false) +		if (!$query_id)  		{  			return false;  		} @@ -583,18 +586,24 @@ class oracle extends \phpbb\db\driver\driver  			{  				$query = 'SELECT ' . $tablename[1] . '_seq.currval FROM DUAL';  				$stmt = @ociparse($this->db_connect_id, $query); -				@ociexecute($stmt, OCI_DEFAULT); +				if ($stmt) +				{ +					$success = @ociexecute($stmt, OCI_DEFAULT); -				$temp_result = @ocifetchinto($stmt, $temp_array, OCI_ASSOC + OCI_RETURN_NULLS); -				@ocifreestatement($stmt); +					if ($success) +					{ +						$temp_result = ocifetchinto($stmt, $temp_array, OCI_ASSOC + OCI_RETURN_NULLS); +						ocifreestatement($stmt); -				if ($temp_result) -				{ -					return $temp_array['CURRVAL']; -				} -				else -				{ -					return false; +						if ($temp_result) +						{ +							return $temp_array['CURRVAL']; +						} +						else +						{ +							return false; +						} +					}  				}  			}  		} @@ -622,7 +631,7 @@ class oracle extends \phpbb\db\driver\driver  		if (isset($this->open_queries[(int) $query_id]))  		{  			unset($this->open_queries[(int) $query_id]); -			return @ocifreestatement($query_id); +			return ocifreestatement($query_id);  		}  		return false; @@ -787,14 +796,20 @@ class oracle extends \phpbb\db\driver\driver  				$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)) +				if ($result)  				{ -					// Take the time spent on parsing rows into account +					$success = @ociexecute($result, OCI_DEFAULT); +					if ($success) +					{ +						$row = array(); + +						while (ocifetchinto($result, $row, OCI_ASSOC + OCI_RETURN_NULLS)) +						{ +							// Take the time spent on parsing rows into account +						} +						@ocifreestatement($result); +					}  				} -				@ocifreestatement($result);  				$splittime = explode(' ', microtime());  				$splittime = $splittime[0] + $splittime[1]; diff --git a/phpBB/phpbb/db/driver/postgres.php b/phpBB/phpbb/db/driver/postgres.php index a3b9aa4c6b..44476612c3 100644 --- a/phpBB/phpbb/db/driver/postgres.php +++ b/phpBB/phpbb/db/driver/postgres.php @@ -123,14 +123,17 @@ class postgres extends \phpbb\db\driver\driver  		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); +			if ($query_id) +			{ +				$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; +				$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); +				if (!empty($cache) && $use_cache) +				{ +					$cache->put('pgsql_version', $this->sql_server_version); +				}  			}  		} @@ -200,12 +203,17 @@ class postgres extends \phpbb\db\driver\driver  					$this->sql_time += microtime(true) - $this->curtime;  				} +				if (!$this->query_result) +				{ +					return false; +				} +  				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) +				else if (strpos($query, 'SELECT') === 0)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result;  				} @@ -275,7 +283,7 @@ class postgres extends \phpbb\db\driver\driver  			return $cache->sql_fetchrow($query_id);  		} -		return ($query_id !== false) ? @pg_fetch_assoc($query_id, null) : false; +		return ($query_id) ? pg_fetch_assoc($query_id, null) : false;  	}  	/** @@ -295,7 +303,7 @@ class postgres extends \phpbb\db\driver\driver  			return $cache->sql_rowseek($rownum, $query_id);  		} -		return ($query_id !== false) ? @pg_result_seek($query_id, $rownum) : false; +		return ($query_id) ? @pg_result_seek($query_id, $rownum) : false;  	}  	/** @@ -317,8 +325,8 @@ class postgres extends \phpbb\db\driver\driver  					return false;  				} -				$temp_result = @pg_fetch_assoc($temp_q_id, null); -				@pg_free_result($query_id); +				$temp_result = pg_fetch_assoc($temp_q_id, null); +				pg_free_result($query_id);  				return ($temp_result) ? $temp_result['last_value'] : false;  			} @@ -347,7 +355,7 @@ class postgres extends \phpbb\db\driver\driver  		if (isset($this->open_queries[(int) $query_id]))  		{  			unset($this->open_queries[(int) $query_id]); -			return @pg_free_result($query_id); +			return pg_free_result($query_id);  		}  		return false; @@ -453,12 +461,12 @@ class postgres extends \phpbb\db\driver\driver  					if ($result = @pg_query($this->db_connect_id, "EXPLAIN $explain_query"))  					{ -						while ($row = @pg_fetch_assoc($result, null)) +						while ($row = pg_fetch_assoc($result, null))  						{  							$html_table = $this->sql_report('add_select_row', $query, $html_table, $row);  						} +						pg_free_result($result);  					} -					@pg_free_result($result);  					if ($html_table)  					{ @@ -473,11 +481,14 @@ class postgres extends \phpbb\db\driver\driver  				$endtime = $endtime[0] + $endtime[1];  				$result = @pg_query($this->db_connect_id, $query); -				while ($void = @pg_fetch_assoc($result, null)) +				if ($result)  				{ -					// Take the time spent on parsing rows into account +					while ($void = pg_fetch_assoc($result, null)) +					{ +						// Take the time spent on parsing rows into account +					} +					pg_free_result($result);  				} -				@pg_free_result($result);  				$splittime = explode(' ', microtime());  				$splittime = $splittime[0] + $splittime[1]; diff --git a/phpBB/phpbb/db/driver/sqlite.php b/phpBB/phpbb/db/driver/sqlite.php index d5da0e2438..8e205ebb81 100644 --- a/phpBB/phpbb/db/driver/sqlite.php +++ b/phpBB/phpbb/db/driver/sqlite.php @@ -70,13 +70,16 @@ class sqlite extends \phpbb\db\driver\driver  		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); +			if ($result) +			{ +				$row = sqlite_fetch_array($result, SQLITE_ASSOC); -			$this->sql_server_version = (!empty($row['version'])) ? $row['version'] : 0; +				$this->sql_server_version = (!empty($row['version'])) ? $row['version'] : 0; -			if (!empty($cache) && $use_cache) -			{ -				$cache->put('sqlite_version', $this->sql_server_version); +				if (!empty($cache) && $use_cache) +				{ +					$cache->put('sqlite_version', $this->sql_server_version); +				}  			}  		} @@ -145,14 +148,14 @@ class sqlite extends \phpbb\db\driver\driver  					$this->sql_time += microtime(true) - $this->curtime;  				} -				if ($cache && $cache_ttl) +				if (!$this->query_result)  				{ -					$this->open_queries[(int) $this->query_result] = $this->query_result; -					$this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); +					return false;  				} -				else if (strpos($query, 'SELECT') === 0 && $this->query_result) + +				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 (defined('DEBUG')) @@ -211,7 +214,7 @@ class sqlite extends \phpbb\db\driver\driver  			return $cache->sql_fetchrow($query_id);  		} -		return ($query_id !== false) ? @sqlite_fetch_array($query_id, SQLITE_ASSOC) : false; +		return ($query_id) ? sqlite_fetch_array($query_id, SQLITE_ASSOC) : false;  	}  	/** @@ -231,7 +234,7 @@ class sqlite extends \phpbb\db\driver\driver  			return $cache->sql_rowseek($rownum, $query_id);  		} -		return ($query_id !== false) ? @sqlite_seek($query_id, $rownum) : false; +		return ($query_id) ? @sqlite_seek($query_id, $rownum) : false;  	}  	/** @@ -362,9 +365,12 @@ class sqlite extends \phpbb\db\driver\driver  				$endtime = $endtime[0] + $endtime[1];  				$result = @sqlite_query($query, $this->db_connect_id); -				while ($void = @sqlite_fetch_array($result, SQLITE_ASSOC)) +				if ($result)  				{ -					// Take the time spent on parsing rows into account +					while ($void = sqlite_fetch_array($result, SQLITE_ASSOC)) +					{ +						// Take the time spent on parsing rows into account +					}  				}  				$splittime = explode(' ', microtime()); diff --git a/phpBB/phpbb/db/driver/sqlite3.php b/phpBB/phpbb/db/driver/sqlite3.php index cc3352af34..0508500c52 100644 --- a/phpBB/phpbb/db/driver/sqlite3.php +++ b/phpBB/phpbb/db/driver/sqlite3.php @@ -102,7 +102,7 @@ class sqlite3 extends \phpbb\db\driver\driver  			break;  			case 'rollback': -				return $this->dbo->exec('ROLLBACK'); +				return @$this->dbo->exec('ROLLBACK');  			break;  		} @@ -134,9 +134,26 @@ class sqlite3 extends \phpbb\db\driver\driver  			if ($this->query_result === false)  			{ +				if ($this->transaction === true && strpos($query, 'INSERT') === 0) +				{ +					$query = preg_replace('/^INSERT INTO/', 'INSERT OR ROLLBACK INTO', $query); +				} +  				if (($this->query_result = @$this->dbo->query($query)) === false)  				{ -					$this->sql_error($query); +					// Try to recover a lost database connection +					if ($this->dbo && !@$this->dbo->lastErrorMsg()) +					{ +						if ($this->sql_connect($this->server, $this->user, '', $this->dbname)) +						{ +							$this->query_result = @$this->dbo->query($query); +						} +					} + +					if ($this->query_result === false) +					{ +						$this->sql_error($query); +					}  				}  				if (defined('DEBUG')) @@ -148,6 +165,11 @@ class sqlite3 extends \phpbb\db\driver\driver  					$this->sql_time += microtime(true) - $this->curtime;  				} +				if (!$this->query_result) +				{ +					return false; +				} +  				if ($cache && $cache_ttl)  				{  					$this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); @@ -208,6 +230,7 @@ class sqlite3 extends \phpbb\db\driver\driver  		if ($query_id === false)  		{ +			/** @var \SQLite3Result $query_id */  			$query_id = $this->query_result;  		} @@ -216,7 +239,7 @@ class sqlite3 extends \phpbb\db\driver\driver  			return $cache->sql_fetchrow($query_id);  		} -		return is_object($query_id) ? $query_id->fetchArray(SQLITE3_ASSOC) : false; +		return is_object($query_id) ? @$query_id->fetchArray(SQLITE3_ASSOC) : false;  	}  	/** @@ -389,9 +412,12 @@ class sqlite3 extends \phpbb\db\driver\driver  				$endtime = $endtime[0] + $endtime[1];  				$result = $this->dbo->query($query); -				while ($void = $result->fetchArray(SQLITE3_ASSOC)) +				if ($result)  				{ -					// Take the time spent on parsing rows into account +						while ($void = $result->fetchArray(SQLITE3_ASSOC)) +						{ +							// Take the time spent on parsing rows into account +						}  				}  				$splittime = explode(' ', microtime()); diff --git a/phpBB/phpbb/db/extractor/base_extractor.php b/phpBB/phpbb/db/extractor/base_extractor.php new file mode 100644 index 0000000000..547c85f066 --- /dev/null +++ b/phpBB/phpbb/db/extractor/base_extractor.php @@ -0,0 +1,252 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +use phpbb\db\extractor\exception\invalid_format_exception; +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +/** + * Abstract base class for database extraction + */ +abstract class base_extractor implements extractor_interface +{ +	/** +	 * @var    string    phpBB root path +	 */ +	protected $phpbb_root_path; + +	/** +	 * @var    \phpbb\request\request_interface +	 */ +	protected $request; + +	/** +	 * @var    \phpbb\db\driver\driver_interface +	 */ +	protected $db; + +	/** +	 * @var    bool +	 */ +	protected $download; + +	/** +	 * @var    bool +	 */ +	protected $store; + +	/** +	 * @var    int +	 */ +	protected $time; + +	/** +	 * @var    string +	 */ +	protected $format; + +	/** +	 * @var    resource +	 */ +	protected $fp; + +	/** +	 * @var string +	 */ +	protected $write; + +	/** +	 * @var string +	 */ +	protected $close; + +	/** +	 * @var bool +	 */ +	protected $run_comp; + +	/** +	 * @var bool +	 */ +	protected $is_initialized; + +	/** +	 * Constructor +	 * +	 * @param string $phpbb_root_path +	 * @param \phpbb\request\request_interface $request +	 * @param \phpbb\db\driver\driver_interface $db +	 */ +	public function __construct($phpbb_root_path, \phpbb\request\request_interface $request, \phpbb\db\driver\driver_interface $db) +	{ +		$this->phpbb_root_path	= $phpbb_root_path; +		$this->request			= $request; +		$this->db				= $db; +		$this->fp				= null; + +		$this->is_initialized   = false; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function init_extractor($format, $filename, $time, $download = false, $store = false) +	{ +		$this->download			= $download; +		$this->store			= $store; +		$this->time				= $time; +		$this->format			= $format; + +		switch ($format) +		{ +			case 'text': +				$ext = '.sql'; +				$open = 'fopen'; +				$this->write = 'fwrite'; +				$this->close = 'fclose'; +				$mimetype = 'text/x-sql'; +			break; +			case 'bzip2': +				$ext = '.sql.bz2'; +				$open = 'bzopen'; +				$this->write = 'bzwrite'; +				$this->close = 'bzclose'; +				$mimetype = 'application/x-bzip2'; +			break; +			case 'gzip': +				$ext = '.sql.gz'; +				$open = 'gzopen'; +				$this->write = 'gzwrite'; +				$this->close = 'gzclose'; +				$mimetype = 'application/x-gzip'; +			break; +			default: +				throw new invalid_format_exception(); +			break; +		} + +		if ($download === true) +		{ +			$name = $filename . $ext; +			header('Cache-Control: private, no-cache'); +			header("Content-Type: $mimetype; name=\"$name\""); +			header("Content-disposition: attachment; filename=$name"); + +			switch ($format) +			{ +				case 'bzip2': +					ob_start(); +				break; + +				case 'gzip': +					if (strpos($this->request->header('Accept-Encoding'), 'gzip') !== false && strpos(strtolower($this->request->header('User-Agent')), 'msie') === false) +					{ +						ob_start('ob_gzhandler'); +					} +					else +					{ +						$this->run_comp = true; +					} +				break; +			} +		} + +		if ($store === true) +		{ +			$file = $this->phpbb_root_path . 'store/' . $filename . $ext; + +			$this->fp = $open($file, 'w'); + +			if (!$this->fp) +			{ +				trigger_error('FILE_WRITE_FAIL', E_USER_ERROR); +			} +		} + +		$this->is_initialized = true; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_end() +	{ +		static $close; + +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		if ($this->store) +		{ +			if ($close === null) +			{ +				$close = $this->close; +			} +			$close($this->fp); +		} + +		// bzip2 must be written all the way at the end +		if ($this->download && $this->format === 'bzip2') +		{ +			$c = ob_get_clean(); +			echo bzcompress($c); +		} +	} + +	/** +	* {@inheritdoc} +	*/ +	public function flush($data) +	{ +		static $write; + +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		if ($this->store === true) +		{ +			if ($write === null) +			{ +				$write = $this->write; +			} +			$write($this->fp, $data); +		} + +		if ($this->download === true) +		{ +			if ($this->format === 'bzip2' || $this->format === 'text' || ($this->format === 'gzip' && !$this->run_comp)) +			{ +				echo $data; +			} + +			// we can write the gzip data as soon as we get it +			if ($this->format === 'gzip') +			{ +				if ($this->run_comp) +				{ +					echo gzencode($data); +				} +				else +				{ +					ob_flush(); +					flush(); +				} +			} +		} +	} +} diff --git a/phpBB/phpbb/db/extractor/exception/extractor_not_initialized_exception.php b/phpBB/phpbb/db/extractor/exception/extractor_not_initialized_exception.php new file mode 100644 index 0000000000..62eb434be1 --- /dev/null +++ b/phpBB/phpbb/db/extractor/exception/extractor_not_initialized_exception.php @@ -0,0 +1,24 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor\exception; + +use phpbb\exception\runtime_exception; + +/** +* This exception is thrown when invalid format is given to the extractor +*/ +class extractor_not_initialized_exception extends runtime_exception +{ + +} diff --git a/phpBB/phpbb/db/extractor/exception/invalid_format_exception.php b/phpBB/phpbb/db/extractor/exception/invalid_format_exception.php new file mode 100644 index 0000000000..6be24cb5dc --- /dev/null +++ b/phpBB/phpbb/db/extractor/exception/invalid_format_exception.php @@ -0,0 +1,22 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor\exception; + +/** +* This exception is thrown when invalid format is given to the extractor +*/ +class invalid_format_exception extends \InvalidArgumentException +{ + +} diff --git a/phpBB/phpbb/db/extractor/extractor_interface.php b/phpBB/phpbb/db/extractor/extractor_interface.php new file mode 100644 index 0000000000..ff45df9bb7 --- /dev/null +++ b/phpBB/phpbb/db/extractor/extractor_interface.php @@ -0,0 +1,80 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +/** +* Database extractor interface +*/ +interface extractor_interface +{ +	/** +	* Start the extraction of the database +	* +	* This function initialize the database extraction. It is required to call this +	* function before calling any other extractor functions. +	* +	* @param string	$format +	* @param string	$filename +	* @param int	$time +	* @param bool	$download +	* @param bool	$store +	* @return null +	* @throws \phpbb\db\extractor\exception\invalid_format_exception when $format is invalid +	*/ +	public function init_extractor($format, $filename, $time, $download = false, $store = false); + +	/** +	* Writes header comments to the database backup +	* +	* @param	string	$table_prefix	prefix of phpBB database tables +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	public function write_start($table_prefix); + +	/** +	* Closes file and/or dumps download data +	* +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	public function write_end(); + +	/** +	* Extracts database table structure +	* +	* @param	string	$table_name	name of the database table +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	public function write_table($table_name); + +	/** +	* Extracts data from database table +	* +	* @param	string	$table_name	name of the database table +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	public function write_data($table_name); + +	/** +	* Writes data to file/download content +	* +	* @param string	$data +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	public function flush($data); +} diff --git a/phpBB/phpbb/db/extractor/factory.php b/phpBB/phpbb/db/extractor/factory.php new file mode 100644 index 0000000000..a1ffb65595 --- /dev/null +++ b/phpBB/phpbb/db/extractor/factory.php @@ -0,0 +1,79 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +/** +* A factory which serves the suitable extractor instance for the given dbal +*/ +class factory +{ +	/** +	 * @var \phpbb\db\driver\driver_interface +	 */ +	protected $db; + +	/** +	 * @var \Symfony\Component\DependencyInjection\ContainerInterface +	 */ +	protected $container; + +	/** +	* Extractor factory constructor +	* +	* @param \phpbb\db\driver\driver_interface							$db +	* @param \Symfony\Component\DependencyInjection\ContainerInterface	$container +	*/ +	public function __construct(\phpbb\db\driver\driver_interface $db, \Symfony\Component\DependencyInjection\ContainerInterface $container) +	{ +		$this->db			= $db; +		$this->container	= $container; +	} + +	/** +	* DB extractor factory getter +	* +	* @return \phpbb\db\extractor\extractor_interface an appropriate instance of the database extractor for the used database driver +	* @throws \InvalidArgumentException when the database driver is unknown +	*/ +	public function get() +	{ +		// Return the appropriate DB extractor +		if ($this->db instanceof \phpbb\db\driver\mssql || $this->db instanceof \phpbb\db\driver\mssql_base) +		{ +			return $this->container->get('dbal.extractor.extractors.mssql_extractor'); +		} +		else if ($this->db instanceof \phpbb\db\driver\mysql_base) +		{ +			return $this->container->get('dbal.extractor.extractors.mysql_extractor'); +		} +		else if ($this->db instanceof \phpbb\db\driver\oracle) +		{ +			return $this->container->get('dbal.extractor.extractors.oracle_extractor'); +		} +		else if ($this->db instanceof \phpbb\db\driver\postgres) +		{ +			return $this->container->get('dbal.extractor.extractors.postgres_extractor'); +		} +		else if ($this->db instanceof \phpbb\db\driver\sqlite) +		{ +			return $this->container->get('dbal.extractor.extractors.sqlite_extractor'); +		} +		else if ($this->db instanceof \phpbb\db\driver\sqlite3) +		{ +			return $this->container->get('dbal.extractor.extractors.sqlite3_extractor'); +		} + +		throw new \InvalidArgumentException('Invalid database driver given'); +	} +} diff --git a/phpBB/phpbb/db/extractor/mssql_extractor.php b/phpBB/phpbb/db/extractor/mssql_extractor.php new file mode 100644 index 0000000000..fc30f4789d --- /dev/null +++ b/phpBB/phpbb/db/extractor/mssql_extractor.php @@ -0,0 +1,524 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class mssql_extractor extends base_extractor +{ +	/** +	* Writes closing line(s) to database backup +	* +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	public function write_end() +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$this->flush("COMMIT\nGO\n"); +		parent::write_end(); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_start($table_prefix) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql_data = "--\n"; +		$sql_data .= "-- phpBB Backup Script\n"; +		$sql_data .= "-- Dump of tables for $table_prefix\n"; +		$sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; +		$sql_data .= "--\n"; +		$sql_data .= "BEGIN TRANSACTION\n"; +		$sql_data .= "GO\n"; +		$this->flush($sql_data); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_table($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql_data = '-- Table: ' . $table_name . "\n"; +		$sql_data .= "IF OBJECT_ID(N'$table_name', N'U') IS NOT NULL\n"; +		$sql_data .= "DROP TABLE $table_name;\n"; +		$sql_data .= "GO\n"; +		$sql_data .= "\nCREATE TABLE [$table_name] (\n"; +		$rows = array(); + +		$text_flag = false; + +		$sql = "SELECT COLUMN_NAME, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') as IS_IDENTITY +			FROM INFORMATION_SCHEMA.COLUMNS +			WHERE TABLE_NAME = '$table_name'"; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$line = "\t[{$row['COLUMN_NAME']}] [{$row['DATA_TYPE']}]"; + +			if ($row['DATA_TYPE'] == 'text') +			{ +				$text_flag = true; +			} + +			if ($row['IS_IDENTITY']) +			{ +				$line .= ' IDENTITY (1 , 1)'; +			} + +			if ($row['CHARACTER_MAXIMUM_LENGTH'] && $row['DATA_TYPE'] !== 'text') +			{ +				$line .= ' (' . $row['CHARACTER_MAXIMUM_LENGTH'] . ')'; +			} + +			if ($row['IS_NULLABLE'] == 'YES') +			{ +				$line .= ' NULL'; +			} +			else +			{ +				$line .= ' NOT NULL'; +			} + +			if ($row['COLUMN_DEFAULT']) +			{ +				$line .= ' DEFAULT ' . $row['COLUMN_DEFAULT']; +			} + +			$rows[] = $line; +		} +		$this->db->sql_freeresult($result); + +		$sql_data .= implode(",\n", $rows); +		$sql_data .= "\n) ON [PRIMARY]"; + +		if ($text_flag) +		{ +			$sql_data .= " TEXTIMAGE_ON [PRIMARY]"; +		} + +		$sql_data .= "\nGO\n\n"; +		$rows = array(); + +		$sql = "SELECT CONSTRAINT_NAME, COLUMN_NAME +			FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE +			WHERE TABLE_NAME = '$table_name'"; +		$result = $this->db->sql_query($sql); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if (!sizeof($rows)) +			{ +				$sql_data .= "ALTER TABLE [$table_name] WITH NOCHECK ADD\n"; +				$sql_data .= "\tCONSTRAINT [{$row['CONSTRAINT_NAME']}] PRIMARY KEY  CLUSTERED \n\t(\n"; +			} +			$rows[] = "\t\t[{$row['COLUMN_NAME']}]"; +		} +		if (sizeof($rows)) +		{ +			$sql_data .= implode(",\n", $rows); +			$sql_data .= "\n\t)  ON [PRIMARY] \nGO\n"; +		} +		$this->db->sql_freeresult($result); + +		$index = array(); +		$sql = "EXEC sp_statistics '$table_name'"; +		$result = $this->db->sql_query($sql); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if ($row['TYPE'] == 3) +			{ +				$index[$row['INDEX_NAME']][] = '[' . $row['COLUMN_NAME'] . ']'; +			} +		} +		$this->db->sql_freeresult($result); + +		foreach ($index as $index_name => $column_name) +		{ +			$index[$index_name] = implode(', ', $column_name); +		} + +		foreach ($index as $index_name => $columns) +		{ +			$sql_data .= "\nCREATE  INDEX [$index_name] ON [$table_name]($columns) ON [PRIMARY]\nGO\n"; +		} +		$this->flush($sql_data); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_data($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		if ($this->db->get_sql_layer() === 'mssql') +		{ +			$this->write_data_mssql($table_name); +		} +		else if ($this->db->get_sql_layer() === 'mssqlnative') +		{ +			$this->write_data_mssqlnative($table_name); +		} +		else +		{ +			$this->write_data_odbc($table_name); +		} +	} + +	/** +	* Extracts data from database table (for MSSQL driver) +	* +	* @param	string	$table_name	name of the database table +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	protected function write_data_mssql($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$ary_type = $ary_name = array(); +		$ident_set = false; +		$sql_data = ''; + +		// Grab all of the data from current table. +		$sql = "SELECT * +			FROM $table_name"; +		$result = $this->db->sql_query($sql); + +		$retrieved_data = mssql_num_rows($result); + +		$i_num_fields = mssql_num_fields($result); + +		for ($i = 0; $i < $i_num_fields; $i++) +		{ +			$ary_type[$i] = mssql_field_type($result, $i); +			$ary_name[$i] = mssql_field_name($result, $i); +		} + +		if ($retrieved_data) +		{ +			$sql = "SELECT 1 as has_identity +				FROM INFORMATION_SCHEMA.COLUMNS +				WHERE COLUMNPROPERTY(object_id('$table_name'), COLUMN_NAME, 'IsIdentity') = 1"; +			$result2 = $this->db->sql_query($sql); +			$row2 = $this->db->sql_fetchrow($result2); +			if (!empty($row2['has_identity'])) +			{ +				$sql_data .= "\nSET IDENTITY_INSERT $table_name ON\nGO\n"; +				$ident_set = true; +			} +			$this->db->sql_freeresult($result2); +		} + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$schema_vals = $schema_fields = array(); + +			// Build the SQL statement to recreate the data. +			for ($i = 0; $i < $i_num_fields; $i++) +			{ +				$str_val = $row[$ary_name[$i]]; + +				if (preg_match('#char|text|bool|varbinary#i', $ary_type[$i])) +				{ +					$str_quote = ''; +					$str_empty = "''"; +					$str_val = sanitize_data_mssql(str_replace("'", "''", $str_val)); +				} +				else if (preg_match('#date|timestamp#i', $ary_type[$i])) +				{ +					if (empty($str_val)) +					{ +						$str_quote = ''; +					} +					else +					{ +						$str_quote = "'"; +					} +				} +				else +				{ +					$str_quote = ''; +					$str_empty = 'NULL'; +				} + +				if (empty($str_val) && $str_val !== '0' && !(is_int($str_val) || is_float($str_val))) +				{ +					$str_val = $str_empty; +				} + +				$schema_vals[$i] = $str_quote . $str_val . $str_quote; +				$schema_fields[$i] = $ary_name[$i]; +			} + +			// Take the ordered fields and their associated data and build it +			// into a valid sql statement to recreate that field in the data. +			$sql_data .= "INSERT INTO $table_name (" . implode(', ', $schema_fields) . ') VALUES (' . implode(', ', $schema_vals) . ");\nGO\n"; + +			$this->flush($sql_data); +			$sql_data = ''; +		} +		$this->db->sql_freeresult($result); + +		if ($retrieved_data && $ident_set) +		{ +			$sql_data .= "\nSET IDENTITY_INSERT $table_name OFF\nGO\n"; +		} +		$this->flush($sql_data); +	} + +	/** +	* Extracts data from database table (for MSSQL Native driver) +	* +	* @param	string	$table_name	name of the database table +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	protected function write_data_mssqlnative($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$ary_type = $ary_name = array(); +		$ident_set = false; +		$sql_data = ''; + +		// Grab all of the data from current table. +		$sql = "SELECT * FROM $table_name"; +		$this->db->mssqlnative_set_query_options(array('Scrollable' => SQLSRV_CURSOR_STATIC)); +		$result = $this->db->sql_query($sql); + +		$retrieved_data = $this->db->mssqlnative_num_rows($result); + +		if (!$retrieved_data) +		{ +			$this->db->sql_freeresult($result); +			return; +		} + +		$sql = "SELECT COLUMN_NAME, DATA_TYPE +			FROM INFORMATION_SCHEMA.COLUMNS +			WHERE INFORMATION_SCHEMA.COLUMNS.TABLE_NAME = '" . $this->db->sql_escape($table_name) . "'"; +		$result_fields = $this->db->sql_query($sql); + +		$i_num_fields = 0; +		while ($row = $this->db->sql_fetchrow($result_fields)) +		{ +			$ary_type[$i_num_fields] = $row['DATA_TYPE']; +			$ary_name[$i_num_fields] = $row['COLUMN_NAME']; +			$i_num_fields++; +		} +		$this->db->sql_freeresult($result_fields); + +		$sql = "SELECT 1 as has_identity +			FROM INFORMATION_SCHEMA.COLUMNS +			WHERE COLUMNPROPERTY(object_id('$table_name'), COLUMN_NAME, 'IsIdentity') = 1"; +		$result2 = $this->db->sql_query($sql); +		$row2 = $this->db->sql_fetchrow($result2); + +		if (!empty($row2['has_identity'])) +		{ +			$sql_data .= "\nSET IDENTITY_INSERT $table_name ON\nGO\n"; +			$ident_set = true; +		} +		$this->db->sql_freeresult($result2); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$schema_vals = $schema_fields = array(); + +			// Build the SQL statement to recreate the data. +			for ($i = 0; $i < $i_num_fields; $i++) +			{ +				$str_val = $row[$ary_name[$i]]; + +				// defaults to type number - better quote just to be safe, so check for is_int too +				if (is_int($ary_type[$i]) || preg_match('#char|text|bool|varbinary#i', $ary_type[$i])) +				{ +					$str_quote = ''; +					$str_empty = "''"; +					$str_val = sanitize_data_mssql(str_replace("'", "''", $str_val)); +				} +				else if (preg_match('#date|timestamp#i', $ary_type[$i])) +				{ +					if (empty($str_val)) +					{ +						$str_quote = ''; +					} +					else +					{ +						$str_quote = "'"; +					} +				} +				else +				{ +					$str_quote = ''; +					$str_empty = 'NULL'; +				} + +				if (empty($str_val) && $str_val !== '0' && !(is_int($str_val) || is_float($str_val))) +				{ +					$str_val = $str_empty; +				} + +				$schema_vals[$i] = $str_quote . $str_val . $str_quote; +				$schema_fields[$i] = $ary_name[$i]; +			} + +			// Take the ordered fields and their associated data and build it +			// into a valid sql statement to recreate that field in the data. +			$sql_data .= "INSERT INTO $table_name (" . implode(', ', $schema_fields) . ') VALUES (' . implode(', ', $schema_vals) . ");\nGO\n"; + +			$this->flush($sql_data); +			$sql_data = ''; +		} +		$this->db->sql_freeresult($result); + +		if ($ident_set) +		{ +			$sql_data .= "\nSET IDENTITY_INSERT $table_name OFF\nGO\n"; +		} +		$this->flush($sql_data); +	} + +	/** +	* Extracts data from database table (for ODBC driver) +	* +	* @param	string	$table_name	name of the database table +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	protected function write_data_odbc($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$ary_type = $ary_name = array(); +		$ident_set = false; +		$sql_data = ''; + +		// Grab all of the data from current table. +		$sql = "SELECT * +			FROM $table_name"; +		$result = $this->db->sql_query($sql); + +		$retrieved_data = odbc_num_rows($result); + +		if ($retrieved_data) +		{ +			$sql = "SELECT 1 as has_identity +				FROM INFORMATION_SCHEMA.COLUMNS +				WHERE COLUMNPROPERTY(object_id('$table_name'), COLUMN_NAME, 'IsIdentity') = 1"; +			$result2 = $this->db->sql_query($sql); +			$row2 = $this->db->sql_fetchrow($result2); +			if (!empty($row2['has_identity'])) +			{ +				$sql_data .= "\nSET IDENTITY_INSERT $table_name ON\nGO\n"; +				$ident_set = true; +			} +			$this->db->sql_freeresult($result2); +		} + +		$i_num_fields = odbc_num_fields($result); + +		for ($i = 0; $i < $i_num_fields; $i++) +		{ +			$ary_type[$i] = odbc_field_type($result, $i + 1); +			$ary_name[$i] = odbc_field_name($result, $i + 1); +		} + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$schema_vals = $schema_fields = array(); + +			// Build the SQL statement to recreate the data. +			for ($i = 0; $i < $i_num_fields; $i++) +			{ +				$str_val = $row[$ary_name[$i]]; + +				if (preg_match('#char|text|bool|varbinary#i', $ary_type[$i])) +				{ +					$str_quote = ''; +					$str_empty = "''"; +					$str_val = sanitize_data_mssql(str_replace("'", "''", $str_val)); +				} +				else if (preg_match('#date|timestamp#i', $ary_type[$i])) +				{ +					if (empty($str_val)) +					{ +						$str_quote = ''; +					} +					else +					{ +						$str_quote = "'"; +					} +				} +				else +				{ +					$str_quote = ''; +					$str_empty = 'NULL'; +				} + +				if (empty($str_val) && $str_val !== '0' && !(is_int($str_val) || is_float($str_val))) +				{ +					$str_val = $str_empty; +				} + +				$schema_vals[$i] = $str_quote . $str_val . $str_quote; +				$schema_fields[$i] = $ary_name[$i]; +			} + +			// Take the ordered fields and their associated data and build it +			// into a valid sql statement to recreate that field in the data. +			$sql_data .= "INSERT INTO $table_name (" . implode(', ', $schema_fields) . ') VALUES (' . implode(', ', $schema_vals) . ");\nGO\n"; + +			$this->flush($sql_data); + +			$sql_data = ''; + +		} +		$this->db->sql_freeresult($result); + +		if ($retrieved_data && $ident_set) +		{ +			$sql_data .= "\nSET IDENTITY_INSERT $table_name OFF\nGO\n"; +		} +		$this->flush($sql_data); +	} +} diff --git a/phpBB/phpbb/db/extractor/mysql_extractor.php b/phpBB/phpbb/db/extractor/mysql_extractor.php new file mode 100644 index 0000000000..34e309c19e --- /dev/null +++ b/phpBB/phpbb/db/extractor/mysql_extractor.php @@ -0,0 +1,403 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class mysql_extractor extends base_extractor +{ +	/** +	* {@inheritdoc} +	*/ +	public function write_start($table_prefix) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql_data = "#\n"; +		$sql_data .= "# phpBB Backup Script\n"; +		$sql_data .= "# Dump of tables for $table_prefix\n"; +		$sql_data .= "# DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; +		$sql_data .= "#\n"; +		$this->flush($sql_data); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_table($table_name) +	{ +		static $new_extract; + +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		if ($new_extract === null) +		{ +			if ($this->db->get_sql_layer() === 'mysqli' || version_compare($this->db->sql_server_info(true), '3.23.20', '>=')) +			{ +				$new_extract = true; +			} +			else +			{ +				$new_extract = false; +			} +		} + +		if ($new_extract) +		{ +			$this->new_write_table($table_name); +		} +		else +		{ +			$this->old_write_table($table_name); +		} +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_data($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		if ($this->db->get_sql_layer() === 'mysqli') +		{ +			$this->write_data_mysqli($table_name); +		} +		else +		{ +			$this->write_data_mysql($table_name); +		} +	} + +	/** +	* Extracts data from database table (for MySQLi driver) +	* +	* @param	string	$table_name	name of the database table +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	protected function write_data_mysqli($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql = "SELECT * +			FROM $table_name"; +		$result = mysqli_query($this->db->get_db_connect_id(), $sql, MYSQLI_USE_RESULT); +		if ($result != false) +		{ +			$fields_cnt = mysqli_num_fields($result); + +			// Get field information +			$field = mysqli_fetch_fields($result); +			$field_set = array(); + +			for ($j = 0; $j < $fields_cnt; $j++) +			{ +				$field_set[] = $field[$j]->name; +			} + +			$search			= array("\\", "'", "\x00", "\x0a", "\x0d", "\x1a", '"'); +			$replace		= array("\\\\", "\\'", '\0', '\n', '\r', '\Z', '\\"'); +			$fields			= implode(', ', $field_set); +			$sql_data		= 'INSERT INTO ' . $table_name . ' (' . $fields . ') VALUES '; +			$first_set		= true; +			$query_len		= 0; +			$max_len		= get_usable_memory(); + +			while ($row = mysqli_fetch_row($result)) +			{ +				$values	= array(); +				if ($first_set) +				{ +					$query = $sql_data . '('; +				} +				else +				{ +					$query  .= ',('; +				} + +				for ($j = 0; $j < $fields_cnt; $j++) +				{ +					if (!isset($row[$j]) || is_null($row[$j])) +					{ +						$values[$j] = 'NULL'; +					} +					else if (($field[$j]->flags & 32768) && !($field[$j]->flags & 1024)) +					{ +						$values[$j] = $row[$j]; +					} +					else +					{ +						$values[$j] = "'" . str_replace($search, $replace, $row[$j]) . "'"; +					} +				} +				$query .= implode(', ', $values) . ')'; + +				$query_len += strlen($query); +				if ($query_len > $max_len) +				{ +					$this->flush($query . ";\n\n"); +					$query = ''; +					$query_len = 0; +					$first_set = true; +				} +				else +				{ +					$first_set = false; +				} +			} +			mysqli_free_result($result); + +			// check to make sure we have nothing left to flush +			if (!$first_set && $query) +			{ +				$this->flush($query . ";\n\n"); +			} +		} +	} + +	/** +	* Extracts data from database table (for MySQL driver) +	* +	* @param	string	$table_name	name of the database table +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	protected function write_data_mysql($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql = "SELECT * +			FROM $table_name"; +		$result = mysql_unbuffered_query($sql, $this->db->get_db_connect_id()); + +		if ($result != false) +		{ +			$fields_cnt = mysql_num_fields($result); + +			// Get field information +			$field = array(); +			for ($i = 0; $i < $fields_cnt; $i++) +			{ +				$field[] = mysql_fetch_field($result, $i); +			} +			$field_set = array(); + +			for ($j = 0; $j < $fields_cnt; $j++) +			{ +				$field_set[] = $field[$j]->name; +			} + +			$search			= array("\\", "'", "\x00", "\x0a", "\x0d", "\x1a", '"'); +			$replace		= array("\\\\", "\\'", '\0', '\n', '\r', '\Z', '\\"'); +			$fields			= implode(', ', $field_set); +			$sql_data		= 'INSERT INTO ' . $table_name . ' (' . $fields . ') VALUES '; +			$first_set		= true; +			$query_len		= 0; +			$max_len		= get_usable_memory(); + +			while ($row = mysql_fetch_row($result)) +			{ +				$values = array(); +				if ($first_set) +				{ +					$query = $sql_data . '('; +				} +				else +				{ +					$query  .= ',('; +				} + +				for ($j = 0; $j < $fields_cnt; $j++) +				{ +					if (!isset($row[$j]) || is_null($row[$j])) +					{ +						$values[$j] = 'NULL'; +					} +					else if ($field[$j]->numeric && ($field[$j]->type !== 'timestamp')) +					{ +						$values[$j] = $row[$j]; +					} +					else +					{ +						$values[$j] = "'" . str_replace($search, $replace, $row[$j]) . "'"; +					} +				} +				$query .= implode(', ', $values) . ')'; + +				$query_len += strlen($query); +				if ($query_len > $max_len) +				{ +					$this->flush($query . ";\n\n"); +					$query = ''; +					$query_len = 0; +					$first_set = true; +				} +				else +				{ +					$first_set = false; +				} +			} +			mysql_free_result($result); + +			// check to make sure we have nothing left to flush +			if (!$first_set && $query) +			{ +				$this->flush($query . ";\n\n"); +			} +		} +	} + +	/** +	* Extracts database table structure (for MySQLi or MySQL 3.23.20+) +	* +	* @param	string	$table_name	name of the database table +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	protected function new_write_table($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql = 'SHOW CREATE TABLE ' . $table_name; +		$result = $this->db->sql_query($sql); +		$row = $this->db->sql_fetchrow($result); + +		$sql_data = '# Table: ' . $table_name . "\n"; +		$sql_data .= "DROP TABLE IF EXISTS $table_name;\n"; +		$this->flush($sql_data . $row['Create Table'] . ";\n\n"); + +		$this->db->sql_freeresult($result); +	} + +	/** +	* Extracts database table structure (for MySQL verisons older than 3.23.20) +	* +	* @param	string	$table_name	name of the database table +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	protected function old_write_table($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql_data = '# Table: ' . $table_name . "\n"; +		$sql_data .= "DROP TABLE IF EXISTS $table_name;\n"; +		$sql_data .= "CREATE TABLE $table_name(\n"; +		$rows = array(); + +		$sql = "SHOW FIELDS +			FROM $table_name"; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$line = '   ' . $row['Field'] . ' ' . $row['Type']; + +			if (!is_null($row['Default'])) +			{ +				$line .= " DEFAULT '{$row['Default']}'"; +			} + +			if ($row['Null'] != 'YES') +			{ +				$line .= ' NOT NULL'; +			} + +			if ($row['Extra'] != '') +			{ +				$line .= ' ' . $row['Extra']; +			} + +			$rows[] = $line; +		} +		$this->db->sql_freeresult($result); + +		$sql = "SHOW KEYS +			FROM $table_name"; + +		$result = $this->db->sql_query($sql); + +		$index = array(); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$kname = $row['Key_name']; + +			if ($kname != 'PRIMARY') +			{ +				if ($row['Non_unique'] == 0) +				{ +					$kname = "UNIQUE|$kname"; +				} +			} + +			if ($row['Sub_part']) +			{ +				$row['Column_name'] .= '(' . $row['Sub_part'] . ')'; +			} +			$index[$kname][] = $row['Column_name']; +		} +		$this->db->sql_freeresult($result); + +		foreach ($index as $key => $columns) +		{ +			$line = '   '; + +			if ($key == 'PRIMARY') +			{ +				$line .= 'PRIMARY KEY (' . implode(', ', $columns) . ')'; +			} +			else if (strpos($key, 'UNIQUE') === 0) +			{ +				$line .= 'UNIQUE ' . substr($key, 7) . ' (' . implode(', ', $columns) . ')'; +			} +			else if (strpos($key, 'FULLTEXT') === 0) +			{ +				$line .= 'FULLTEXT ' . substr($key, 9) . ' (' . implode(', ', $columns) . ')'; +			} +			else +			{ +				$line .= "KEY $key (" . implode(', ', $columns) . ')'; +			} + +			$rows[] = $line; +		} + +		$sql_data .= implode(",\n", $rows); +		$sql_data .= "\n);\n\n"; + +		$this->flush($sql_data); +	} +} diff --git a/phpBB/phpbb/db/extractor/oracle_extractor.php b/phpBB/phpbb/db/extractor/oracle_extractor.php new file mode 100644 index 0000000000..79a991889b --- /dev/null +++ b/phpBB/phpbb/db/extractor/oracle_extractor.php @@ -0,0 +1,263 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class oracle_extractor extends base_extractor +{ +	/** +	* {@inheritdoc} +	*/ +	public function write_table($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql_data = '-- Table: ' . $table_name . "\n"; +		$sql_data .= "DROP TABLE $table_name\n/\n"; +		$sql_data .= "\nCREATE TABLE $table_name (\n"; + +		$sql = "SELECT COLUMN_NAME, DATA_TYPE, DATA_PRECISION, DATA_LENGTH, NULLABLE, DATA_DEFAULT +			FROM ALL_TAB_COLS +			WHERE table_name = '{$table_name}'"; +		$result = $this->db->sql_query($sql); + +		$rows = array(); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$line = '  "' . $row['column_name'] . '" ' . $row['data_type']; + +			if ($row['data_type'] !== 'CLOB') +			{ +				if ($row['data_type'] !== 'VARCHAR2' && $row['data_type'] !== 'CHAR') +				{ +					$line .= '(' . $row['data_precision'] . ')'; +				} +				else +				{ +					$line .= '(' . $row['data_length'] . ')'; +				} +			} + +			if (!empty($row['data_default'])) +			{ +				$line .= ' DEFAULT ' . $row['data_default']; +			} + +			if ($row['nullable'] == 'N') +			{ +				$line .= ' NOT NULL'; +			} +			$rows[] = $line; +		} +		$this->db->sql_freeresult($result); + +		$sql = "SELECT A.CONSTRAINT_NAME, A.COLUMN_NAME +			FROM USER_CONS_COLUMNS A, USER_CONSTRAINTS B +			WHERE A.CONSTRAINT_NAME = B.CONSTRAINT_NAME +				AND B.CONSTRAINT_TYPE = 'P' +				AND A.TABLE_NAME = '{$table_name}'"; +		$result = $this->db->sql_query($sql); + +		$primary_key = array(); +		$constraint_name = ''; +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$constraint_name = '"' . $row['constraint_name'] . '"'; +			$primary_key[] = '"' . $row['column_name'] . '"'; +		} +		$this->db->sql_freeresult($result); + +		if (sizeof($primary_key)) +		{ +			$rows[] = "  CONSTRAINT {$constraint_name} PRIMARY KEY (" . implode(', ', $primary_key) . ')'; +		} + +		$sql = "SELECT A.CONSTRAINT_NAME, A.COLUMN_NAME +			FROM USER_CONS_COLUMNS A, USER_CONSTRAINTS B +			WHERE A.CONSTRAINT_NAME = B.CONSTRAINT_NAME +				AND B.CONSTRAINT_TYPE = 'U' +				AND A.TABLE_NAME = '{$table_name}'"; +		$result = $this->db->sql_query($sql); + +		$unique = array(); +		$constraint_name = ''; +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$constraint_name = '"' . $row['constraint_name'] . '"'; +			$unique[] = '"' . $row['column_name'] . '"'; +		} +		$this->db->sql_freeresult($result); + +		if (sizeof($unique)) +		{ +			$rows[] = "  CONSTRAINT {$constraint_name} UNIQUE (" . implode(', ', $unique) . ')'; +		} + +		$sql_data .= implode(",\n", $rows); +		$sql_data .= "\n)\n/\n"; + +		$sql = "SELECT A.REFERENCED_NAME, C.* +			FROM USER_DEPENDENCIES A, USER_TRIGGERS B, USER_SEQUENCES C +			WHERE A.REFERENCED_TYPE = 'SEQUENCE' +				AND A.NAME = B.TRIGGER_NAME +				AND B.TABLE_NAME = '{$table_name}' +				AND C.SEQUENCE_NAME = A.REFERENCED_NAME"; +		$result = $this->db->sql_query($sql); + +		$type = $this->request->variable('type', ''); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$sql_data .= "\nDROP SEQUENCE \"{$row['referenced_name']}\"\n/\n"; +			$sql_data .= "\nCREATE SEQUENCE \"{$row['referenced_name']}\""; + +			if ($type == 'full') +			{ +				$sql_data .= ' START WITH ' . $row['last_number']; +			} + +			$sql_data .= "\n/\n"; +		} +		$this->db->sql_freeresult($result); + +		$sql = "SELECT DESCRIPTION, WHEN_CLAUSE, TRIGGER_BODY +			FROM USER_TRIGGERS +			WHERE TABLE_NAME = '{$table_name}'"; +		$result = $this->db->sql_query($sql); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$sql_data .= "\nCREATE OR REPLACE TRIGGER {$row['description']}WHEN ({$row['when_clause']})\n{$row['trigger_body']}\n/\n"; +		} +		$this->db->sql_freeresult($result); + +		$sql = "SELECT A.INDEX_NAME, B.COLUMN_NAME +			FROM USER_INDEXES A, USER_IND_COLUMNS B +			WHERE A.UNIQUENESS = 'NONUNIQUE' +				AND A.INDEX_NAME = B.INDEX_NAME +				AND B.TABLE_NAME = '{$table_name}'"; +		$result = $this->db->sql_query($sql); + +		$index = array(); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$index[$row['index_name']][] = $row['column_name']; +		} + +		foreach ($index as $index_name => $column_names) +		{ +			$sql_data .= "\nCREATE INDEX $index_name ON $table_name(" . implode(', ', $column_names) . ")\n/\n"; +		} +		$this->db->sql_freeresult($result); +		$this->flush($sql_data); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_data($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$ary_type = $ary_name = array(); + +		// Grab all of the data from current table. +		$sql = "SELECT * +			FROM $table_name"; +		$result = $this->db->sql_query($sql); + +		$i_num_fields = ocinumcols($result); + +		for ($i = 0; $i < $i_num_fields; $i++) +		{ +			$ary_type[$i] = ocicolumntype($result, $i + 1); +			$ary_name[$i] = ocicolumnname($result, $i + 1); +		} + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$schema_vals = $schema_fields = array(); + +			// Build the SQL statement to recreate the data. +			for ($i = 0; $i < $i_num_fields; $i++) +			{ +				// Oracle uses uppercase - we use lowercase +				$str_val = $row[strtolower($ary_name[$i])]; + +				if (preg_match('#char|text|bool|raw|clob#i', $ary_type[$i])) +				{ +					$str_quote = ''; +					$str_empty = "''"; +					$str_val = sanitize_data_oracle($str_val); +				} +				else if (preg_match('#date|timestamp#i', $ary_type[$i])) +				{ +					if (empty($str_val)) +					{ +						$str_quote = ''; +					} +					else +					{ +						$str_quote = "'"; +					} +				} +				else +				{ +					$str_quote = ''; +					$str_empty = 'NULL'; +				} + +				if (empty($str_val) && $str_val !== '0') +				{ +					$str_val = $str_empty; +				} + +				$schema_vals[$i] = $str_quote . $str_val . $str_quote; +				$schema_fields[$i] = '"' . $ary_name[$i] . '"'; +			} + +			// Take the ordered fields and their associated data and build it +			// into a valid sql statement to recreate that field in the data. +			$sql_data = "INSERT INTO $table_name (" . implode(', ', $schema_fields) . ') VALUES (' . implode(', ', $schema_vals) . ")\n/\n"; + +			$this->flush($sql_data); +		} +		$this->db->sql_freeresult($result); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_start($table_prefix) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql_data = "--\n"; +		$sql_data .= "-- phpBB Backup Script\n"; +		$sql_data .= "-- Dump of tables for $table_prefix\n"; +		$sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; +		$sql_data .= "--\n"; +		$this->flush($sql_data); +	} +} diff --git a/phpBB/phpbb/db/extractor/postgres_extractor.php b/phpBB/phpbb/db/extractor/postgres_extractor.php new file mode 100644 index 0000000000..a98e39621c --- /dev/null +++ b/phpBB/phpbb/db/extractor/postgres_extractor.php @@ -0,0 +1,339 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class postgres_extractor extends base_extractor +{ +	/** +	* {@inheritdoc} +	*/ +	public function write_start($table_prefix) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql_data = "--\n"; +		$sql_data .= "-- phpBB Backup Script\n"; +		$sql_data .= "-- Dump of tables for $table_prefix\n"; +		$sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; +		$sql_data .= "--\n"; +		$sql_data .= "BEGIN TRANSACTION;\n"; +		$this->flush($sql_data); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_table($table_name) +	{ +		static $domains_created = array(); + +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql = "SELECT a.domain_name, a.data_type, a.character_maximum_length, a.domain_default +			FROM INFORMATION_SCHEMA.domains a, INFORMATION_SCHEMA.column_domain_usage b +			WHERE a.domain_name = b.domain_name +				AND b.table_name = '{$table_name}'"; +		$result = $this->db->sql_query($sql); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if (empty($domains_created[$row['domain_name']])) +			{ +				$domains_created[$row['domain_name']] = true; +				//$sql_data = "DROP DOMAIN {$row['domain_name']};\n"; +				$sql_data = "CREATE DOMAIN {$row['domain_name']} as {$row['data_type']}"; +				if (!empty($row['character_maximum_length'])) +				{ +					$sql_data .= '(' . $row['character_maximum_length'] . ')'; +				} +				$sql_data .= ' NOT NULL'; +				if (!empty($row['domain_default'])) +				{ +					$sql_data .= ' DEFAULT ' . $row['domain_default']; +				} +				$this->flush($sql_data . ";\n"); +			} +		} +		$this->db->sql_freeresult($result); + +		$sql_data = '-- Table: ' . $table_name . "\n"; +		$sql_data .= "DROP TABLE $table_name;\n"; +		// PGSQL does not "tightly" bind sequences and tables, we must guess... +		$sql = "SELECT relname +			FROM pg_class +			WHERE relkind = 'S' +				AND relname = '{$table_name}_seq'"; +		$result = $this->db->sql_query($sql); +		// We don't even care about storing the results. We already know the answer if we get rows back. +		if ($this->db->sql_fetchrow($result)) +		{ +			$sql_data .= "DROP SEQUENCE {$table_name}_seq;\n"; +			$sql_data .= "CREATE SEQUENCE {$table_name}_seq;\n"; +		} +		$this->db->sql_freeresult($result); + +		$field_query = "SELECT a.attnum, a.attname as field, t.typname as type, a.attlen as length, a.atttypmod as lengthvar, a.attnotnull as notnull +			FROM pg_class c, pg_attribute a, pg_type t +			WHERE c.relname = '" . $this->db->sql_escape($table_name) . "' +				AND a.attnum > 0 +				AND a.attrelid = c.oid +				AND a.atttypid = t.oid +			ORDER BY a.attnum"; +		$result = $this->db->sql_query($field_query); + +		$sql_data .= "CREATE TABLE $table_name(\n"; +		$lines = array(); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			// Get the data from the table +			$sql_get_default = "SELECT pg_get_expr(d.adbin, d.adrelid) as rowdefault +				FROM pg_attrdef d, pg_class c +				WHERE (c.relname = '" . $this->db->sql_escape($table_name) . "') +					AND (c.oid = d.adrelid) +					AND d.adnum = " . $row['attnum']; +			$def_res = $this->db->sql_query($sql_get_default); +			$def_row = $this->db->sql_fetchrow($def_res); +			$this->db->sql_freeresult($def_res); + +			if (empty($def_row)) +			{ +				unset($row['rowdefault']); +			} +			else +			{ +				$row['rowdefault'] = $def_row['rowdefault']; +			} + +			if ($row['type'] == 'bpchar') +			{ +				// Internally stored as bpchar, but isn't accepted in a CREATE TABLE statement. +				$row['type'] = 'char'; +			} + +			$line = '  ' . $row['field'] . ' ' . $row['type']; + +			if (strpos($row['type'], 'char') !== false) +			{ +				if ($row['lengthvar'] > 0) +				{ +					$line .= '(' . ($row['lengthvar'] - 4) . ')'; +				} +			} + +			if (strpos($row['type'], 'numeric') !== false) +			{ +				$line .= '('; +				$line .= sprintf("%s,%s", (($row['lengthvar'] >> 16) & 0xffff), (($row['lengthvar'] - 4) & 0xffff)); +				$line .= ')'; +			} + +			if (isset($row['rowdefault'])) +			{ +				$line .= ' DEFAULT ' . $row['rowdefault']; +			} + +			if ($row['notnull'] == 't') +			{ +				$line .= ' NOT NULL'; +			} + +			$lines[] = $line; +		} +		$this->db->sql_freeresult($result); + +		// Get the listing of primary keys. +		$sql_pri_keys = "SELECT ic.relname as index_name, bc.relname as tab_name, ta.attname as column_name, i.indisunique as unique_key, i.indisprimary as primary_key +			FROM pg_class bc, pg_class ic, pg_index i, pg_attribute ta, pg_attribute ia +			WHERE (bc.oid = i.indrelid) +				AND (ic.oid = i.indexrelid) +				AND (ia.attrelid = i.indexrelid) +				AND	(ta.attrelid = bc.oid) +				AND (bc.relname = '" . $this->db->sql_escape($table_name) . "') +				AND (ta.attrelid = i.indrelid) +				AND (ta.attnum = i.indkey[ia.attnum-1]) +			ORDER BY index_name, tab_name, column_name"; + +		$result = $this->db->sql_query($sql_pri_keys); + +		$index_create = $index_rows = $primary_key = array(); + +		// We do this in two steps. It makes placing the comma easier +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if ($row['primary_key'] == 't') +			{ +				$primary_key[] = $row['column_name']; +				$primary_key_name = $row['index_name']; +			} +			else +			{ +				// We have to store this all this info because it is possible to have a multi-column key... +				// we can loop through it again and build the statement +				$index_rows[$row['index_name']]['table'] = $table_name; +				$index_rows[$row['index_name']]['unique'] = ($row['unique_key'] == 't') ? true : false; +				$index_rows[$row['index_name']]['column_names'][] = $row['column_name']; +			} +		} +		$this->db->sql_freeresult($result); + +		if (!empty($index_rows)) +		{ +			foreach ($index_rows as $idx_name => $props) +			{ +				$index_create[] = 'CREATE ' . ($props['unique'] ? 'UNIQUE ' : '') . "INDEX $idx_name ON $table_name (" . implode(', ', $props['column_names']) . ");"; +			} +		} + +		if (!empty($primary_key)) +		{ +			$lines[] = "  CONSTRAINT $primary_key_name PRIMARY KEY (" . implode(', ', $primary_key) . ")"; +		} + +		// Generate constraint clauses for CHECK constraints +		$sql_checks = "SELECT conname as index_name, consrc +			FROM pg_constraint, pg_class bc +			WHERE conrelid = bc.oid +				AND bc.relname = '" . $this->db->sql_escape($table_name) . "' +				AND NOT EXISTS ( +					SELECT * +						FROM pg_constraint as c, pg_inherits as i +						WHERE i.inhrelid = pg_constraint.conrelid +							AND c.conname = pg_constraint.conname +							AND c.consrc = pg_constraint.consrc +							AND c.conrelid = i.inhparent +				)"; +		$result = $this->db->sql_query($sql_checks); + +		// Add the constraints to the sql file. +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if (!is_null($row['consrc'])) +			{ +				$lines[] = '  CONSTRAINT ' . $row['index_name'] . ' CHECK ' . $row['consrc']; +			} +		} +		$this->db->sql_freeresult($result); + +		$sql_data .= implode(", \n", $lines); +		$sql_data .= "\n);\n"; + +		if (!empty($index_create)) +		{ +			$sql_data .= implode("\n", $index_create) . "\n\n"; +		} +		$this->flush($sql_data); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_data($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		// Grab all of the data from current table. +		$sql = "SELECT * +			FROM $table_name"; +		$result = $this->db->sql_query($sql); + +		$i_num_fields = pg_num_fields($result); +		$seq = ''; + +		for ($i = 0; $i < $i_num_fields; $i++) +		{ +			$ary_type[] = pg_field_type($result, $i); +			$ary_name[] = pg_field_name($result, $i); + +			$sql = "SELECT pg_get_expr(d.adbin, d.adrelid) as rowdefault +				FROM pg_attrdef d, pg_class c +				WHERE (c.relname = '{$table_name}') +					AND (c.oid = d.adrelid) +					AND d.adnum = " . strval($i + 1); +			$result2 = $this->db->sql_query($sql); +			if ($row = $this->db->sql_fetchrow($result2)) +			{ +				// Determine if we must reset the sequences +				if (strpos($row['rowdefault'], "nextval('") === 0) +				{ +					$seq .= "SELECT SETVAL('{$table_name}_seq',(select case when max({$ary_name[$i]})>0 then max({$ary_name[$i]})+1 else 1 end FROM {$table_name}));\n"; +				} +			} +		} + +		$this->flush("COPY $table_name (" . implode(', ', $ary_name) . ') FROM stdin;' . "\n"); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$schema_vals = array(); + +			// Build the SQL statement to recreate the data. +			for ($i = 0; $i < $i_num_fields; $i++) +			{ +				$str_val = $row[$ary_name[$i]]; + +				if (preg_match('#char|text|bool|bytea#i', $ary_type[$i])) +				{ +					$str_val = str_replace(array("\n", "\t", "\r", "\b", "\f", "\v"), array('\n', '\t', '\r', '\b', '\f', '\v'), addslashes($str_val)); +					$str_empty = ''; +				} +				else +				{ +					$str_empty = '\N'; +				} + +				if (empty($str_val) && $str_val !== '0') +				{ +					$str_val = $str_empty; +				} + +				$schema_vals[] = $str_val; +			} + +			// Take the ordered fields and their associated data and build it +			// into a valid sql statement to recreate that field in the data. +			$this->flush(implode("\t", $schema_vals) . "\n"); +		} +		$this->db->sql_freeresult($result); +		$this->flush("\\.\n"); + +		// Write out the sequence statements +		$this->flush($seq); +	} + +	/** +	* Writes closing line(s) to database backup +	* +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	public function write_end() +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$this->flush("COMMIT;\n"); +		parent::write_end(); +	} +} diff --git a/phpBB/phpbb/db/extractor/sqlite3_extractor.php b/phpBB/phpbb/db/extractor/sqlite3_extractor.php new file mode 100644 index 0000000000..ce8da6a652 --- /dev/null +++ b/phpBB/phpbb/db/extractor/sqlite3_extractor.php @@ -0,0 +1,151 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class sqlite3_extractor extends base_extractor +{ +	/** +	* {@inheritdoc} +	*/ +	public function write_start($table_prefix) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql_data = "--\n"; +		$sql_data .= "-- phpBB Backup Script\n"; +		$sql_data .= "-- Dump of tables for $table_prefix\n"; +		$sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; +		$sql_data .= "--\n"; +		$sql_data .= "BEGIN TRANSACTION;\n"; +		$this->flush($sql_data); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_table($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql_data = '-- Table: ' . $table_name . "\n"; +		$sql_data .= "DROP TABLE $table_name;\n"; + +		$sql = "SELECT sql +			FROM sqlite_master +			WHERE type = 'table' +				AND name = '" . $this->db->sql_escape($table_name) . "' +			ORDER BY name ASC;"; +		$result = $this->db->sql_query($sql); +		$row = $this->db->sql_fetchrow($result); +		$this->db->sql_freeresult($result); + +		// Create Table +		$sql_data .= $row['sql'] . ";\n"; + +		$result = $this->db->sql_query("PRAGMA index_list('" . $this->db->sql_escape($table_name) . "');"); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if (strpos($row['name'], 'autoindex') !== false) +			{ +				continue; +			} + +			$result2 = $this->db->sql_query("PRAGMA index_info('" . $this->db->sql_escape($row['name']) . "');"); + +			$fields = array(); +			while ($row2 = $this->db->sql_fetchrow($result2)) +			{ +				$fields[] = $row2['name']; +			} +			$this->db->sql_freeresult($result2); + +			$sql_data .= 'CREATE ' . ($row['unique'] ? 'UNIQUE ' : '') . 'INDEX ' . $row['name'] . ' ON ' . $table_name . ' (' . implode(', ', $fields) . ");\n"; +		} +		$this->db->sql_freeresult($result); + +		$this->flush($sql_data . "\n"); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_data($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$result = $this->db->sql_query("PRAGMA table_info('" . $this->db->sql_escape($table_name) . "');"); + +		$col_types = array(); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$col_types[$row['name']] = $row['type']; +		} +		$this->db->sql_freeresult($result); + +		$sql_insert = 'INSERT INTO ' . $table_name . ' (' . implode(', ', array_keys($col_types)) . ') VALUES ('; + +		$sql = "SELECT * +			FROM $table_name"; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			foreach ($row as $column_name => $column_data) +			{ +				if (is_null($column_data)) +				{ +					$row[$column_name] = 'NULL'; +				} +				else if ($column_data === '') +				{ +					$row[$column_name] = "''"; +				} +				else if (stripos($col_types[$column_name], 'text') !== false || stripos($col_types[$column_name], 'char') !== false || stripos($col_types[$column_name], 'blob') !== false) +				{ +					$row[$column_name] = sanitize_data_generic(str_replace("'", "''", $column_data)); +				} +			} +			$this->flush($sql_insert . implode(', ', $row) . ");\n"); +		} +	} + +	/** +	* Writes closing line(s) to database backup +	* +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	public function write_end() +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$this->flush("COMMIT;\n"); +		parent::write_end(); +	} +} diff --git a/phpBB/phpbb/db/extractor/sqlite_extractor.php b/phpBB/phpbb/db/extractor/sqlite_extractor.php new file mode 100644 index 0000000000..2734e23235 --- /dev/null +++ b/phpBB/phpbb/db/extractor/sqlite_extractor.php @@ -0,0 +1,149 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class sqlite_extractor extends base_extractor +{ +	/** +	* {@inheritdoc} +	*/ +	public function write_start($table_prefix) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql_data = "--\n"; +		$sql_data .= "-- phpBB Backup Script\n"; +		$sql_data .= "-- Dump of tables for $table_prefix\n"; +		$sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; +		$sql_data .= "--\n"; +		$sql_data .= "BEGIN TRANSACTION;\n"; +		$this->flush($sql_data); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_table($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql_data = '-- Table: ' . $table_name . "\n"; +		$sql_data .= "DROP TABLE $table_name;\n"; + +		$sql = "SELECT sql +			FROM sqlite_master +			WHERE type = 'table' +				AND name = '" . $this->db->sql_escape($table_name) . "' +			ORDER BY type DESC, name;"; +		$result = $this->db->sql_query($sql); +		$row = $this->db->sql_fetchrow($result); +		$this->db->sql_freeresult($result); + +		// Create Table +		$sql_data .= $row['sql'] . ";\n"; + +		$result = $this->db->sql_query("PRAGMA index_list('" . $this->db->sql_escape($table_name) . "');"); + +		$ar = array(); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$ar[] = $row; +		} +		$this->db->sql_freeresult($result); + +		foreach ($ar as $value) +		{ +			if (strpos($value['name'], 'autoindex') !== false) +			{ +				continue; +			} + +			$result = $this->db->sql_query("PRAGMA index_info('" . $this->db->sql_escape($value['name']) . "');"); + +			$fields = array(); +			while ($row = $this->db->sql_fetchrow($result)) +			{ +				$fields[] = $row['name']; +			} +			$this->db->sql_freeresult($result); + +			$sql_data .= 'CREATE ' . ($value['unique'] ? 'UNIQUE ' : '') . 'INDEX ' . $value['name'] . ' on ' . $table_name . ' (' . implode(', ', $fields) . ");\n"; +		} + +		$this->flush($sql_data . "\n"); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_data($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$col_types = sqlite_fetch_column_types($this->db->get_db_connect_id(), $table_name); + +		$sql = "SELECT * +			FROM $table_name"; +		$result = sqlite_unbuffered_query($this->db->get_db_connect_id(), $sql); +		$rows = sqlite_fetch_all($result, SQLITE_ASSOC); +		$sql_insert = 'INSERT INTO ' . $table_name . ' (' . implode(', ', array_keys($col_types)) . ') VALUES ('; +		foreach ($rows as $row) +		{ +			foreach ($row as $column_name => $column_data) +			{ +				if (is_null($column_data)) +				{ +					$row[$column_name] = 'NULL'; +				} +				else if ($column_data == '') +				{ +					$row[$column_name] = "''"; +				} +				else if (strpos($col_types[$column_name], 'text') !== false || strpos($col_types[$column_name], 'char') !== false || strpos($col_types[$column_name], 'blob') !== false) +				{ +					$row[$column_name] = sanitize_data_generic(str_replace("'", "''", $column_data)); +				} +			} +			$this->flush($sql_insert . implode(', ', $row) . ");\n"); +		} +	} + +	/** +	* Writes closing line(s) to database backup +	* +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	public function write_end() +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$this->flush("COMMIT;\n"); +		parent::write_end(); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v30x/release_3_0_5_rc1.php b/phpBB/phpbb/db/migration/data/v30x/release_3_0_5_rc1.php index 003ccf8f18..084d00a13a 100644 --- a/phpBB/phpbb/db/migration/data/v30x/release_3_0_5_rc1.php +++ b/phpBB/phpbb/db/migration/data/v30x/release_3_0_5_rc1.php @@ -57,7 +57,9 @@ class release_3_0_5_rc1 extends container_aware_migration  	public function hash_old_passwords()  	{ +		/* @var $passwords_manager \phpbb\passwords\manager */  		$passwords_manager = $this->container->get('passwords.manager'); +  		$sql = 'SELECT user_id, user_password  				FROM ' . $this->table_prefix . 'users  				WHERE user_pass_convert = 1'; @@ -116,7 +118,7 @@ class release_3_0_5_rc1 extends container_aware_migration  				$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); +				$this->db->sql_fetchrow($result);  				while ($row = $this->db->sql_fetchrow($result))  				{ diff --git a/phpBB/phpbb/db/migration/data/v30x/release_3_0_9_rc1.php b/phpBB/phpbb/db/migration/data/v30x/release_3_0_9_rc1.php index 06e46d522f..5f928df47c 100644 --- a/phpBB/phpbb/db/migration/data/v30x/release_3_0_9_rc1.php +++ b/phpBB/phpbb/db/migration/data/v30x/release_3_0_9_rc1.php @@ -34,7 +34,7 @@ class release_3_0_9_rc1 extends \phpbb\db\migration\migration  						// 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 +						// soon as the \phpbb\db\tools\tools class is capable of properly  						// removing a primary key.  						// 'attempt_id'			=> array('UINT', NULL, 'auto_increment'),  						'attempt_ip'			=> array('VCHAR:40', ''), diff --git a/phpBB/phpbb/db/migration/data/v310/acp_prune_users_module.php b/phpBB/phpbb/db/migration/data/v310/acp_prune_users_module.php index 0ca4f2f19c..725c57ca86 100644 --- a/phpBB/phpbb/db/migration/data/v310/acp_prune_users_module.php +++ b/phpBB/phpbb/db/migration/data/v310/acp_prune_users_module.php @@ -13,7 +13,7 @@  namespace phpbb\db\migration\data\v310; -class acp_prune_users_module extends \phpbb\db\migration\migration +class acp_prune_users_module extends \phpbb\db\migration\container_aware_migration  {  	public function effectively_installed()  	{ @@ -70,12 +70,7 @@ class acp_prune_users_module extends \phpbb\db\migration\migration  		$acp_cat_users_id = (int) $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); -		} -		$module_manager = new \acp_modules(); -		$module_manager->module_class = 'acp'; -		$module_manager->move_module($acp_prune_users_id, $acp_cat_users_id); +		$module_manager = $this->container->get('module.manager'); +		$module_manager->move_module($acp_prune_users_id, $acp_cat_users_id, 'acp');  	}  } diff --git a/phpBB/phpbb/db/migration/data/v310/dev.php b/phpBB/phpbb/db/migration/data/v310/dev.php index f037191c2a..250258eea7 100644 --- a/phpBB/phpbb/db/migration/data/v310/dev.php +++ b/phpBB/phpbb/db/migration/data/v310/dev.php @@ -13,7 +13,7 @@  namespace phpbb\db\migration\data\v310; -class dev extends \phpbb\db\migration\migration +class dev extends \phpbb\db\migration\container_aware_migration  {  	public function effectively_installed()  	{ @@ -204,18 +204,13 @@ class dev extends \phpbb\db\migration\migration  		$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); +		$module_manager = $this->container->get('module.manager'); +		$module_manager->move_module($language_module_id, $language_management_module_id, 'acp');  	}  	public function update_ucp_pm_basename() diff --git a/phpBB/phpbb/db/migration/data/v31x/update_custom_bbcodes_with_idn.php b/phpBB/phpbb/db/migration/data/v31x/update_custom_bbcodes_with_idn.php index 854ed1f568..14b7b7b0f6 100644 --- a/phpBB/phpbb/db/migration/data/v31x/update_custom_bbcodes_with_idn.php +++ b/phpBB/phpbb/db/migration/data/v31x/update_custom_bbcodes_with_idn.php @@ -45,7 +45,6 @@ class update_custom_bbcodes_with_idn extends \phpbb\db\migration\migration  		$sql_ary = array();  		while ($row = $this->db->sql_fetchrow($result))  		{ -			$data = array();  			if (preg_match('/(URL|LOCAL_URL|RELATIVE_URL)/', $row['bbcode_match']))  			{  				$data = $bbcodes->build_regexp($row['bbcode_match'], $row['bbcode_tpl']); diff --git a/phpBB/phpbb/db/migration/data/v320/allowed_schemes_links.php b/phpBB/phpbb/db/migration/data/v320/allowed_schemes_links.php new file mode 100644 index 0000000000..726822bc71 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/allowed_schemes_links.php @@ -0,0 +1,31 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class allowed_schemes_links extends \phpbb\db\migration\migration +{ +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v320\dev', +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.add', array('allowed_schemes_links', 'http,https,ftp')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/announce_global_permission.php b/phpBB/phpbb/db/migration/data/v320/announce_global_permission.php new file mode 100644 index 0000000000..7afecb884b --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/announce_global_permission.php @@ -0,0 +1,43 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class announce_global_permission extends \phpbb\db\migration\migration +{ +	public function effectively_installed() +	{ +		$sql = 'SELECT auth_option_id +			FROM ' . ACL_OPTIONS_TABLE . " +			WHERE auth_option = 'f_announce_global'"; +		$result = $this->db->sql_query($sql); +		$auth_option_id = $this->db->sql_fetchfield('auth_option_id'); +		$this->db->sql_freeresult($result); + +		return $auth_option_id !== false; +	} + +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v320\dev', +		); +	} + +	public function update_data() +	{ +		return array( +			array('permission.add', array('f_announce_global', false, 'f_announce')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/default_data_type_ids.php b/phpBB/phpbb/db/migration/data/v320/default_data_type_ids.php new file mode 100644 index 0000000000..65e5b3fa73 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/default_data_type_ids.php @@ -0,0 +1,361 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class default_data_type_ids extends \phpbb\db\migration\migration +{ +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v320\v320a2', +			'\phpbb\db\migration\data\v320\oauth_states', +		); +	} + +	public function update_schema() +	{ +		return array( +			'change_columns'	=> array( +				$this->table_prefix . 'acl_users'			=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'attachments'			=> array( +					'attach_id'		=> array('ULINT', null, 'auto_increment'), +					'post_msg_id'	=> array('ULINT', 0), +					'poster_id'		=> array('ULINT', 0), +					'topic_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'banlist'				=> array( +					'ban_id'		=> array('ULINT', null, 'auto_increment'), +					'ban_userid'	=> array('ULINT', 0), +				), +				$this->table_prefix . 'bookmarks'			=> array( +					'topic_id'		=> array('ULINT', 0), +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'bots'				=> array( +					'bot_id'		=> array('ULINT', null, 'auto_increment'), +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'drafts'				=> array( +					'draft_id'		=> array('ULINT', null, 'auto_increment'), +					'user_id'		=> array('ULINT', 0), +					'topic_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'forums'				=> array( +					'forum_last_post_id'	=> array('ULINT', 0), +					'forum_last_poster_id'	=> array('ULINT', 0), +				), +				$this->table_prefix . 'forums_access'		=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'forums_track'		=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'forums_watch'		=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'log'					=> array( +					'log_id'		=> array('ULINT', null, 'auto_increment'), +					'post_id'		=> array('ULINT', 0), +					'reportee_id'	=> array('ULINT', 0), +					'user_id'		=> array('ULINT', 0), +					'topic_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'login_attempts'		=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'moderator_cache'		=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'notifications'		=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'oauth_accounts'		=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'oauth_states'		=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'oauth_tokens'		=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'poll_options'		=> array( +					'topic_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'poll_votes'			=> array( +					'topic_id'		=> array('ULINT', 0), +					'vote_user_id'	=> array('ULINT', 0), +				), +				$this->table_prefix . 'posts'				=> array( +					'post_id'			=> array('ULINT', null, 'auto_increment'), +					'poster_id'			=> array('ULINT', 0), +					'post_delete_user'	=> array('ULINT', 0), +					'post_edit_user'	=> array('ULINT', 0), +					'topic_id'			=> array('ULINT', 0), +				), +				$this->table_prefix . 'privmsgs'			=> array( +					'author_id'			=> array('ULINT', 0), +					'message_edit_user'	=> array('ULINT', 0), +					'msg_id'			=> array('ULINT', null, 'auto_increment'), +				), +				$this->table_prefix . 'privmsgs_folder'		=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'privmsgs_rules'		=> array( +					'rule_user_id'	=> array('ULINT', 0), +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'privmsgs_to'			=> array( +					'author_id'		=> array('ULINT', 0), +					'msg_id'		=> array('ULINT', 0), +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'profile_fields_data'	=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'reports'				=> array( +					'report_id'		=> array('ULINT', 0), +					'pm_id'			=> array('ULINT', 0), +					'post_id'		=> array('ULINT', 0), +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'search_wordlist'		=> array( +					'word_id'		=> array('ULINT', null, 'auto_increment'), +				), +				$this->table_prefix . 'search_wordmatch'	=> array( +					'post_id'		=> array('ULINT', 0), +					'word_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'sessions'			=> array( +					'session_user_id'	=> array('ULINT', 0), +				), +				$this->table_prefix . 'sessions_keys'		=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'topics'				=> array( +					'topic_id'				=> array('ULINT', null, 'auto_increment'), +					'topic_poster'			=> array('ULINT', 0), +					'topic_first_post_id'	=> array('ULINT', 0), +					'topic_last_post_id'	=> array('ULINT', 0), +					'topic_last_poster_id'	=> array('ULINT', 0), +					'topic_moved_id'		=> array('ULINT', 0), +					'topic_delete_user'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'topics_track'		=> array( +					'user_id'		=> array('ULINT', 0), +					'topic_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'topics_posted'		=> array( +					'user_id'		=> array('ULINT', 0), +					'topic_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'topics_watch'		=> array( +					'user_id'		=> array('ULINT', 0), +					'topic_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'user_notifications'	=> array( +					'item_id'		=> array('ULINT', 0), +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'user_group'			=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'users'				=> array( +					'user_id'		=> array('ULINT', null, 'auto_increment'), +				), +				$this->table_prefix . 'warnings'			=> array( +					'log_id'		=> array('ULINT', 0), +					'user_id'		=> array('ULINT', 0), +					'post_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'words'				=> array( +					'word_id'		=> array('ULINT', null, 'auto_increment'), +				), +				$this->table_prefix . 'zebra'			=> array( +					'user_id'		=> array('ULINT', 0), +					'zebra_id'		=> array('ULINT', 0), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'change_columns'	=> array( +				$this->table_prefix . 'acl_users'			=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'attachments'			=> array( +					'attach_id'		=> array('UINT', null, 'auto_increment'), +					'post_msg_id'	=> array('UINT', 0), +					'poster_id'		=> array('UINT', 0), +					'topic_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'banlist'				=> array( +					'ban_id'		=> array('UINT', null, 'auto_increment'), +					'ban_userid'	=> array('UINT', 0), +				), +				$this->table_prefix . 'bookmarks'			=> array( +					'topic_id'		=> array('UINT', 0), +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'bots'				=> array( +					'bot_id'		=> array('UINT', null, 'auto_increment'), +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'drafts'				=> array( +					'draft_id'		=> array('UINT', null, 'auto_increment'), +					'user_id'		=> array('UINT', 0), +					'topic_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'forums'				=> array( +					'forum_last_post_id'	=> array('UINT', 0), +					'forum_last_poster_id'	=> array('UINT', 0), +				), +				$this->table_prefix . 'forums_access'		=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'forums_track'		=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'forums_watch'		=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'log'					=> array( +					'log_id'		=> array('UINT', null, 'auto_increment'), +					'post_id'		=> array('UINT', 0), +					'reportee_id'	=> array('UINT', 0), +					'user_id'		=> array('UINT', 0), +					'topic_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'login_attempts'		=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'moderator_cache'		=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'notifications'		=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'oauth_accounts'		=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'oauth_states'		=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'oauth_tokens'		=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'poll_options'		=> array( +					'topic_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'poll_votes'			=> array( +					'topic_id'		=> array('UINT', 0), +					'vote_user_id'	=> array('UINT', 0), +				), +				$this->table_prefix . 'posts'				=> array( +					'post_id'			=> array('UINT', null, 'auto_increment'), +					'poster_id'			=> array('UINT', 0), +					'post_delete_user'	=> array('UINT', 0), +					'post_edit_user'	=> array('UINT', 0), +					'topic_id'			=> array('UINT', 0), +				), +				$this->table_prefix . 'privmsgs'			=> array( +					'author_id'			=> array('UINT', 0), +					'message_edit_user'	=> array('UINT', 0), +					'msg_id'			=> array('UINT', null, 'auto_increment'), +				), +				$this->table_prefix . 'privmsgs_folder'		=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'privmsgs_rules'		=> array( +					'rule_user_id'	=> array('UINT', 0), +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'privmsgs_to'			=> array( +					'author_id'		=> array('UINT', 0), +					'msg_id'		=> array('UINT', 0), +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'profile_fields_data'	=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'reports'				=> array( +					'report_id'		=> array('UINT', 0), +					'pm_id'			=> array('UINT', 0), +					'post_id'		=> array('UINT', 0), +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'search_wordlist'		=> array( +					'word_id'		=> array('UINT', null, 'auto_increment'), +				), +				$this->table_prefix . 'search_wordmatch'	=> array( +					'post_id'		=> array('UINT', 0), +					'word_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'sessions'			=> array( +					'session_user_id'	=> array('UINT', 0), +				), +				$this->table_prefix . 'sessions_keys'		=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'topics'				=> array( +					'topic_id'				=> array('UINT', null, 'auto_increment'), +					'topic_poster'			=> array('UINT', 0), +					'topic_first_post_id'	=> array('UINT', 0), +					'topic_last_post_id'	=> array('UINT', 0), +					'topic_last_poster_id'	=> array('UINT', 0), +					'topic_moved_id'		=> array('UINT', 0), +					'topic_delete_user'		=> array('UINT', 0), +				), +				$this->table_prefix . 'topics_track'		=> array( +					'user_id'		=> array('UINT', 0), +					'topic_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'topics_posted'		=> array( +					'user_id'		=> array('UINT', 0), +					'topic_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'topics_watch'		=> array( +					'user_id'		=> array('UINT', 0), +					'topic_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'user_notifications'	=> array( +					'item_id'		=> array('UINT', 0), +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'user_group'			=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'users'				=> array( +					'user_id'		=> array('UINT', null, 'auto_increment'), +				), +				$this->table_prefix . 'warnings'			=> array( +					'log_id'		=> array('UINT', 0), +					'user_id'		=> array('UINT', 0), +					'post_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'words'				=> array( +					'word_id'		=> array('UINT', null, 'auto_increment'), +				), +				$this->table_prefix . 'zebra'			=> array( +					'user_id'		=> array('UINT', 0), +					'zebra_id'		=> array('UINT', 0), +				), +			), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/dev.php b/phpBB/phpbb/db/migration/data/v320/dev.php new file mode 100644 index 0000000000..ad2da3c1f4 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/dev.php @@ -0,0 +1,36 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class dev extends \phpbb\db\migration\container_aware_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.2.0-dev', '>='); +	} + +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v31x\v316', +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.2.0-dev')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/font_awesome_update.php b/phpBB/phpbb/db/migration/data/v320/font_awesome_update.php new file mode 100644 index 0000000000..817b638037 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/font_awesome_update.php @@ -0,0 +1,36 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class font_awesome_update extends \phpbb\db\migration\migration +{ +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v320\dev', +		); +	} + +	public function effectively_installed() +	{ +		return isset($this->config['load_font_awesome_url']); +	} + +	public function update_data() +	{ +		return array( +			array('config.add', array('load_font_awesome_url', 'https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/icons_alt.php b/phpBB/phpbb/db/migration/data/v320/icons_alt.php new file mode 100644 index 0000000000..80132e579e --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/icons_alt.php @@ -0,0 +1,46 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class icons_alt extends \phpbb\db\migration\migration +{ +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v320\dev', +		); +	} + +	public function update_schema() +	{ +		return array( +			'add_columns'        => array( +				$this->table_prefix . 'icons'        => array( +					'icons_alt'    => array('VCHAR', '', 'after' => 'icons_height'), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'drop_columns'        => array( +				$this->table_prefix . 'icons'        => array( +					'icons_alt', +				), +			), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/log_post_id.php b/phpBB/phpbb/db/migration/data/v320/log_post_id.php new file mode 100644 index 0000000000..ead53c8138 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/log_post_id.php @@ -0,0 +1,46 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class log_post_id extends \phpbb\db\migration\migration +{ +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v320\dev', +		); +	} + +	public function update_schema() +	{ +		return array( +			'add_columns'        => array( +				$this->table_prefix . 'log'        => array( +					'post_id'    => array('UINT', 0, 'after' => 'topic_id'), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'drop_columns'        => array( +				$this->table_prefix . 'log'        => array( +					'post_id', +				), +			), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/notifications_board.php b/phpBB/phpbb/db/migration/data/v320/notifications_board.php new file mode 100644 index 0000000000..ac1b3a0f2c --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/notifications_board.php @@ -0,0 +1,75 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class notifications_board extends \phpbb\db\migration\migration +{ +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v320\dev', +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.add', array('allow_board_notifications', 1)), +			array('custom', array(array($this, 'update_user_subscriptions'))), +			array('custom', array(array($this, 'update_module'))), +		); +	} + +	public function update_module() +	{ +		$sql = 'UPDATE ' . MODULES_TABLE . " +			SET module_auth = 'cfg_allow_board_notifications' +			WHERE module_basename = 'ucp_notifications' +				AND module_mode = 'notification_list'"; +		$this->sql_query($sql); +	} + +	public function update_user_subscriptions() +	{ +		$sql = 'UPDATE ' . USER_NOTIFICATIONS_TABLE . " +			SET method = 'notification.method.board' +			WHERE method = ''"; +		$this->sql_query($sql); +	} + +	public function revert_data() +	{ +		return array( +			array('custom', array(array($this, 'revert_user_subscriptions'))), +			array('custom', array(array($this, 'revert_module'))), +		); +	} + +	public function revert_user_subscriptions() +	{ +		$sql = 'UPDATE ' . USER_NOTIFICATIONS_TABLE . " +			SET method = '' +			WHERE method = 'notification.method.board'"; +		$this->sql_query($sql); +	} + +	public function revert_module() +	{ +		$sql = 'UPDATE ' . MODULES_TABLE . " +			SET auth = '' +			WHERE module_basename = 'ucp_notifications' +				AND module_mode = 'notification_list'"; +		$this->sql_query($sql); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/oauth_states.php b/phpBB/phpbb/db/migration/data/v320/oauth_states.php new file mode 100644 index 0000000000..22ab2dabb3 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/oauth_states.php @@ -0,0 +1,56 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class oauth_states extends \phpbb\db\migration\migration +{ +	static public function depends_on() +	{ +		return array('\phpbb\db\migration\data\v310\auth_provider_oauth'); +	} + +	public function effectively_installed() +	{ +		return $this->db_tools->sql_table_exists($this->table_prefix . 'oauth_states'); +	} + +	public function update_schema() +	{ +		return array( +			'add_tables'	=> array( +				$this->table_prefix . 'oauth_states'	=> array( +					'COLUMNS' => array( +						'user_id'			=> array('UINT', 0), +						'session_id'		=> array('CHAR:32', ''), +						'provider'			=> array('VCHAR', ''), +						'oauth_state'		=> array('VCHAR', ''), +					), +					'KEYS' => array( +						'user_id'			=> array('INDEX', 'user_id'), +						'provider'			=> array('INDEX', 'provider'), +					), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'drop_tables'	=> array( +				$this->table_prefix . 'oauth_states', +			), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/remote_upload_validation.php b/phpBB/phpbb/db/migration/data/v320/remote_upload_validation.php new file mode 100644 index 0000000000..d61f6b96fd --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/remote_upload_validation.php @@ -0,0 +1,31 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class remote_upload_validation extends \phpbb\db\migration\migration +{ +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v320\v320a2', +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.add', array('remote_upload_verify', '0')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/remove_outdated_media.php b/phpBB/phpbb/db/migration/data/v320/remove_outdated_media.php new file mode 100644 index 0000000000..c14d31f1c0 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/remove_outdated_media.php @@ -0,0 +1,90 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class remove_outdated_media extends \phpbb\db\migration\migration +{ +	protected $cat_id = array( +			ATTACHMENT_CATEGORY_WM, +			ATTACHMENT_CATEGORY_RM, +			ATTACHMENT_CATEGORY_QUICKTIME, +		); + +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v320\dev', +		); +	} + +	public function update_data() +	{ +		return array( +			array('custom', array(array($this, 'change_extension_group'))), +		); +	} + +	public function change_extension_group() +	{ +		// select group ids of outdated media +		$sql = 'SELECT group_id +			FROM ' . EXTENSION_GROUPS_TABLE . ' +			WHERE ' . $this->db->sql_in_set('cat_id', $this->cat_id); +		$result = $this->db->sql_query($sql); + +		$group_ids = array(); +		while ($group_id = (int) $this->db->sql_fetchfield('group_id')) +		{ +			$group_ids[] = $group_id; +		} +		$this->db->sql_freeresult($result); + +		// nothing to do, admin has removed all the outdated media extension groups +		if (empty($group_ids)) +		{ +			return true; +		} + +		// get the group id of downloadable files +		$sql = 'SELECT group_id +			FROM ' . EXTENSION_GROUPS_TABLE . " +			WHERE group_name = 'DOWNLOADABLE_FILES'"; +		$result = $this->db->sql_query($sql); +		$download_id = (int) $this->db->sql_fetchfield('group_id'); +		$this->db->sql_freeresult($result); + +		if (empty($download_id)) +		{ +			$sql = 'UPDATE ' . EXTENSIONS_TABLE . ' +				SET group_id = 0 +				WHERE ' . $this->db->sql_in_set('group_id', $group_ids); +		} +		else +		{ +			// move outdated media extensions to downloadable files +			$sql = 'UPDATE ' . EXTENSIONS_TABLE . " +				SET group_id = $download_id" . ' +				WHERE ' . $this->db->sql_in_set('group_id', $group_ids); +		} + +		$result = $this->db->sql_query($sql); +		$this->db->sql_freeresult($result); + +		// delete the now empty, outdated media extension groups +		$sql = 'DELETE FROM ' . EXTENSION_GROUPS_TABLE . ' +			WHERE ' . $this->db->sql_in_set('group_id', $group_ids); +		$result = $this->db->sql_query($sql); +		$this->db->sql_freeresult($result); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/remove_profilefield_wlm.php b/phpBB/phpbb/db/migration/data/v320/remove_profilefield_wlm.php new file mode 100644 index 0000000000..1cb9070bf9 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/remove_profilefield_wlm.php @@ -0,0 +1,152 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class remove_profilefield_wlm extends \phpbb\db\migration\migration +{ +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v320\dev', +		); +	} + +	public function update_schema() +	{ +		return array( +			'drop_columns'	=> array( +				$this->table_prefix . 'profile_fields_data'			=> array( +					'pf_phpbb_wlm', +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'add_columns'	=> array( +				$this->table_prefix . 'profile_fields_data'			=> array( +					'pf_phpbb_wlm'		=> array('VCHAR', ''), +				), +			), +		); +	} + +	public function update_data() +	{ +		return array( +			array('custom', array(array($this, 'delete_custom_profile_field_data'))), +		); +	} + +	public function revert_data() +	{ +		return array( +			array('custom', array(array($this, 'create_custom_field'))), +		); +	} + +	public function delete_custom_profile_field_data() +	{ +		$field_id = $this->get_custom_profile_field_id(); + +		$sql = 'DELETE FROM ' . PROFILE_FIELDS_TABLE . ' +			WHERE field_id = ' . (int) $field_id; +		$this->db->sql_query($sql); + +		$sql = 'DELETE FROM ' . PROFILE_LANG_TABLE . ' +			WHERE field_id = ' . (int) $field_id; +		$this->db->sql_query($sql); + +		$sql = 'DELETE FROM ' . PROFILE_FIELDS_LANG_TABLE . ' +			WHERE field_id = ' . (int) $field_id; +		$this->db->sql_query($sql); +	} + +	/** +	 * Get custom profile field id +	 * @return	int	custom profile filed id +	 */ +	public function get_custom_profile_field_id() +	{ +		$sql = 'SELECT field_id +			FROM ' . PROFILE_FIELDS_TABLE . " +			WHERE field_name = 'phpbb_wlm'"; +		$result = $this->db->sql_query($sql); +		$field_id = (int) $this->db->sql_fetchfield('field_id'); +		$this->db->sql_freeresult($result); + +		return $field_id; +	} + +	public function create_custom_field() +	{ +		$sql = 'SELECT MAX(field_order) as max_field_order +			FROM ' . PROFILE_FIELDS_TABLE; +		$result = $this->db->sql_query($sql); +		$max_field_order = (int) $this->db->sql_fetchfield('max_field_order'); +		$this->db->sql_freeresult($result); + +		$sql_ary = array( +			'field_name'			=> 'phpbb_wlm', +			'field_type'			=> 'profilefields.type.string', +			'field_ident'			=> 'phpbb_wlm', +			'field_length'			=> '40', +			'field_minlen'			=> '5', +			'field_maxlen'			=> '255', +			'field_novalue'			=> '', +			'field_default_value'	=> '', +			'field_validation'		=> '.*', +			'field_required'		=> 0, +			'field_show_novalue'	=> 0, +			'field_show_on_reg'		=> 0, +			'field_show_on_pm'		=> 1, +			'field_show_on_vt'		=> 1, +			'field_show_on_ml'		=> 0, +			'field_show_profile'	=> 1, +			'field_hide'			=> 0, +			'field_no_view'			=> 0, +			'field_active'			=> 1, +			'field_is_contact'		=> 1, +			'field_contact_desc'	=> '', +			'field_contact_url'		=> '', +			'field_order'			=> $max_field_order + 1, +		); + +		$sql = 'INSERT INTO ' . PROFILE_FIELDS_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); +		$this->db->sql_query($sql); +		$field_id = (int) $this->db->sql_nextid(); + +		$insert_buffer = new \phpbb\db\sql_insert_buffer($this->db, PROFILE_LANG_TABLE); + +		$sql = 'SELECT lang_id +			FROM ' . LANG_TABLE; +		$result = $this->db->sql_query($sql); +		$lang_name = 'WLM'; +		while ($lang_id = (int) $this->db->sql_fetchfield('lang_id')) +		{ +			$insert_buffer->insert(array( +				'field_id'				=> (int) $field_id, +				'lang_id'				=> (int) $lang_id, +				'lang_name'				=> $lang_name, +				'lang_explain'			=> '', +				'lang_default_value'	=> '', +			)); +		} +		$this->db->sql_freeresult($result); + +		$insert_buffer->flush(); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/report_id_auto_increment.php b/phpBB/phpbb/db/migration/data/v320/report_id_auto_increment.php new file mode 100644 index 0000000000..6e81baefb9 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/report_id_auto_increment.php @@ -0,0 +1,46 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class report_id_auto_increment extends \phpbb\db\migration\migration +{ +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v320\default_data_type_ids', +		); +	} + +	public function update_schema() +	{ +		return array( +			'change_columns'	=> array( +				$this->table_prefix . 'reports'				=> array( +					'report_id'		=> array('ULINT', null, 'auto_increment'), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'change_columns'	=> array( +				$this->table_prefix . 'reports'				=> array( +					'report_id'		=> array('ULINT', 0), +				), +			), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/text_reparser.php b/phpBB/phpbb/db/migration/data/v320/text_reparser.php new file mode 100644 index 0000000000..ea614feb40 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/text_reparser.php @@ -0,0 +1,104 @@ +<?php +/** + * + * This file is part of the phpBB Forum Software package. + * + * @copyright (c) phpBB Limited <https://www.phpbb.com> + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v320; + +class text_reparser extends \phpbb\db\migration\container_aware_migration +{ +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v310\contact_admin_form', +			'\phpbb\db\migration\data\v320\allowed_schemes_links', +		); +	} + +	public function effectively_installed() +	{ +		return isset($this->config['reparse_lock']); +	} + +	public function update_data() +	{ +		return array( +			array('config.add', array('reparse_lock', 0, true)), +			array('config.add', array('text_reparser.pm_text_cron_interval', 10)), +			array('config.add', array('text_reparser.pm_text_last_cron', 0)), +			array('config.add', array('text_reparser.poll_option_cron_interval', 10)), +			array('config.add', array('text_reparser.poll_option_last_cron', 0)), +			array('config.add', array('text_reparser.poll_title_cron_interval', 10)), +			array('config.add', array('text_reparser.poll_title_last_cron', 0)), +			array('config.add', array('text_reparser.post_text_cron_interval', 10)), +			array('config.add', array('text_reparser.post_text_last_cron', 0)), +			array('config.add', array('text_reparser.user_signature_cron_interval', 10)), +			array('config.add', array('text_reparser.user_signature_last_cron', 0)), +			array('custom', array(array($this, 'reparse'))), +		); +	} + +	public function reparse($resume_data) +	{ +		// Somtimes a cron job is too much +		$limit = 100; +		$fast_reparsers = array( +			'text_reparser.contact_admin_info', +			'text_reparser.forum_description', +			'text_reparser.forum_rules', +			'text_reparser.group_description', +		); + +		if (!is_array($resume_data)) +		{ +			$resume_data = array( +				'reparser'	=> 0, +				'current'	=> $this->container->get($fast_reparsers[0])->get_max_id(), +			); +		} + +		$fast_reparsers_size = sizeof($fast_reparsers); +		$processed_records = 0; +		while ($processed_records < $limit && $resume_data['reparser'] < $fast_reparsers_size) +		{ +			$reparser = $this->container->get($fast_reparsers[$resume_data['reparser']]); + +			// New reparser +			if ($resume_data['current'] === 0) +			{ +				$resume_data['current'] = $reparser->get_max_id(); +			} + +			$start = max(1, $resume_data['current'] + 1 - ($limit - $processed_records)); +			$end = max(1, $resume_data['current']); +			$reparser->reparse_range($start, $end); + +			$processed_records += $end - $start + 1; +			$resume_data['current'] = $start - 1; + +			if ($start === 1) +			{ +				// Prevent CLI command from running these reparsers again +				$reparser_manager = $this->container->get('text_reparser.manager'); +				$reparser_manager->update_resume_data($fast_reparsers[$resume_data['reparser']], 1, 0, $limit); + +				$resume_data['reparser']++; +			} +		} + +		if ($resume_data['reparser'] === $fast_reparsers_size) +		{ +			return true; +		} + +		return $resume_data; +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/v320a1.php b/phpBB/phpbb/db/migration/data/v320/v320a1.php new file mode 100644 index 0000000000..d7ecb36f90 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/v320a1.php @@ -0,0 +1,44 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class v320a1 extends \phpbb\db\migration\container_aware_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.2.0-a1', '>='); +	} + +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v320\dev', +			'\phpbb\db\migration\data\v320\allowed_schemes_links', +			'\phpbb\db\migration\data\v320\announce_global_permission', +			'\phpbb\db\migration\data\v320\remove_profilefield_wlm', +			'\phpbb\db\migration\data\v320\font_awesome_update', +			'\phpbb\db\migration\data\v320\icons_alt', +			'\phpbb\db\migration\data\v320\log_post_id', +			'\phpbb\db\migration\data\v320\remove_outdated_media', +			'\phpbb\db\migration\data\v320\notifications_board', +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.2.0-dev')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/v320a2.php b/phpBB/phpbb/db/migration/data/v320/v320a2.php new file mode 100644 index 0000000000..ae53a73210 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/v320a2.php @@ -0,0 +1,38 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class v320a2 extends \phpbb\db\migration\container_aware_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.2.0-a2', '>='); +	} + +	static public function depends_on() +	{ +		return 		array( +			'\phpbb\db\migration\data\v31x\v317rc1', +			'\phpbb\db\migration\data\v320\text_reparser', +			'\phpbb\db\migration\data\v320\v320a1', +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.2.0-a2')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/v320b1.php b/phpBB/phpbb/db/migration/data/v320/v320b1.php new file mode 100644 index 0000000000..5c3a3797cd --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/v320b1.php @@ -0,0 +1,39 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class v320b1 extends \phpbb\db\migration\container_aware_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.2.0-b1', '>='); +	} + +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v31x\v317pl1', +			'\phpbb\db\migration\data\v320\v320a2', +			'\phpbb\db\migration\data\v31x\increase_size_of_dateformat', +			'\phpbb\db\migration\data\v320\default_data_type_ids', +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.2.0-b1')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/v320b2.php b/phpBB/phpbb/db/migration/data/v320/v320b2.php new file mode 100644 index 0000000000..007f7588e6 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/v320b2.php @@ -0,0 +1,40 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +use phpbb\db\migration\migration; + +class v320b2 extends migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.2.0-b2', '>='); +	} + +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v31x\v318', +			'\phpbb\db\migration\data\v320\v320b1', +			'\phpbb\db\migration\data\v320\remote_upload_validation', +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.2.0-b2')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/v320rc1.php b/phpBB/phpbb/db/migration/data/v320/v320rc1.php new file mode 100644 index 0000000000..a04a2abb19 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/v320rc1.php @@ -0,0 +1,40 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +use phpbb\db\migration\migration; + +class v320rc1 extends migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.2.0-RC1', '>='); +	} + +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v31x\v319', +			'\phpbb\db\migration\data\v320\report_id_auto_increment', +			'\phpbb\db\migration\data\v320\v320b2', +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.2.0-RC1')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/migration.php b/phpBB/phpbb/db/migration/migration.php index 5f120333e1..4e218344f4 100644 --- a/phpBB/phpbb/db/migration/migration.php +++ b/phpBB/phpbb/db/migration/migration.php @@ -20,7 +20,7 @@ namespace phpbb\db\migration;  * in a subclass. This class provides various utility methods to simplify editing  * a phpBB.  */ -abstract class migration +abstract class migration implements migration_interface  {  	/** @var \phpbb\config\config */  	protected $config; @@ -28,7 +28,7 @@ abstract class migration  	/** @var \phpbb\db\driver\driver_interface */  	protected $db; -	/** @var \phpbb\db\tools */ +	/** @var \phpbb\db\tools\tools_interface */  	protected $db_tools;  	/** @var string */ @@ -51,12 +51,12 @@ abstract class migration  	*  	* @param \phpbb\config\config $config  	* @param \phpbb\db\driver\driver_interface $db -	* @param \phpbb\db\tools $db_tools +	* @param \phpbb\db\tools\tools_interface $db_tools  	* @param string $phpbb_root_path  	* @param string $php_ext  	* @param string $table_prefix  	*/ -	public function __construct(\phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\db\tools $db_tools, $phpbb_root_path, $php_ext, $table_prefix) +	public function __construct(\phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\db\tools\tools_interface $db_tools, $phpbb_root_path, $php_ext, $table_prefix)  	{  		$this->config = $config;  		$this->db = $db; @@ -70,9 +70,7 @@ abstract class migration  	}  	/** -	* Defines other migrations to be applied first -	* -	* @return array An array of migration class names +	* {@inheritdoc}  	*/  	static public function depends_on()  	{ @@ -80,14 +78,7 @@ abstract class migration  	}  	/** -	* 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) +	* {@inheritdoc}  	*/  	public function effectively_installed()  	{ @@ -95,9 +86,7 @@ abstract class migration  	}  	/** -	* Updates the database schema by providing a set of change instructions -	* -	* @return array Array of schema changes (compatible with db_tools->perform_schema_changes()) +	* {@inheritdoc}  	*/  	public function update_schema()  	{ @@ -105,9 +94,7 @@ abstract class migration  	}  	/** -	* Reverts the database schema by providing a set of change instructions -	* -	* @return array Array of schema changes (compatible with db_tools->perform_schema_changes()) +	* {@inheritdoc}  	*/  	public function revert_schema()  	{ @@ -115,9 +102,7 @@ abstract class migration  	}  	/** -	* Updates data by returning a list of instructions to be executed -	* -	* @return array Array of data update instructions +	* {@inheritdoc}  	*/  	public function update_data()  	{ @@ -125,12 +110,7 @@ abstract class migration  	}  	/** -	* 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) +	* {@inheritdoc}  	*/  	public function revert_data()  	{ diff --git a/phpBB/phpbb/db/migration/migration_interface.php b/phpBB/phpbb/db/migration/migration_interface.php new file mode 100644 index 0000000000..2aba5ec608 --- /dev/null +++ b/phpBB/phpbb/db/migration/migration_interface.php @@ -0,0 +1,70 @@ +<?php +/** + * + * This file is part of the phpBB Forum Software package. + * + * @copyright (c) phpBB Limited <https://www.phpbb.com> + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration; + +/** + * Base class interface for database migrations + */ +interface migration_interface +{ +	/** +	 * Defines other migrations to be applied first +	 * +	 * @return array An array of migration class names +	 */ +	static public function depends_on(); + +	/** +	 * 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(); + +	/** +	 * 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(); + +	/** +	 * 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(); + +	/** +	 * Updates data by returning a list of instructions to be executed +	 * +	 * @return array Array of data update instructions +	 */ +	public function update_data(); + +	/** +	 * 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(); +} diff --git a/phpBB/phpbb/db/migration/profilefield_base_migration.php b/phpBB/phpbb/db/migration/profilefield_base_migration.php index da1a38e2fa..3f26a4998c 100644 --- a/phpBB/phpbb/db/migration/profilefield_base_migration.php +++ b/phpBB/phpbb/db/migration/profilefield_base_migration.php @@ -237,6 +237,7 @@ abstract class profilefield_base_migration extends container_aware_migration  		if ($profile_row === null)  		{ +			/* @var $manager \phpbb\profilefields\manager */  			$manager = $this->container->get('profilefields.manager');  			$profile_row = $manager->build_insert_sql_array(array());  		} diff --git a/phpBB/phpbb/db/migration/schema_generator.php b/phpBB/phpbb/db/migration/schema_generator.php index 91d8307d91..c579e25824 100644 --- a/phpBB/phpbb/db/migration/schema_generator.php +++ b/phpBB/phpbb/db/migration/schema_generator.php @@ -24,7 +24,7 @@ class schema_generator  	/** @var \phpbb\db\driver\driver_interface */  	protected $db; -	/** @var \phpbb\db\tools */ +	/** @var \phpbb\db\tools\tools_interface */  	protected $db_tools;  	/** @var array */ @@ -48,7 +48,7 @@ class schema_generator  	/**  	* Constructor  	*/ -	public function __construct(array $class_names, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\db\tools $db_tools, $phpbb_root_path, $php_ext, $table_prefix) +	public function __construct(array $class_names, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\db\tools\tools_interface $db_tools, $phpbb_root_path, $php_ext, $table_prefix)  	{  		$this->config = $config;  		$this->db = $db; @@ -77,8 +77,15 @@ class schema_generator  		$check_dependencies = true;  		while (!empty($migrations))  		{ -			foreach ($migrations as $migration_class) +			foreach ($migrations as $key => $migration_class)  			{ +				// Unset classes that are not a valid migration +				if (\phpbb\db\migrator::is_migration($migration_class) === false) +				{ +					unset($migrations[$key]); +					continue; +				} +  				$open_dependencies = array_diff($migration_class::depends_on(), $tree);  				if (empty($open_dependencies)) diff --git a/phpBB/phpbb/db/migration/tool/module.php b/phpBB/phpbb/db/migration/tool/module.php index 6d5378e35f..a7dffbb7f2 100644 --- a/phpBB/phpbb/db/migration/tool/module.php +++ b/phpBB/phpbb/db/migration/tool/module.php @@ -13,6 +13,8 @@  namespace phpbb\db\migration\tool; +use phpbb\module\exception\module_exception; +  /**  * Migration module management tool  */ @@ -27,6 +29,9 @@ class module implements \phpbb\db\migration\tool\tool_interface  	/** @var \phpbb\user */  	protected $user; +	/** @var \phpbb\module\module_manager */ +	protected $module_manager; +  	/** @var string */  	protected $phpbb_root_path; @@ -45,15 +50,17 @@ class module implements \phpbb\db\migration\tool\tool_interface  	* @param \phpbb\db\driver\driver_interface $db  	* @param \phpbb\cache\service $cache  	* @param \phpbb\user $user +	* @param \phpbb\module\module_manager	$module_manager  	* @param string $phpbb_root_path  	* @param string $php_ext  	* @param string $modules_table  	*/ -	public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\cache\service $cache, \phpbb\user $user, $phpbb_root_path, $php_ext, $modules_table) +	public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\cache\service $cache, \phpbb\user $user, \phpbb\module\module_manager $module_manager, $phpbb_root_path, $php_ext, $modules_table)  	{  		$this->db = $db;  		$this->cache = $cache;  		$this->user = $user; +		$this->module_manager = $module_manager;  		$this->phpbb_root_path = $phpbb_root_path;  		$this->php_ext = $php_ext;  		$this->modules_table = $modules_table; @@ -152,6 +159,8 @@ class module implements \phpbb\db\migration\tool\tool_interface  	*/  	public function add($class, $parent = 0, $data = array())  	{ +		global $user, $phpbb_log; +  		// allow sending the name as a string in $data to create a category  		if (!is_array($data))  		{ @@ -166,7 +175,6 @@ class module implements \phpbb\db\migration\tool\tool_interface  			$basename = (isset($data['module_basename'])) ? $data['module_basename'] : '';  			$module = $this->get_module_info($class, $basename); -			$result = '';  			foreach ($module['modes'] as $mode => $module_info)  			{  				if (!isset($data['modes']) || in_array($mode, $data['modes'])) @@ -200,13 +208,6 @@ class module implements \phpbb\db\migration\tool\tool_interface  			throw new \phpbb\db\migration\exception('MODULE_EXISTS', $module_id);  		} -		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, @@ -217,19 +218,14 @@ class module implements \phpbb\db\migration\tool\tool_interface  			'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 +		try  		{ +			$this->module_manager->update_module_data($module_data); +  			// 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); +			$phpbb_log->add('admin', (isset($user->data['user_id'])) ? $user->data['user_id'] : ANONYMOUS, $user->ip, 'LOG_MODULE_ADD', false, array($module_log_name));  			// Move the module if requested above/below an existing one  			if (isset($data['before']) && $data['before']) @@ -279,6 +275,11 @@ class module implements \phpbb\db\migration\tool\tool_interface  				$this->db->sql_query($sql);  			}  		} +		catch (module_exception $e) +		{ +			// Error +			throw new \phpbb\db\migration\exception('MODULE_ERROR', $e->getMessage()); +		}  		// Clear the Modules Cache  		$this->cache->destroy("_modules_$class"); @@ -360,21 +361,9 @@ class module implements \phpbb\db\migration\tool\tool_interface  				$module_ids[] = (int) $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->module_manager->delete_module($module_id, $class);  			}  			$this->cache->destroy("_modules_$class"); @@ -422,13 +411,7 @@ class module implements \phpbb\db\migration\tool\tool_interface  	*/  	protected function get_module_info($class, $basename)  	{ -		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 = $acp_modules->get_module_infos($basename, $class, true); +		$module = $this->module_manager->get_module_infos($class, $basename, true);  		if (empty($module))  		{ diff --git a/phpBB/phpbb/db/migrator.php b/phpBB/phpbb/db/migrator.php index 4c4c0a8672..86cb45df6f 100644 --- a/phpBB/phpbb/db/migrator.php +++ b/phpBB/phpbb/db/migrator.php @@ -13,6 +13,8 @@  namespace phpbb\db; +use phpbb\db\output_handler\migrator_output_handler_interface; +use phpbb\db\output_handler\null_migrator_output_handler;  use Symfony\Component\DependencyInjection\ContainerAwareInterface;  use Symfony\Component\DependencyInjection\ContainerInterface; @@ -32,7 +34,7 @@ class migrator  	/** @var \phpbb\db\driver\driver_interface */  	protected $db; -	/** @var \phpbb\db\tools */ +	/** @var \phpbb\db\tools\tools_interface */  	protected $db_tools;  	/** @var \phpbb\db\migration\helper */ @@ -92,7 +94,7 @@ class migrator  	/**  	* Constructor of the database migrator  	*/ -	public function __construct(ContainerInterface $container, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\db\tools $db_tools, $migrations_table, $phpbb_root_path, $php_ext, $table_prefix, $tools, \phpbb\db\migration\helper $helper) +	public function __construct(ContainerInterface $container, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\db\tools\tools_interface $db_tools, $migrations_table, $phpbb_root_path, $php_ext, $table_prefix, $tools, \phpbb\db\migration\helper $helper)  	{  		$this->container = $container;  		$this->config = $config; @@ -122,7 +124,7 @@ class migrator  	/**  	 * Set the output handler.  	 * -	 * @param migrator_output_handler $handler The output handler +	 * @param migrator_output_handler_interface $handler The output handler  	 */  	public function set_output_handler(migrator_output_handler_interface $handler)  	{ @@ -182,10 +184,50 @@ class migrator  	*/  	public function set_migrations($class_names)  	{ +		foreach ($class_names as $key => $class) +		{ +			if (!self::is_migration($class)) +			{ +				unset($class_names[$key]); +			} +		} +  		$this->migrations = $class_names;  	}  	/** +	 * Get the list of available migration class names +	 * +	 * @return array Array of all migrations available to be run +	 */ +	public function get_migrations() +	{ +		return $this->migrations; +	} + +	/** +	 * Get the list of available and not installed migration class names +	 * +	 * @return array +	 */ +	public function get_installable_migrations() +	{ +		$unfinished_migrations = array(); + +		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']) +			{ +				$unfinished_migrations[] = $name; +			} +		} + +		return $unfinished_migrations; +	} + +	/**  	* 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 @@ -464,19 +506,60 @@ class migrator  		if ($state['migration_data_done'])  		{ +			$verbosity = empty($state['migration_data_state']) ? +				migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG; +			$this->output_handler->write(array('MIGRATION_REVERT_DATA_RUNNING', $name), $verbosity); + +			$total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ? +				$state['migration_data_state']['_total_time'] : 0.0; +			$elapsed_time = microtime(true); +  			$steps = array_merge($this->helper->reverse_update_data($migration->update_data()), $migration->revert_data());  			$result = $this->process_data_step($steps, $state['migration_data_state']); +			$elapsed_time = microtime(true) - $elapsed_time; +			$total_time += $elapsed_time; + +			if (is_array($result)) +			{ +				$result['_total_time'] = $total_time; +			} +  			$state['migration_data_state'] = ($result === true) ? '' : $result;  			$state['migration_data_done'] = ($result === true) ? false : true;  			$this->set_migration_state($name, $state); + +			if (!$state['migration_data_done']) +			{ +				$this->output_handler->write(array('MIGRATION_REVERT_DATA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL); +			} +			else +			{ +				$this->output_handler->write(array('MIGRATION_REVERT_DATA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE); +			}  		}  		else if ($state['migration_schema_done'])  		{ +			$verbosity = empty($state['migration_data_state']) ? +				migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG; +			$this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_RUNNING', $name), $verbosity); + +			$total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ? +				$state['migration_data_state']['_total_time'] : 0.0; +			$elapsed_time = microtime(true); +  			$steps = $this->helper->get_schema_steps($migration->revert_schema());  			$result = $this->process_data_step($steps, $state['migration_data_state']); +			$elapsed_time = microtime(true) - $elapsed_time; +			$total_time += $elapsed_time; + +			if (is_array($result)) +			{ +				$result['_total_time'] = $total_time; +			} +  			$state['migration_data_state'] = ($result === true) ? '' : $result;  			$state['migration_schema_done'] = ($result === true) ? false : true; @@ -488,10 +571,14 @@ class migrator  				$this->last_run_migration = false;  				unset($this->migration_state[$name]); + +				$this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL);  			}  			else  			{  				$this->set_migration_state($name, $state); + +				$this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE);  			}  		} @@ -885,4 +972,27 @@ class migrator  			));  		}  	} + +	/** +	 * Check if a class is a migration. +	 * +	 * @param string $migration A migration class name +	 * @return bool Return true if class is a migration, false otherwise +	 */ +	static public function is_migration($migration) +	{ +		if (class_exists($migration)) +		{ +			// Migration classes should extend the abstract class +			// phpbb\db\migration\migration (which implements the +			// migration_interface) and be instantiable. +			$reflector = new \ReflectionClass($migration); +			if ($reflector->implementsInterface('\phpbb\db\migration\migration_interface') && $reflector->isInstantiable()) +			{ +				return true; +			} +		} + +		return false; +	}  } diff --git a/phpBB/phpbb/db/html_migrator_output_handler.php b/phpBB/phpbb/db/output_handler/html_migrator_output_handler.php index e37c667463..67309649c9 100644 --- a/phpBB/phpbb/db/html_migrator_output_handler.php +++ b/phpBB/phpbb/db/output_handler/html_migrator_output_handler.php @@ -11,27 +11,25 @@  *  */ -namespace phpbb\db; - -use phpbb\user; +namespace phpbb\db\output_handler;  class html_migrator_output_handler implements migrator_output_handler_interface  {  	/** -	 * User object. +	 * Language object.  	 * -	 * @var user +	 * @var \phpbb\language\language  	 */ -	private $user; +	private $language;  	/**  	 * Constructor  	 * -	 * @param user $user	User object +	 * @param \phpbb\language\language	$language	Language object  	 */ -	public function __construct(user $user) +	public function __construct(\phpbb\language\language $language)  	{ -		$this->user = $user; +		$this->language = $language;  	}  	/** @@ -41,7 +39,7 @@ class html_migrator_output_handler implements migrator_output_handler_interface  	{  		if ($verbosity <= migrator_output_handler_interface::VERBOSITY_VERBOSE)  		{ -			$final_message = call_user_func_array(array($this->user, 'lang'), $message); +			$final_message = $this->language->lang_array(array_shift($message), $message);  			echo $final_message . "<br />\n";  		}  	} diff --git a/phpBB/phpbb/db/output_handler/installer_migrator_output_handler.php b/phpBB/phpbb/db/output_handler/installer_migrator_output_handler.php new file mode 100644 index 0000000000..56d5cf49a1 --- /dev/null +++ b/phpBB/phpbb/db/output_handler/installer_migrator_output_handler.php @@ -0,0 +1,46 @@ +<?php +/** + * + * This file is part of the phpBB Forum Software package. + * + * @copyright (c) phpBB Limited <https://www.phpbb.com> + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\output_handler; + +use phpbb\install\helper\iohandler\iohandler_interface; + +class installer_migrator_output_handler implements migrator_output_handler_interface +{ +	/** +	 * @var iohandler_interface +	 */ +	protected $iohandler; + +	/** +	 * Constructor +	 * +	 * @param iohandler_interface	$iohandler	Installer's IO-handler +	 */ +	public function __construct(iohandler_interface $iohandler) +	{ +		$this->iohandler = $iohandler; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function write($message, $verbosity) +	{ +		if ($verbosity <= migrator_output_handler_interface::VERBOSITY_VERBOSE) +		{ +			$this->iohandler->add_log_message($message); +			$this->iohandler->send_response(); +		} +	} +} diff --git a/phpBB/phpbb/db/log_wrapper_migrator_output_handler.php b/phpBB/phpbb/db/output_handler/log_wrapper_migrator_output_handler.php index 94c293dc45..20991746ac 100644 --- a/phpBB/phpbb/db/log_wrapper_migrator_output_handler.php +++ b/phpBB/phpbb/db/output_handler/log_wrapper_migrator_output_handler.php @@ -11,18 +11,16 @@  *  */ -namespace phpbb\db; - -use phpbb\user; +namespace phpbb\db\output_handler;  class log_wrapper_migrator_output_handler implements migrator_output_handler_interface  {  	/** -	 * User object. +	 * Language object.  	 * -	 * @var user +	 * @var \phpbb\language\language  	 */ -	protected $user; +	protected $language;  	/**  	 * A migrator output handler @@ -38,16 +36,23 @@ class log_wrapper_migrator_output_handler implements migrator_output_handler_int  	protected $file_handle = false;  	/** +	 * @var \phpbb\filesystem\filesystem_interface +	 */ +	protected $filesystem; + +	/**  	 * Constructor  	 * -	 * @param user $user	User object -	 * @param migrator_output_handler_interface $migrator Migrator output handler -	 * @param string $log_file	File to log to +	 * @param \phpbb\language\language					$language	Language object +	 * @param migrator_output_handler_interface			$migrator	Migrator output handler +	 * @param string									$log_file	File to log to +	 * @param \phpbb\filesystem\filesystem_interface	$filesystem	phpBB filesystem object  	 */ -	public function __construct(user $user, migrator_output_handler_interface $migrator, $log_file) +	public function __construct(\phpbb\language\language $language, migrator_output_handler_interface $migrator, $log_file, \phpbb\filesystem\filesystem_interface $filesystem)  	{ -		$this->user = $user; +		$this->language = $language;  		$this->migrator = $migrator; +		$this->filesystem = $filesystem;  		$this->file_open($log_file);  	} @@ -58,7 +63,7 @@ class log_wrapper_migrator_output_handler implements migrator_output_handler_int  	 */  	protected function file_open($file)  	{ -		if (phpbb_is_writable(dirname($file))) +		if ($this->filesystem->is_writable(dirname($file)))  		{  			$this->file_handle = fopen($file, 'w');  		} @@ -77,7 +82,8 @@ class log_wrapper_migrator_output_handler implements migrator_output_handler_int  		if ($this->file_handle !== false)  		{ -			$translated_message = call_user_func_array(array($this->user, 'lang'), $message) . "\n"; + +			$translated_message = $this->language->lang_array(array_shift($message), $message);  			if ($verbosity <= migrator_output_handler_interface::VERBOSITY_NORMAL)  			{ diff --git a/phpBB/phpbb/db/migrator_output_handler_interface.php b/phpBB/phpbb/db/output_handler/migrator_output_handler_interface.php index 9947b51dcc..455d8aabbb 100644 --- a/phpBB/phpbb/db/migrator_output_handler_interface.php +++ b/phpBB/phpbb/db/output_handler/migrator_output_handler_interface.php @@ -11,7 +11,7 @@  *  */ -namespace phpbb\db; +namespace phpbb\db\output_handler;  interface migrator_output_handler_interface  { diff --git a/phpBB/phpbb/db/null_migrator_output_handler.php b/phpBB/phpbb/db/output_handler/null_migrator_output_handler.php index 0e8cfbb049..5fc2a52577 100644 --- a/phpBB/phpbb/db/null_migrator_output_handler.php +++ b/phpBB/phpbb/db/output_handler/null_migrator_output_handler.php @@ -11,7 +11,7 @@  *  */ -namespace phpbb\db; +namespace phpbb\db\output_handler;  class null_migrator_output_handler implements migrator_output_handler_interface  { diff --git a/phpBB/phpbb/db/tools/factory.php b/phpBB/phpbb/db/tools/factory.php new file mode 100644 index 0000000000..d204451a63 --- /dev/null +++ b/phpBB/phpbb/db/tools/factory.php @@ -0,0 +1,43 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\tools; + +/** + * A factory which serves the suitable tools instance for the given dbal + */ +class factory +{ +	/** +	 * @param mixed $db_driver +	 * @param bool $return_statements +	 * @return \phpbb\db\tools\tools_interface +	 */ +	public function get($db_driver, $return_statements = false) +	{ +		if ($db_driver instanceof \phpbb\db\driver\mssql || $db_driver instanceof \phpbb\db\driver\mssql_base) +		{ +			return new \phpbb\db\tools\mssql($db_driver, $return_statements); +		} +		else if ($db_driver instanceof \phpbb\db\driver\postgres) +		{ +			return new \phpbb\db\tools\postgres($db_driver, $return_statements); +		} +		else if ($db_driver instanceof \phpbb\db\driver\driver_interface) +		{ +			return new \phpbb\db\tools\tools($db_driver, $return_statements); +		} + +		throw new \InvalidArgumentException('Invalid database driver given'); +	} +} diff --git a/phpBB/phpbb/db/tools/mssql.php b/phpBB/phpbb/db/tools/mssql.php new file mode 100644 index 0000000000..87719acd21 --- /dev/null +++ b/phpBB/phpbb/db/tools/mssql.php @@ -0,0 +1,795 @@ +<?php +/** + * + * This file is part of the phpBB Forum Software package. + * + * @copyright (c) phpBB Limited <https://www.phpbb.com> + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\tools; + +/** + * Database Tools for handling cross-db actions such as altering columns, etc. + * Currently not supported is returning SQL for creating tables. + */ +class mssql extends tools +{ +	/** +	 * Is the used MS SQL Server a SQL Server 2000? +	 * @var bool +	 */ +	protected $is_sql_server_2000; + +	/** +	 * Get the column types for mssql based databases +	 * +	 * @return array +	 */ +	public static function get_dbms_type_map() +	{ +		return array( +			'mssql'		=> array( +				'INT:'		=> '[int]', +				'BINT'		=> '[float]', +				'ULINT'		=> '[int]', +				'UINT'		=> '[int]', +				'UINT:'		=> '[int]', +				'TINT:'		=> '[int]', +				'USINT'		=> '[int]', +				'BOOL'		=> '[int]', +				'VCHAR'		=> '[varchar] (255)', +				'VCHAR:'	=> '[varchar] (%d)', +				'CHAR:'		=> '[char] (%d)', +				'XSTEXT'	=> '[varchar] (1000)', +				'STEXT'		=> '[varchar] (3000)', +				'TEXT'		=> '[varchar] (8000)', +				'MTEXT'		=> '[text]', +				'XSTEXT_UNI'=> '[varchar] (100)', +				'STEXT_UNI'	=> '[varchar] (255)', +				'TEXT_UNI'	=> '[varchar] (4000)', +				'MTEXT_UNI'	=> '[text]', +				'TIMESTAMP'	=> '[int]', +				'DECIMAL'	=> '[float]', +				'DECIMAL:'	=> '[float]', +				'PDECIMAL'	=> '[float]', +				'PDECIMAL:'	=> '[float]', +				'VCHAR_UNI'	=> '[varchar] (255)', +				'VCHAR_UNI:'=> '[varchar] (%d)', +				'VCHAR_CI'	=> '[varchar] (255)', +				'VARBINARY'	=> '[varchar] (255)', +			), + +			'mssqlnative'	=> array( +				'INT:'		=> '[int]', +				'BINT'		=> '[float]', +				'ULINT'		=> '[int]', +				'UINT'		=> '[int]', +				'UINT:'		=> '[int]', +				'TINT:'		=> '[int]', +				'USINT'		=> '[int]', +				'BOOL'		=> '[int]', +				'VCHAR'		=> '[varchar] (255)', +				'VCHAR:'	=> '[varchar] (%d)', +				'CHAR:'		=> '[char] (%d)', +				'XSTEXT'	=> '[varchar] (1000)', +				'STEXT'		=> '[varchar] (3000)', +				'TEXT'		=> '[varchar] (8000)', +				'MTEXT'		=> '[text]', +				'XSTEXT_UNI'=> '[varchar] (100)', +				'STEXT_UNI'	=> '[varchar] (255)', +				'TEXT_UNI'	=> '[varchar] (4000)', +				'MTEXT_UNI'	=> '[text]', +				'TIMESTAMP'	=> '[int]', +				'DECIMAL'	=> '[float]', +				'DECIMAL:'	=> '[float]', +				'PDECIMAL'	=> '[float]', +				'PDECIMAL:'	=> '[float]', +				'VCHAR_UNI'	=> '[varchar] (255)', +				'VCHAR_UNI:'=> '[varchar] (%d)', +				'VCHAR_CI'	=> '[varchar] (255)', +				'VARBINARY'	=> '[varchar] (255)', +			), +		); +	} + +	/** +	* Constructor. Set DB Object and set {@link $return_statements return_statements}. +	* +	* @param \phpbb\db\driver\driver_interface	$db					Database connection +	* @param bool		$return_statements	True if only statements should be returned and no SQL being executed +	*/ +	public function __construct(\phpbb\db\driver\driver_interface $db, $return_statements = false) +	{ +		parent::__construct($db, $return_statements); + +		// Determine mapping database type +		switch ($this->db->get_sql_layer()) +		{ +			case 'mssql': +			case 'mssql_odbc': +				$this->sql_layer = 'mssql'; +			break; + +			case 'mssqlnative': +				$this->sql_layer = 'mssqlnative'; +			break; +		} + +		$this->dbms_type_map = self::get_dbms_type_map(); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_list_tables() +	{ +		$sql = "SELECT name +			FROM sysobjects +			WHERE type='U'"; +		$result = $this->db->sql_query($sql); + +		$tables = array(); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$name = current($row); +			$tables[$name] = $name; +		} +		$this->db->sql_freeresult($result); + +		return $tables; +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_create_table($table_name, $table_data) +	{ +		// holds the DDL for a column +		$columns = $statements = array(); + +		if ($this->sql_table_exists($table_name)) +		{ +			return $this->_sql_run_sql($statements); +		} + +		// Begin transaction +		$statements[] = 'begin'; + +		// Determine if we have created a PRIMARY KEY in the earliest +		$primary_key_gen = false; + +		// Determine if the table requires a sequence +		$create_sequence = false; + +		// Begin table sql statement +		$table_sql = 'CREATE TABLE [' . $table_name . '] (' . "\n"; + +		if (!isset($table_data['PRIMARY_KEY'])) +		{ +			$table_data['COLUMNS']['mssqlindex'] = array('UINT', null, 'auto_increment'); +			$table_data['PRIMARY_KEY'] = 'mssqlindex'; +		} + +		// Iterate through the columns to create a table +		foreach ($table_data['COLUMNS'] as $column_name => $column_data) +		{ +			// here lies an array, filled with information compiled on the column's data +			$prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + +			if (isset($prepared_column['auto_increment']) && $prepared_column['auto_increment'] && strlen($column_name) > 26) // "${column_name}_gen" +			{ +				trigger_error("Index name '${column_name}_gen' on table '$table_name' is too long. The maximum auto increment column length is 26 characters.", E_USER_ERROR); +			} + +			// here we add the definition of the new column to the list of columns +			$columns[] = "\t [{$column_name}] " . $prepared_column['column_type_sql_default']; + +			// see if we have found a primary key set due to a column definition if we have found it, we can stop looking +			if (!$primary_key_gen) +			{ +				$primary_key_gen = isset($prepared_column['primary_key_set']) && $prepared_column['primary_key_set']; +			} + +			// create sequence DDL based off of the existance of auto incrementing columns +			if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment']) +			{ +				$create_sequence = $column_name; +			} +		} + +		// this makes up all the columns in the create table statement +		$table_sql .= implode(",\n", $columns); + +		// Close the table for two DBMS and add to the statements +		$table_sql .= "\n);"; +		$statements[] = $table_sql; + +		// we have yet to create a primary key for this table, +		// this means that we can add the one we really wanted instead +		if (!$primary_key_gen) +		{ +			// Write primary key +			if (isset($table_data['PRIMARY_KEY'])) +			{ +				if (!is_array($table_data['PRIMARY_KEY'])) +				{ +					$table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']); +				} + +				// We need the data here +				$old_return_statements = $this->return_statements; +				$this->return_statements = true; + +				$primary_key_stmts = $this->sql_create_primary_key($table_name, $table_data['PRIMARY_KEY']); +				foreach ($primary_key_stmts as $pk_stmt) +				{ +					$statements[] = $pk_stmt; +				} + +				$this->return_statements = $old_return_statements; +			} +		} + +		// Write Keys +		if (isset($table_data['KEYS'])) +		{ +			foreach ($table_data['KEYS'] as $key_name => $key_data) +			{ +				if (!is_array($key_data[1])) +				{ +					$key_data[1] = array($key_data[1]); +				} + +				$old_return_statements = $this->return_statements; +				$this->return_statements = true; + +				$key_stmts = ($key_data[0] == 'UNIQUE') ? $this->sql_create_unique_index($table_name, $key_name, $key_data[1]) : $this->sql_create_index($table_name, $key_name, $key_data[1]); + +				foreach ($key_stmts as $key_stmt) +				{ +					$statements[] = $key_stmt; +				} + +				$this->return_statements = $old_return_statements; +			} +		} + +		// Commit Transaction +		$statements[] = 'commit'; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_list_columns($table_name) +	{ +		$columns = array(); + +		$sql = "SELECT c.name +			FROM syscolumns c +			LEFT JOIN sysobjects o ON c.id = o.id +			WHERE o.name = '{$table_name}'"; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$column = strtolower(current($row)); +			$columns[$column] = $column; +		} +		$this->db->sql_freeresult($result); + +		return $columns; +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_index_exists($table_name, $index_name) +	{ +		$sql = "EXEC sp_statistics '$table_name'"; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if ($row['TYPE'] == 3) +			{ +				if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) +				{ +					$this->db->sql_freeresult($result); +					return true; +				} +			} +		} +		$this->db->sql_freeresult($result); + +		return false; +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_unique_index_exists($table_name, $index_name) +	{ +		$sql = "EXEC sp_statistics '$table_name'"; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			// Usually NON_UNIQUE is the column we want to check, but we allow for both +			if ($row['TYPE'] == 3) +			{ +				if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) +				{ +					$this->db->sql_freeresult($result); +					return true; +				} +			} +		} +		$this->db->sql_freeresult($result); + +		return false; +	} + +	/** +	 * {@inheritDoc} +	*/ +	function sql_prepare_column_data($table_name, $column_name, $column_data) +	{ +		if (strlen($column_name) > 30) +		{ +			trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR); +		} + +		// Get type +		list($column_type, ) = $this->get_column_type($column_data[0]); + +		// Adjust default value if db-dependent specified +		if (is_array($column_data[1])) +		{ +			$column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default']; +		} + +		$sql = ''; + +		$return_array = array(); + +		$sql .= " {$column_type} "; +		$sql_default = " {$column_type} "; + +		// For adding columns we need the default definition +		if (!is_null($column_data[1])) +		{ +			// For hexadecimal values do not use single quotes +			if (strpos($column_data[1], '0x') === 0) +			{ +				$return_array['default'] = 'DEFAULT (' . $column_data[1] . ') '; +				$sql_default .= $return_array['default']; +			} +			else +			{ +				$return_array['default'] = 'DEFAULT (' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ') '; +				$sql_default .= $return_array['default']; +			} +		} + +		if (isset($column_data[2]) && $column_data[2] == 'auto_increment') +		{ +			// $sql .= 'IDENTITY (1, 1) '; +			$sql_default .= 'IDENTITY (1, 1) '; +		} + +		$return_array['textimage'] = $column_type === '[text]'; + +		if (!is_null($column_data[1]) || (isset($column_data[2]) && $column_data[2] == 'auto_increment')) +		{ +			$sql .= 'NOT NULL'; +			$sql_default .= 'NOT NULL'; +		} +		else +		{ +			$sql .= 'NULL'; +			$sql_default .= 'NULL'; +		} + +		$return_array['column_type_sql_default'] = $sql_default; + +		$return_array['column_type_sql'] = $sql; + +		return $return_array; +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_column_add($table_name, $column_name, $column_data, $inline = false) +	{ +		$column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); +		$statements = array(); + +		// Does not support AFTER, only through temporary table +		$statements[] = 'ALTER TABLE [' . $table_name . '] ADD [' . $column_name . '] ' . $column_data['column_type_sql_default']; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_column_remove($table_name, $column_name, $inline = false) +	{ +		$statements = array(); + +		// We need the data here +		$old_return_statements = $this->return_statements; +		$this->return_statements = true; + +		$indexes = $this->get_existing_indexes($table_name, $column_name); +		$indexes = array_merge($indexes, $this->get_existing_indexes($table_name, $column_name, true)); + +		// Drop any indexes +		$recreate_indexes = array(); +		if (!empty($indexes)) +		{ +			foreach ($indexes as $index_name => $index_data) +			{ +				$result = $this->sql_index_drop($table_name, $index_name); +				$statements = array_merge($statements, $result); +				if (sizeof($index_data) > 1) +				{ +					// Remove this column from the index and recreate it +					$recreate_indexes[$index_name] = array_diff($index_data, array($column_name)); +				} +			} +		} + +		// Drop default value constraint +		$result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name); +		$statements = array_merge($statements, $result); + +		// Remove the column +		$statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']'; + +		if (!empty($recreate_indexes)) +		{ +			// Recreate indexes after we removed the column +			foreach ($recreate_indexes as $index_name => $index_data) +			{ +				$result = $this->sql_create_index($table_name, $index_name, $index_data); +				$statements = array_merge($statements, $result); +			} +		} + +		$this->return_statements = $old_return_statements; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_index_drop($table_name, $index_name) +	{ +		$statements = array(); + +		$statements[] = 'DROP INDEX ' . $table_name . '.' . $index_name; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_table_drop($table_name) +	{ +		$statements = array(); + +		if (!$this->sql_table_exists($table_name)) +		{ +			return $this->_sql_run_sql($statements); +		} + +		// the most basic operation, get rid of the table +		$statements[] = 'DROP TABLE ' . $table_name; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_create_primary_key($table_name, $column, $inline = false) +	{ +		$statements = array(); + +		$sql = "ALTER TABLE [{$table_name}] WITH NOCHECK ADD "; +		$sql .= "CONSTRAINT [PK_{$table_name}] PRIMARY KEY  CLUSTERED ("; +		$sql .= '[' . implode("],\n\t\t[", $column) . ']'; +		$sql .= ')'; + +		$statements[] = $sql; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_create_unique_index($table_name, $index_name, $column) +	{ +		$statements = array(); + +		$this->check_index_name_length($table_name, $index_name); + +		$statements[] = 'CREATE UNIQUE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])'; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_create_index($table_name, $index_name, $column) +	{ +		$statements = array(); + +		$this->check_index_name_length($table_name, $index_name); + +		// remove index length +		$column = preg_replace('#:.*$#', '', $column); + +		$statements[] = 'CREATE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])'; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_list_index($table_name) +	{ +		$index_array = array(); +		$sql = "EXEC sp_statistics '$table_name'"; +		$result = $this->db->sql_query($sql); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if ($row['TYPE'] == 3) +			{ +				$index_array[] = strtolower($row['INDEX_NAME']); +			} +		} +		$this->db->sql_freeresult($result); + +		return $index_array; +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_column_change($table_name, $column_name, $column_data, $inline = false) +	{ +		$column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); +		$statements = array(); + +		// We need the data here +		$old_return_statements = $this->return_statements; +		$this->return_statements = true; + +		$indexes = $this->get_existing_indexes($table_name, $column_name); +		$unique_indexes = $this->get_existing_indexes($table_name, $column_name, true); + +		// Drop any indexes +		if (!empty($indexes) || !empty($unique_indexes)) +		{ +			$drop_indexes = array_merge(array_keys($indexes), array_keys($unique_indexes)); +			foreach ($drop_indexes as $index_name) +			{ +				$result = $this->sql_index_drop($table_name, $index_name); +				$statements = array_merge($statements, $result); +			} +		} + +		// Drop default value constraint +		$result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name); +		$statements = array_merge($statements, $result); + +		// Change the column +		$statements[] = 'ALTER TABLE [' . $table_name . '] ALTER COLUMN [' . $column_name . '] ' . $column_data['column_type_sql']; + +		if (!empty($column_data['default'])) +		{ +			// Add new default value constraint +			$statements[] = 'ALTER TABLE [' . $table_name . '] ADD CONSTRAINT [DF_' . $table_name . '_' . $column_name . '_1] ' . $column_data['default'] . ' FOR [' . $column_name . ']'; +		} + +		if (!empty($indexes)) +		{ +			// Recreate indexes after we changed the column +			foreach ($indexes as $index_name => $index_data) +			{ +				$result = $this->sql_create_index($table_name, $index_name, $index_data); +				$statements = array_merge($statements, $result); +			} +		} + +		if (!empty($unique_indexes)) +		{ +			// Recreate unique indexes after we changed the column +			foreach ($unique_indexes as $index_name => $index_data) +			{ +				$result = $this->sql_create_unique_index($table_name, $index_name, $index_data); +				$statements = array_merge($statements, $result); +			} +		} + +		$this->return_statements = $old_return_statements; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	* Get queries to drop the default constraints of a column +	* +	* We need to drop the default constraints of a column, +	* before being able to change their type or deleting them. +	* +	* @param string $table_name +	* @param string $column_name +	* @return array		Array with SQL statements +	*/ +	protected function mssql_get_drop_default_constraints_queries($table_name, $column_name) +	{ +		$statements = array(); +		if ($this->mssql_is_sql_server_2000()) +		{ +			// http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx +			// Deprecated in SQL Server 2005 +			$sql = "SELECT so.name AS def_name +				FROM sysobjects so +				JOIN sysconstraints sc ON so.id = sc.constid +				WHERE object_name(so.parent_obj) = '{$table_name}' +					AND so.xtype = 'D' +					AND sc.colid = (SELECT colid FROM syscolumns +						WHERE id = object_id('{$table_name}') +							AND name = '{$column_name}')"; +		} +		else +		{ +			$sql = "SELECT dobj.name AS def_name +				FROM sys.columns col +					LEFT OUTER JOIN sys.objects dobj ON (dobj.object_id = col.default_object_id AND dobj.type = 'D') +				WHERE col.object_id = object_id('{$table_name}') +					AND col.name = '{$column_name}' +					AND dobj.name IS NOT NULL"; +		} + +		$result = $this->db->sql_query($sql); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$statements[] = 'ALTER TABLE [' . $table_name . '] DROP CONSTRAINT [' . $row['def_name'] . ']'; +		} +		$this->db->sql_freeresult($result); + +		return $statements; +	} + +	/** +	* Get a list with existing indexes for the column +	* +	* @param string $table_name +	* @param string $column_name +	* @param bool $unique Should we get unique indexes or normal ones +	* @return array		Array with Index name => columns +	*/ +	public function get_existing_indexes($table_name, $column_name, $unique = false) +	{ +		$existing_indexes = array(); +		if ($this->mssql_is_sql_server_2000()) +		{ +			// http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx +			// Deprecated in SQL Server 2005 +			$sql = "SELECT DISTINCT ix.name AS phpbb_index_name +				FROM sysindexes ix +				INNER JOIN sysindexkeys ixc +					ON ixc.id = ix.id +						AND ixc.indid = ix.indid +				INNER JOIN syscolumns cols +					ON cols.colid = ixc.colid +						AND cols.id = ix.id +				WHERE ix.id = object_id('{$table_name}') +					AND cols.name = '{$column_name}' +					AND INDEXPROPERTY(ix.id, ix.name, 'IsUnique') = " . ($unique ? '1' : '0'); +		} +		else +		{ +			$sql = "SELECT DISTINCT ix.name AS phpbb_index_name +				FROM sys.indexes ix +				INNER JOIN sys.index_columns ixc +					ON ixc.object_id = ix.object_id +						AND ixc.index_id = ix.index_id +				INNER JOIN sys.columns cols +					ON cols.column_id = ixc.column_id +						AND cols.object_id = ix.object_id +				WHERE ix.object_id = object_id('{$table_name}') +					AND cols.name = '{$column_name}' +					AND ix.is_unique = " . ($unique ? '1' : '0'); +		} + +		$result = $this->db->sql_query($sql); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if (!isset($row['is_unique']) || ($unique && $row['is_unique'] == 'UNIQUE') || (!$unique && $row['is_unique'] == 'NONUNIQUE')) +			{ +				$existing_indexes[$row['phpbb_index_name']] = array(); +			} +		} +		$this->db->sql_freeresult($result); + +		if (empty($existing_indexes)) +		{ +			return array(); +		} + +		if ($this->mssql_is_sql_server_2000()) +		{ +			$sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name +				FROM sysindexes ix +				INNER JOIN sysindexkeys ixc +					ON ixc.id = ix.id +						AND ixc.indid = ix.indid +				INNER JOIN syscolumns cols +					ON cols.colid = ixc.colid +						AND cols.id = ix.id +				WHERE ix.id = object_id('{$table_name}') +					AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes)); +		} +		else +		{ +			$sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name +				FROM sys.indexes ix +				INNER JOIN sys.index_columns ixc +					ON ixc.object_id = ix.object_id +						AND ixc.index_id = ix.index_id +				INNER JOIN sys.columns cols +					ON cols.column_id = ixc.column_id +						AND cols.object_id = ix.object_id +				WHERE ix.object_id = object_id('{$table_name}') +					AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes)); +		} + +		$result = $this->db->sql_query($sql); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$existing_indexes[$row['phpbb_index_name']][] = $row['phpbb_column_name']; +		} +		$this->db->sql_freeresult($result); + +		return $existing_indexes; +	} + +	/** +	* Is the used MS SQL Server a SQL Server 2000? +	* +	* @return bool +	*/ +	protected function mssql_is_sql_server_2000() +	{ +		if ($this->is_sql_server_2000 === null) +		{ +			$sql = "SELECT CAST(SERVERPROPERTY('productversion') AS VARCHAR(25)) AS mssql_version"; +			$result = $this->db->sql_query($sql); +			$properties = $this->db->sql_fetchrow($result); +			$this->db->sql_freeresult($result); +			$this->is_sql_server_2000 = $properties['mssql_version'][0] == '8'; +		} + +		return $this->is_sql_server_2000; +	} + +} diff --git a/phpBB/phpbb/db/tools/postgres.php b/phpBB/phpbb/db/tools/postgres.php new file mode 100644 index 0000000000..e2a4e668a6 --- /dev/null +++ b/phpBB/phpbb/db/tools/postgres.php @@ -0,0 +1,614 @@ +<?php +/** + * + * This file is part of the phpBB Forum Software package. + * + * @copyright (c) phpBB Limited <https://www.phpbb.com> + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\tools; + +/** + * Database Tools for handling cross-db actions such as altering columns, etc. + * Currently not supported is returning SQL for creating tables. + */ +class postgres extends tools +{ +	/** +	 * Get the column types for postgres only +	 * +	 * @return array +	 */ +	public static function get_dbms_type_map() +	{ +		return array( +			'postgres'	=> array( +				'INT:'		=> 'INT4', +				'BINT'		=> 'INT8', +				'ULINT'		=> 'INT4', // unsigned +				'UINT'		=> 'INT4', // unsigned +				'UINT:'		=> 'INT4', // unsigned +				'USINT'		=> 'INT2', // unsigned +				'BOOL'		=> 'INT2', // unsigned +				'TINT:'		=> 'INT2', +				'VCHAR'		=> 'varchar(255)', +				'VCHAR:'	=> 'varchar(%d)', +				'CHAR:'		=> 'char(%d)', +				'XSTEXT'	=> 'varchar(1000)', +				'STEXT'		=> 'varchar(3000)', +				'TEXT'		=> 'varchar(8000)', +				'MTEXT'		=> 'TEXT', +				'XSTEXT_UNI'=> 'varchar(100)', +				'STEXT_UNI'	=> 'varchar(255)', +				'TEXT_UNI'	=> 'varchar(4000)', +				'MTEXT_UNI'	=> 'TEXT', +				'TIMESTAMP'	=> 'INT4', // unsigned +				'DECIMAL'	=> 'decimal(5,2)', +				'DECIMAL:'	=> 'decimal(%d,2)', +				'PDECIMAL'	=> 'decimal(6,3)', +				'PDECIMAL:'	=> 'decimal(%d,3)', +				'VCHAR_UNI'	=> 'varchar(255)', +				'VCHAR_UNI:'=> 'varchar(%d)', +				'VCHAR_CI'	=> 'varchar_ci', +				'VARBINARY'	=> 'bytea', +			), +		); +	} + +	/** +	* Constructor. Set DB Object and set {@link $return_statements return_statements}. +	* +	* @param \phpbb\db\driver\driver_interface	$db					Database connection +	* @param bool		$return_statements	True if only statements should be returned and no SQL being executed +	*/ +	public function __construct(\phpbb\db\driver\driver_interface $db, $return_statements = false) +	{ +		parent::__construct($db, $return_statements); + +		// Determine mapping database type +		$this->sql_layer = 'postgres'; + +		$this->dbms_type_map = self::get_dbms_type_map(); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_list_tables() +	{ +		$sql = 'SELECT relname +			FROM pg_stat_user_tables'; +		$result = $this->db->sql_query($sql); + +		$tables = array(); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$name = current($row); +			$tables[$name] = $name; +		} +		$this->db->sql_freeresult($result); + +		return $tables; +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_create_table($table_name, $table_data) +	{ +		// holds the DDL for a column +		$columns = $statements = array(); + +		if ($this->sql_table_exists($table_name)) +		{ +			return $this->_sql_run_sql($statements); +		} + +		// Begin transaction +		$statements[] = 'begin'; + +		// Determine if we have created a PRIMARY KEY in the earliest +		$primary_key_gen = false; + +		// Determine if the table requires a sequence +		$create_sequence = false; + +		// Begin table sql statement +		$table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n"; + +		// Iterate through the columns to create a table +		foreach ($table_data['COLUMNS'] as $column_name => $column_data) +		{ +			// here lies an array, filled with information compiled on the column's data +			$prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + +			if (isset($prepared_column['auto_increment']) && $prepared_column['auto_increment'] && strlen($column_name) > 26) // "${column_name}_gen" +			{ +				trigger_error("Index name '${column_name}_gen' on table '$table_name' is too long. The maximum auto increment column length is 26 characters.", E_USER_ERROR); +			} + +			// here we add the definition of the new column to the list of columns +			$columns[] = "\t {$column_name} " . $prepared_column['column_type_sql']; + +			// see if we have found a primary key set due to a column definition if we have found it, we can stop looking +			if (!$primary_key_gen) +			{ +				$primary_key_gen = isset($prepared_column['primary_key_set']) && $prepared_column['primary_key_set']; +			} + +			// create sequence DDL based off of the existance of auto incrementing columns +			if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment']) +			{ +				$create_sequence = $column_name; +			} +		} + +		// this makes up all the columns in the create table statement +		$table_sql .= implode(",\n", $columns); + +		// we have yet to create a primary key for this table, +		// this means that we can add the one we really wanted instead +		if (!$primary_key_gen) +		{ +			// Write primary key +			if (isset($table_data['PRIMARY_KEY'])) +			{ +				if (!is_array($table_data['PRIMARY_KEY'])) +				{ +					$table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']); +				} + +				$table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; +			} +		} + +		// do we need to add a sequence for auto incrementing columns? +		if ($create_sequence) +		{ +			$statements[] = "CREATE SEQUENCE {$table_name}_seq;"; +		} + +		// close the table +		$table_sql .= "\n);"; +		$statements[] = $table_sql; + +		// Write Keys +		if (isset($table_data['KEYS'])) +		{ +			foreach ($table_data['KEYS'] as $key_name => $key_data) +			{ +				if (!is_array($key_data[1])) +				{ +					$key_data[1] = array($key_data[1]); +				} + +				$old_return_statements = $this->return_statements; +				$this->return_statements = true; + +				$key_stmts = ($key_data[0] == 'UNIQUE') ? $this->sql_create_unique_index($table_name, $key_name, $key_data[1]) : $this->sql_create_index($table_name, $key_name, $key_data[1]); + +				foreach ($key_stmts as $key_stmt) +				{ +					$statements[] = $key_stmt; +				} + +				$this->return_statements = $old_return_statements; +			} +		} + +		// Commit Transaction +		$statements[] = 'commit'; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_list_columns($table_name) +	{ +		$columns = array(); + +		$sql = "SELECT a.attname +			FROM pg_class c, pg_attribute a +			WHERE c.relname = '{$table_name}' +				AND a.attnum > 0 +				AND a.attrelid = c.oid"; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$column = strtolower(current($row)); +			$columns[$column] = $column; +		} +		$this->db->sql_freeresult($result); + +		return $columns; +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_index_exists($table_name, $index_name) +	{ +		$sql = "SELECT ic.relname as index_name +			FROM pg_class bc, pg_class ic, pg_index i +			WHERE (bc.oid = i.indrelid) +				AND (ic.oid = i.indexrelid) +				AND (bc.relname = '" . $table_name . "') +				AND (i.indisunique != 't') +				AND (i.indisprimary != 't')"; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			// This DBMS prefixes index names with the table name +			$row['index_name'] = $this->strip_table_name_from_index_name($table_name, $row['index_name']); + +			if (strtolower($row['index_name']) == strtolower($index_name)) +			{ +				$this->db->sql_freeresult($result); +				return true; +			} +		} +		$this->db->sql_freeresult($result); + +		return false; +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_unique_index_exists($table_name, $index_name) +	{ +		$sql = "SELECT ic.relname as index_name, i.indisunique +			FROM pg_class bc, pg_class ic, pg_index i +			WHERE (bc.oid = i.indrelid) +				AND (ic.oid = i.indexrelid) +				AND (bc.relname = '" . $table_name . "') +				AND (i.indisprimary != 't')"; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if ($row['indisunique'] != 't') +			{ +				continue; +			} + +			// This DBMS prefixes index names with the table name +			$row['index_name'] = $this->strip_table_name_from_index_name($table_name, $row['index_name']); + +			if (strtolower($row['index_name']) == strtolower($index_name)) +			{ +				$this->db->sql_freeresult($result); +				return true; +			} +		} +		$this->db->sql_freeresult($result); + +		return false; +	} + +	/** +	* Function to prepare some column information for better usage +	* @access private +	*/ +	function sql_prepare_column_data($table_name, $column_name, $column_data) +	{ +		if (strlen($column_name) > 30) +		{ +			trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR); +		} + +		// Get type +		list($column_type, $orig_column_type) = $this->get_column_type($column_data[0]); + +		// Adjust default value if db-dependent specified +		if (is_array($column_data[1])) +		{ +			$column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default']; +		} + +		$sql = " {$column_type} "; + +		$return_array = array( +			'column_type'		=> $column_type, +			'auto_increment'	=> false, +		); + +		if (isset($column_data[2]) && $column_data[2] == 'auto_increment') +		{ +			$default_val = "nextval('{$table_name}_seq')"; +			$return_array['auto_increment'] = true; +		} +		else if (!is_null($column_data[1])) +		{ +			$default_val = "'" . $column_data[1] . "'"; +			$return_array['null'] = 'NOT NULL'; +			$sql .= 'NOT NULL '; +		} +		else +		{ +			// Integers need to have 0 instead of empty string as default +			if (strpos($column_type, 'INT') === 0) +			{ +				$default_val = '0'; +			} +			else +			{ +				$default_val = "'" . $column_data[1] . "'"; +			} +			$return_array['null'] = 'NULL'; +			$sql .= 'NULL '; +		} + +		$return_array['default'] = $default_val; + +		$sql .= "DEFAULT {$default_val}"; + +		// Unsigned? Then add a CHECK contraint +		if (in_array($orig_column_type, $this->unsigned_types)) +		{ +			$return_array['constraint'] = "CHECK ({$column_name} >= 0)"; +			$sql .= " CHECK ({$column_name} >= 0)"; +		} + +		$return_array['column_type_sql'] = $sql; + +		return $return_array; +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_column_add($table_name, $column_name, $column_data, $inline = false) +	{ +		$column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); +		$statements = array(); + +		// Does not support AFTER, only through temporary table +		if (version_compare($this->db->sql_server_info(true), '8.0', '>=')) +		{ +			$statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type_sql']; +		} +		else +		{ +			// old versions cannot add columns with default and null information +			$statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type'] . ' ' . $column_data['constraint']; + +			if (isset($column_data['null'])) +			{ +				if ($column_data['null'] == 'NOT NULL') +				{ +					$statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET NOT NULL'; +				} +			} + +			if (isset($column_data['default'])) +			{ +				$statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; +			} +		} + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_column_remove($table_name, $column_name, $inline = false) +	{ +		$statements = array(); + +		$statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN "' . $column_name . '"'; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_index_drop($table_name, $index_name) +	{ +		$statements = array(); + +		$statements[] = 'DROP INDEX ' . $table_name . '_' . $index_name; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_table_drop($table_name) +	{ +		$statements = array(); + +		if (!$this->sql_table_exists($table_name)) +		{ +			return $this->_sql_run_sql($statements); +		} + +		// the most basic operation, get rid of the table +		$statements[] = 'DROP TABLE ' . $table_name; + +		// PGSQL does not "tightly" bind sequences and tables, we must guess... +		$sql = "SELECT relname +			FROM pg_class +			WHERE relkind = 'S' +				AND relname = '{$table_name}_seq'"; +		$result = $this->db->sql_query($sql); + +		// We don't even care about storing the results. We already know the answer if we get rows back. +		if ($this->db->sql_fetchrow($result)) +		{ +			$statements[] =  "DROP SEQUENCE {$table_name}_seq;\n"; +		} +		$this->db->sql_freeresult($result); + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_create_primary_key($table_name, $column, $inline = false) +	{ +		$statements = array(); + +		$statements[] = 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY (' . implode(', ', $column) . ')'; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_create_unique_index($table_name, $index_name, $column) +	{ +		$statements = array(); + +		$this->check_index_name_length($table_name, $index_name); + +		$statements[] = 'CREATE UNIQUE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_create_index($table_name, $index_name, $column) +	{ +		$statements = array(); + +		$this->check_index_name_length($table_name, $index_name); + +		// remove index length +		$column = preg_replace('#:.*$#', '', $column); + +		$statements[] = 'CREATE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; + +		return $this->_sql_run_sql($statements); +	} + + +	/** +	 * {@inheritDoc} +	 */ +	function sql_list_index($table_name) +	{ +		$index_array = array(); + +		$sql = "SELECT ic.relname as index_name +			FROM pg_class bc, pg_class ic, pg_index i +			WHERE (bc.oid = i.indrelid) +				AND (ic.oid = i.indexrelid) +				AND (bc.relname = '" . $table_name . "') +				AND (i.indisunique != 't') +				AND (i.indisprimary != 't')"; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$row['index_name'] = $this->strip_table_name_from_index_name($table_name, $row['index_name']); + +			$index_array[] = $row['index_name']; +		} +		$this->db->sql_freeresult($result); + +		return array_map('strtolower', $index_array); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_column_change($table_name, $column_name, $column_data, $inline = false) +	{ +		$column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); +		$statements = array(); + +		$sql = 'ALTER TABLE ' . $table_name . ' '; + +		$sql_array = array(); +		$sql_array[] = 'ALTER COLUMN ' . $column_name . ' TYPE ' . $column_data['column_type']; + +		if (isset($column_data['null'])) +		{ +			if ($column_data['null'] == 'NOT NULL') +			{ +				$sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET NOT NULL'; +			} +			else if ($column_data['null'] == 'NULL') +			{ +				$sql_array[] = 'ALTER COLUMN ' . $column_name . ' DROP NOT NULL'; +			} +		} + +		if (isset($column_data['default'])) +		{ +			$sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; +		} + +		// we don't want to double up on constraints if we change different number data types +		if (isset($column_data['constraint'])) +		{ +			$constraint_sql = "SELECT consrc as constraint_data +				FROM pg_constraint, pg_class bc +				WHERE conrelid = bc.oid +					AND bc.relname = '{$table_name}' +					AND NOT EXISTS ( +						SELECT * +						FROM pg_constraint as c, pg_inherits as i +						WHERE i.inhrelid = pg_constraint.conrelid +							AND c.conname = pg_constraint.conname +							AND c.consrc = pg_constraint.consrc +							AND c.conrelid = i.inhparent +					)"; + +			$constraint_exists = false; + +			$result = $this->db->sql_query($constraint_sql); +			while ($row = $this->db->sql_fetchrow($result)) +			{ +				if (trim($row['constraint_data']) == trim($column_data['constraint'])) +				{ +					$constraint_exists = true; +					break; +				} +			} +			$this->db->sql_freeresult($result); + +			if (!$constraint_exists) +			{ +				$sql_array[] = 'ADD ' . $column_data['constraint']; +			} +		} + +		$sql .= implode(', ', $sql_array); + +		$statements[] = $sql; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	* Get a list with existing indexes for the column +	* +	* @param string $table_name +	* @param string $column_name +	* @param bool $unique Should we get unique indexes or normal ones +	* @return array		Array with Index name => columns +	*/ +	public function get_existing_indexes($table_name, $column_name, $unique = false) +	{ +		// Not supported +		throw new \Exception('DBMS is not supported'); +	} +} diff --git a/phpBB/phpbb/db/tools.php b/phpBB/phpbb/db/tools/tools.php index 832a0c510c..37ac0d0468 100644 --- a/phpBB/phpbb/db/tools.php +++ b/phpBB/phpbb/db/tools/tools.php @@ -11,13 +11,13 @@  *  */ -namespace phpbb\db; +namespace phpbb\db\tools;  /**  * Database Tools for handling cross-db actions such as altering columns, etc.  * Currently not supported is returning SQL for creating tables.  */ -class tools +class tools implements tools_interface  {  	/**  	* Current sql layer @@ -36,22 +36,17 @@ class tools  	var $dbms_type_map = array();  	/** -	* Is the used MS SQL Server a SQL Server 2000? -	* @var bool -	*/ -	protected $is_sql_server_2000; - -	/**  	* Get the column types for every database we support  	*  	* @return array  	*/ -	public static function get_dbms_type_map() +	static public function get_dbms_type_map()  	{  		return array(  			'mysql_41'	=> array(  				'INT:'		=> 'int(%d)',  				'BINT'		=> 'bigint(20)', +				'ULINT'		=> 'INT(10) UNSIGNED',  				'UINT'		=> 'mediumint(8) UNSIGNED',  				'UINT:'		=> 'int(%d) UNSIGNED',  				'TINT:'		=> 'tinyint(%d)', @@ -82,6 +77,7 @@ class tools  			'mysql_40'	=> array(  				'INT:'		=> 'int(%d)',  				'BINT'		=> 'bigint(20)', +				'ULINT'		=> 'INT(10) UNSIGNED',  				'UINT'		=> 'mediumint(8) UNSIGNED',  				'UINT:'		=> 'int(%d) UNSIGNED',  				'TINT:'		=> 'tinyint(%d)', @@ -109,69 +105,10 @@ class tools  				'VARBINARY'	=> 'varbinary(255)',  			), -			'mssql'		=> array( -				'INT:'		=> '[int]', -				'BINT'		=> '[float]', -				'UINT'		=> '[int]', -				'UINT:'		=> '[int]', -				'TINT:'		=> '[int]', -				'USINT'		=> '[int]', -				'BOOL'		=> '[int]', -				'VCHAR'		=> '[varchar] (255)', -				'VCHAR:'	=> '[varchar] (%d)', -				'CHAR:'		=> '[char] (%d)', -				'XSTEXT'	=> '[varchar] (1000)', -				'STEXT'		=> '[varchar] (3000)', -				'TEXT'		=> '[varchar] (8000)', -				'MTEXT'		=> '[text]', -				'XSTEXT_UNI'=> '[varchar] (100)', -				'STEXT_UNI'	=> '[varchar] (255)', -				'TEXT_UNI'	=> '[varchar] (4000)', -				'MTEXT_UNI'	=> '[text]', -				'TIMESTAMP'	=> '[int]', -				'DECIMAL'	=> '[float]', -				'DECIMAL:'	=> '[float]', -				'PDECIMAL'	=> '[float]', -				'PDECIMAL:'	=> '[float]', -				'VCHAR_UNI'	=> '[varchar] (255)', -				'VCHAR_UNI:'=> '[varchar] (%d)', -				'VCHAR_CI'	=> '[varchar] (255)', -				'VARBINARY'	=> '[varchar] (255)', -			), - -			'mssqlnative'	=> array( -				'INT:'		=> '[int]', -				'BINT'		=> '[float]', -				'UINT'		=> '[int]', -				'UINT:'		=> '[int]', -				'TINT:'		=> '[int]', -				'USINT'		=> '[int]', -				'BOOL'		=> '[int]', -				'VCHAR'		=> '[varchar] (255)', -				'VCHAR:'	=> '[varchar] (%d)', -				'CHAR:'		=> '[char] (%d)', -				'XSTEXT'	=> '[varchar] (1000)', -				'STEXT'		=> '[varchar] (3000)', -				'TEXT'		=> '[varchar] (8000)', -				'MTEXT'		=> '[text]', -				'XSTEXT_UNI'=> '[varchar] (100)', -				'STEXT_UNI'	=> '[varchar] (255)', -				'TEXT_UNI'	=> '[varchar] (4000)', -				'MTEXT_UNI'	=> '[text]', -				'TIMESTAMP'	=> '[int]', -				'DECIMAL'	=> '[float]', -				'DECIMAL:'	=> '[float]', -				'PDECIMAL'	=> '[float]', -				'PDECIMAL:'	=> '[float]', -				'VCHAR_UNI'	=> '[varchar] (255)', -				'VCHAR_UNI:'=> '[varchar] (%d)', -				'VCHAR_CI'	=> '[varchar] (255)', -				'VARBINARY'	=> '[varchar] (255)', -			), -  			'oracle'	=> array(  				'INT:'		=> 'number(%d)',  				'BINT'		=> 'number(20)', +				'ULINT'		=> 'number(10)',  				'UINT'		=> 'number(8)',  				'UINT:'		=> 'number(%d)',  				'TINT:'		=> 'number(%d)', @@ -202,11 +139,12 @@ class tools  			'sqlite'	=> array(  				'INT:'		=> 'int(%d)',  				'BINT'		=> 'bigint(20)', -				'UINT'		=> 'INTEGER UNSIGNED', //'mediumint(8) UNSIGNED', +				'ULINT'		=> 'INTEGER UNSIGNED', // 'int(10) UNSIGNED', +				'UINT'		=> 'INTEGER UNSIGNED', // 'mediumint(8) UNSIGNED',  				'UINT:'		=> 'INTEGER UNSIGNED', // 'int(%d) UNSIGNED',  				'TINT:'		=> 'tinyint(%d)', -				'USINT'		=> 'INTEGER UNSIGNED', //'mediumint(4) UNSIGNED', -				'BOOL'		=> 'INTEGER UNSIGNED', //'tinyint(1) UNSIGNED', +				'USINT'		=> 'INTEGER UNSIGNED', // 'mediumint(4) UNSIGNED', +				'BOOL'		=> 'INTEGER UNSIGNED', // 'tinyint(1) UNSIGNED',  				'VCHAR'		=> 'varchar(255)',  				'VCHAR:'	=> 'varchar(%d)',  				'CHAR:'		=> 'char(%d)', @@ -218,7 +156,7 @@ class tools  				'STEXT_UNI'	=> 'text(65535)',  				'TEXT_UNI'	=> 'text(65535)',  				'MTEXT_UNI'	=> 'mediumtext(16777215)', -				'TIMESTAMP'	=> 'INTEGER UNSIGNED', //'int(11) UNSIGNED', +				'TIMESTAMP'	=> 'INTEGER UNSIGNED', // 'int(11) UNSIGNED',  				'DECIMAL'	=> 'decimal(5,2)',  				'DECIMAL:'	=> 'decimal(%d,2)',  				'PDECIMAL'	=> 'decimal(6,3)', @@ -232,6 +170,7 @@ class tools  			'sqlite3'	=> array(  				'INT:'		=> 'INT(%d)',  				'BINT'		=> 'BIGINT(20)', +				'ULINT'		=> 'INTEGER UNSIGNED',  				'UINT'		=> 'INTEGER UNSIGNED',  				'UINT:'		=> 'INTEGER UNSIGNED',  				'TINT:'		=> 'TINYINT(%d)', @@ -258,36 +197,6 @@ class tools  				'VCHAR_CI'	=> 'VARCHAR(255)',  				'VARBINARY'	=> 'BLOB',  			), - -			'postgres'	=> array( -				'INT:'		=> 'INT4', -				'BINT'		=> 'INT8', -				'UINT'		=> 'INT4', // unsigned -				'UINT:'		=> 'INT4', // unsigned -				'USINT'		=> 'INT2', // unsigned -				'BOOL'		=> 'INT2', // unsigned -				'TINT:'		=> 'INT2', -				'VCHAR'		=> 'varchar(255)', -				'VCHAR:'	=> 'varchar(%d)', -				'CHAR:'		=> 'char(%d)', -				'XSTEXT'	=> 'varchar(1000)', -				'STEXT'		=> 'varchar(3000)', -				'TEXT'		=> 'varchar(8000)', -				'MTEXT'		=> 'TEXT', -				'XSTEXT_UNI'=> 'varchar(100)', -				'STEXT_UNI'	=> 'varchar(255)', -				'TEXT_UNI'	=> 'varchar(4000)', -				'MTEXT_UNI'	=> 'TEXT', -				'TIMESTAMP'	=> 'INT4', // unsigned -				'DECIMAL'	=> 'decimal(5,2)', -				'DECIMAL:'	=> 'decimal(%d,2)', -				'PDECIMAL'	=> 'decimal(6,3)', -				'PDECIMAL:'	=> 'decimal(%d,3)', -				'VCHAR_UNI'	=> 'varchar(255)', -				'VCHAR_UNI:'=> 'varchar(%d)', -				'VCHAR_CI'	=> 'varchar_ci', -				'VARBINARY'	=> 'bytea', -			),  		);  	} @@ -295,13 +204,7 @@ class tools  	* A list of types being unsigned for better reference in some db's  	* @var array  	*/ -	var $unsigned_types = array('UINT', 'UINT:', 'USINT', 'BOOL', 'TIMESTAMP'); - -	/** -	* A list of supported DBMS. We change this class to support more DBMS, the DBMS itself only need to follow some rules. -	* @var array -	*/ -	var $supported_dbms = array('mssql', 'mssqlnative', 'mysql_40', 'mysql_41', 'oracle', 'postgres', 'sqlite', 'sqlite3'); +	var $unsigned_types = array('ULINT', 'UINT', 'UINT:', 'USINT', 'BOOL', 'TIMESTAMP');  	/**  	* This is set to true if user only wants to return the 'to-be-executed' SQL statement(s) (as an array). @@ -344,15 +247,6 @@ class tools  				$this->sql_layer = 'mysql_41';  			break; -			case 'mssql': -			case 'mssql_odbc': -				$this->sql_layer = 'mssql'; -			break; - -			case 'mssqlnative': -				$this->sql_layer = 'mssqlnative'; -			break; -  			default:  				$this->sql_layer = $this->db->get_sql_layer();  			break; @@ -371,10 +265,8 @@ class tools  	}  	/** -	* Gets a list of tables in the database. -	* -	* @return array		Array of table names  (all lower case) -	*/ +	 * {@inheritDoc} +	 */  	function sql_list_tables()  	{  		switch ($this->db->get_sql_layer()) @@ -398,19 +290,6 @@ class tools  						AND name <> "sqlite_sequence"';  			break; -			case 'mssql': -			case 'mssql_odbc': -			case 'mssqlnative': -				$sql = "SELECT name -					FROM sysobjects -					WHERE type='U'"; -			break; - -			case 'postgres': -				$sql = 'SELECT relname -					FROM pg_stat_user_tables'; -			break; -  			case 'oracle':  				$sql = 'SELECT table_name  					FROM USER_TABLES'; @@ -431,12 +310,8 @@ class tools  	}  	/** -	* Check if table exists -	* -	* -	* @param string	$table_name	The table name to check for -	* @return bool true if table exists, else false -	*/ +	 * {@inheritDoc} +	 */  	function sql_table_exists($table_name)  	{  		$this->db->sql_return_on_error(true); @@ -453,12 +328,8 @@ class tools  	}  	/** -	* Create SQL Table -	* -	* @param string	$table_name	The table name to create -	* @param array	$table_data	Array containing table data. -	* @return array	Statements if $return_statements is true. -	*/ +	 * {@inheritDoc} +	 */  	function sql_create_table($table_name, $table_data)  	{  		// holds the DDL for a column @@ -479,26 +350,7 @@ class tools  		$create_sequence = false;  		// Begin table sql statement -		switch ($this->sql_layer) -		{ -			case 'mssql': -			case 'mssqlnative': -				$table_sql = 'CREATE TABLE [' . $table_name . '] (' . "\n"; -			break; - -			default: -				$table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n"; -			break; -		} - -		if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') -		{ -			if (!isset($table_data['PRIMARY_KEY'])) -			{ -				$table_data['COLUMNS']['mssqlindex'] = array('UINT', null, 'auto_increment'); -				$table_data['PRIMARY_KEY'] = 'mssqlindex'; -			} -		} +		$table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n";  		// Iterate through the columns to create a table  		foreach ($table_data['COLUMNS'] as $column_name => $column_data) @@ -512,17 +364,7 @@ class tools  			}  			// here we add the definition of the new column to the list of columns -			switch ($this->sql_layer) -			{ -				case 'mssql': -				case 'mssqlnative': -					$columns[] = "\t [{$column_name}] " . $prepared_column['column_type_sql_default']; -				break; - -				default: -					$columns[] = "\t {$column_name} " . $prepared_column['column_type_sql']; -				break; -			} +			$columns[] = "\t {$column_name} " . $prepared_column['column_type_sql'];  			// see if we have found a primary key set due to a column definition if we have found it, we can stop looking  			if (!$primary_key_gen) @@ -540,16 +382,6 @@ class tools  		// this makes up all the columns in the create table statement  		$table_sql .= implode(",\n", $columns); -		// Close the table for two DBMS and add to the statements -		switch ($this->sql_layer) -		{ -			case 'mssql': -			case 'mssqlnative': -				$table_sql .= "\n);"; -				$statements[] = $table_sql; -			break; -		} -  		// we have yet to create a primary key for this table,  		// this means that we can add the one we really wanted instead  		if (!$primary_key_gen) @@ -566,27 +398,11 @@ class tools  				{  					case 'mysql_40':  					case 'mysql_41': -					case 'postgres':  					case 'sqlite':  					case 'sqlite3':  						$table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')';  					break; -					case 'mssql': -					case 'mssqlnative': -						// We need the data here -						$old_return_statements = $this->return_statements; -						$this->return_statements = true; - -						$primary_key_stmts = $this->sql_create_primary_key($table_name, $table_data['PRIMARY_KEY']); -						foreach ($primary_key_stmts as $pk_stmt) -						{ -							$statements[] = $pk_stmt; -						} - -						$this->return_statements = $old_return_statements; -					break; -  					case 'oracle':  						$table_sql .= ",\n\t CONSTRAINT pk_{$table_name} PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')';  					break; @@ -610,17 +426,6 @@ class tools  				$statements[] = $table_sql;  			break; -			case 'postgres': -				// do we need to add a sequence for auto incrementing columns? -				if ($create_sequence) -				{ -					$statements[] = "CREATE SEQUENCE {$table_name}_seq;"; -				} - -				$table_sql .= "\n);"; -				$statements[] = $table_sql; -			break; -  			case 'oracle':  				$table_sql .= "\n)";  				$statements[] = $table_sql; @@ -679,27 +484,8 @@ class tools  	}  	/** -	* Handle passed database update array. -	* Expected structure... -	* Key being one of the following -	*	drop_tables: Drop tables -	*	add_tables: Add tables -	*	change_columns: Column changes (only type, not name) -	*	add_columns: Add columns to a table -	*	drop_keys: Dropping keys -	*	drop_columns: Removing/Dropping columns -	*	add_primary_keys: adding primary keys -	*	add_unique_index: adding an unique index -	*	add_index: adding an index (can be column:index_size if you need to provide size) -	* -	* The values are in this format: -	*		{TABLE NAME}		=> array( -	*			{COLUMN NAME}		=> array({COLUMN TYPE}, {DEFAULT VALUE}, {OPTIONAL VARIABLES}), -	*			{KEY/INDEX NAME}	=> array({COLUMN NAMES}), -	*		) -	* -	* For more information have a look at /develop/create_schema_files.php (only available through SVN) -	*/ +	 * {@inheritDoc} +	 */  	function perform_schema_changes($schema_changes)  	{  		if (empty($schema_changes)) @@ -1079,13 +865,9 @@ class tools  	}  	/** -	* Gets a list of columns of a table. -	* -	* @param string $table		Table name -	* -	* @return array				Array of column names (all lower case) -	*/ -	function sql_list_columns($table) +	 * {@inheritDoc} +	 */ +	function sql_list_columns($table_name)  	{  		$columns = array(); @@ -1093,33 +875,13 @@ class tools  		{  			case 'mysql_40':  			case 'mysql_41': -				$sql = "SHOW COLUMNS FROM $table"; -			break; - -			// PostgreSQL has a way of doing this in a much simpler way but would -			// not allow us to support all versions of PostgreSQL -			case 'postgres': -				$sql = "SELECT a.attname -					FROM pg_class c, pg_attribute a -					WHERE c.relname = '{$table}' -						AND a.attnum > 0 -						AND a.attrelid = c.oid"; -			break; - -			// same deal with PostgreSQL, we must perform more complex operations than -			// we technically could -			case 'mssql': -			case 'mssqlnative': -				$sql = "SELECT c.name -					FROM syscolumns c -					LEFT JOIN sysobjects o ON c.id = o.id -					WHERE o.name = '{$table}'"; +				$sql = "SHOW COLUMNS FROM $table_name";  			break;  			case 'oracle':  				$sql = "SELECT column_name  					FROM user_tab_columns -					WHERE LOWER(table_name) = '" . strtolower($table) . "'"; +					WHERE LOWER(table_name) = '" . strtolower($table_name) . "'";  			break;  			case 'sqlite': @@ -1127,7 +889,7 @@ class tools  				$sql = "SELECT sql  					FROM sqlite_master  					WHERE type = 'table' -						AND name = '{$table}'"; +						AND name = '{$table_name}'";  				$result = $this->db->sql_query($sql); @@ -1173,64 +935,22 @@ class tools  	}  	/** -	* Check whether a specified column exist in a table -	* -	* @param string	$table			Table to check -	* @param string	$column_name	Column to check -	* -	* @return bool		True if column exists, false otherwise -	*/ -	function sql_column_exists($table, $column_name) +	 * {@inheritDoc} +	 */ +	function sql_column_exists($table_name, $column_name)  	{ -		$columns = $this->sql_list_columns($table); +		$columns = $this->sql_list_columns($table_name);  		return isset($columns[$column_name]);  	}  	/** -	* Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes. -	* -	* @param string	$table_name		Table to check the index at -	* @param string	$index_name		The index name to check -	* -	* @return bool True if index exists, else false -	*/ +	 * {@inheritDoc} +	 */  	function sql_index_exists($table_name, $index_name)  	{ -		if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') -		{ -			$sql = "EXEC sp_statistics '$table_name'"; -			$result = $this->db->sql_query($sql); - -			while ($row = $this->db->sql_fetchrow($result)) -			{ -				if ($row['TYPE'] == 3) -				{ -					if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) -					{ -						$this->db->sql_freeresult($result); -						return true; -					} -				} -			} -			$this->db->sql_freeresult($result); - -			return false; -		} -  		switch ($this->sql_layer)  		{ -			case 'postgres': -				$sql = "SELECT ic.relname as index_name -					FROM pg_class bc, pg_class ic, pg_index i -					WHERE (bc.oid = i.indrelid) -						AND (ic.oid = i.indexrelid) -						AND (bc.relname = '" . $table_name . "') -						AND (i.indisunique != 't') -						AND (i.indisprimary != 't')"; -				$col = 'index_name'; -			break; -  			case 'mysql_40':  			case 'mysql_41':  				$sql = 'SHOW KEYS @@ -1266,7 +986,6 @@ class tools  			switch ($this->sql_layer)  			{  				case 'oracle': -				case 'postgres':  				case 'sqlite':  				case 'sqlite3':  					$row[$col] = substr($row[$col], strlen($table_name) + 1); @@ -1285,48 +1004,12 @@ class tools  	}  	/** -	* Check if a specified index exists in table. Does not return PRIMARY KEY indexes. -	* -	* @param string	$table_name		Table to check the index at -	* @param string	$index_name		The index name to check -	* -	* @return bool True if index exists, else false -	*/ +	 * {@inheritDoc} +	 */  	function sql_unique_index_exists($table_name, $index_name)  	{ -		if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') -		{ -			$sql = "EXEC sp_statistics '$table_name'"; -			$result = $this->db->sql_query($sql); - -			while ($row = $this->db->sql_fetchrow($result)) -			{ -				// Usually NON_UNIQUE is the column we want to check, but we allow for both -				if ($row['TYPE'] == 3) -				{ -					if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) -					{ -						$this->db->sql_freeresult($result); -						return true; -					} -				} -			} -			$this->db->sql_freeresult($result); -			return false; -		} -  		switch ($this->sql_layer)  		{ -			case 'postgres': -				$sql = "SELECT ic.relname as index_name, i.indisunique -					FROM pg_class bc, pg_class ic, pg_index i -					WHERE (bc.oid = i.indrelid) -						AND (ic.oid = i.indexrelid) -						AND (bc.relname = '" . $table_name . "') -						AND (i.indisprimary != 't')"; -				$col = 'index_name'; -			break; -  			case 'mysql_40':  			case 'mysql_41':  				$sql = 'SHOW KEYS @@ -1363,11 +1046,6 @@ class tools  				continue;  			} -			if ($this->sql_layer == 'postgres' && $row['indisunique'] != 't') -			{ -				continue; -			} -  			// These DBMS prefix index name with the table name  			switch ($this->sql_layer)  			{ @@ -1383,7 +1061,6 @@ class tools  					}  				break; -				case 'postgres':  				case 'sqlite':  				case 'sqlite3':  					$row[$col] = substr($row[$col], strlen($table_name) + 1); @@ -1444,7 +1121,7 @@ class tools  		}  		// Get type -		list($column_type, $orig_column_type) = $this->get_column_type($column_data[0]); +		list($column_type) = $this->get_column_type($column_data[0]);  		// Adjust default value if db-dependent specified  		if (is_array($column_data[1])) @@ -1458,50 +1135,6 @@ class tools  		switch ($this->sql_layer)  		{ -			case 'mssql': -			case 'mssqlnative': -				$sql .= " {$column_type} "; -				$sql_default = " {$column_type} "; - -				// For adding columns we need the default definition -				if (!is_null($column_data[1])) -				{ -					// For hexadecimal values do not use single quotes -					if (strpos($column_data[1], '0x') === 0) -					{ -						$return_array['default'] = 'DEFAULT (' . $column_data[1] . ') '; -						$sql_default .= $return_array['default']; -					} -					else -					{ -						$return_array['default'] = 'DEFAULT (' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ') '; -						$sql_default .= $return_array['default']; -					} -				} - -				if (isset($column_data[2]) && $column_data[2] == 'auto_increment') -				{ -//					$sql .= 'IDENTITY (1, 1) '; -					$sql_default .= 'IDENTITY (1, 1) '; -				} - -				$return_array['textimage'] = $column_type === '[text]'; - -				if (!is_null($column_data[1]) || (isset($column_data[2]) && $column_data[2] == 'auto_increment')) -				{ -					$sql .= 'NOT NULL'; -					$sql_default .= 'NOT NULL'; -				} -				else -				{ -					$sql .= 'NULL'; -					$sql_default .= 'NULL'; -				} - -				$return_array['column_type_sql_default'] = $sql_default; - -			break; -  			case 'mysql_40':  			case 'mysql_41':  				$sql .= " {$column_type} "; @@ -1560,51 +1193,6 @@ class tools  			break; -			case 'postgres': -				$return_array['column_type'] = $column_type; - -				$sql .= " {$column_type} "; - -				$return_array['auto_increment'] = false; -				if (isset($column_data[2]) && $column_data[2] == 'auto_increment') -				{ -					$default_val = "nextval('{$table_name}_seq')"; -					$return_array['auto_increment'] = true; -				} -				else if (!is_null($column_data[1])) -				{ -					$default_val = "'" . $column_data[1] . "'"; -					$return_array['null'] = 'NOT NULL'; -					$sql .= 'NOT NULL '; -				} -				else -				{ -					// Integers need to have 0 instead of empty string as default -					if (strpos($column_type, 'INT') === 0) -					{ -						$default_val = '0'; -					} -					else -					{ -						$default_val = "'" . $column_data[1] . "'"; -					} -					$return_array['null'] = 'NULL'; -					$sql .= 'NULL '; -				} - -				$return_array['default'] = $default_val; - -				$sql .= "DEFAULT {$default_val}"; - -				// Unsigned? Then add a CHECK contraint -				if (in_array($orig_column_type, $this->unsigned_types)) -				{ -					$return_array['constraint'] = "CHECK ({$column_name} >= 0)"; -					$sql .= " CHECK ({$column_name} >= 0)"; -				} - -			break; -  			case 'sqlite':  			case 'sqlite3':  				$return_array['primary_key_set'] = false; @@ -1646,6 +1234,7 @@ class tools  	*/  	function get_column_type($column_map_type)  	{ +		$column_type = '';  		if (strpos($column_map_type, ':') !== false)  		{  			list($orig_column_type, $column_length) = explode(':', $column_map_type); @@ -1697,8 +1286,8 @@ class tools  	}  	/** -	* Add new column -	*/ +	 * {@inheritDoc} +	 */  	function sql_column_add($table_name, $column_name, $column_data, $inline = false)  	{  		$column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); @@ -1706,12 +1295,6 @@ class tools  		switch ($this->sql_layer)  		{ -			case 'mssql': -			case 'mssqlnative': -				// Does not support AFTER, only through temporary table -				$statements[] = 'ALTER TABLE [' . $table_name . '] ADD [' . $column_name . '] ' . $column_data['column_type_sql_default']; -			break; -  			case 'mysql_40':  			case 'mysql_41':  				$after = (!empty($column_data['after'])) ? ' AFTER ' . $column_data['after'] : ''; @@ -1723,33 +1306,6 @@ class tools  				$statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' ' . $column_data['column_type_sql'];  			break; -			case 'postgres': -				// Does not support AFTER, only through temporary table -				if (version_compare($this->db->sql_server_info(true), '8.0', '>=')) -				{ -					$statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type_sql']; -				} -				else -				{ -					// old versions cannot add columns with default and null information -					$statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type'] . ' ' . $column_data['constraint']; - -					if (isset($column_data['null'])) -					{ -						if ($column_data['null'] == 'NOT NULL') -						{ -							$statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET NOT NULL'; -						} -					} - -					if (isset($column_data['default'])) -					{ -						$statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; -					} -				} - -			break; -  			case 'sqlite':  				if ($inline && $this->return_statements)  				{ @@ -1815,59 +1371,14 @@ class tools  	}  	/** -	* Drop column -	*/ +	 * {@inheritDoc} +	 */  	function sql_column_remove($table_name, $column_name, $inline = false)  	{  		$statements = array();  		switch ($this->sql_layer)  		{ -			case 'mssql': -			case 'mssqlnative': -				// We need the data here -				$old_return_statements = $this->return_statements; -				$this->return_statements = true; - -				$indexes = $this->get_existing_indexes($table_name, $column_name); -				$indexes = array_merge($indexes, $this->get_existing_indexes($table_name, $column_name, true)); - -				// Drop any indexes -				$recreate_indexes = array(); -				if (!empty($indexes)) -				{ -					foreach ($indexes as $index_name => $index_data) -					{ -						$result = $this->sql_index_drop($table_name, $index_name); -						$statements = array_merge($statements, $result); -						if (sizeof($index_data) > 1) -						{ -							// Remove this column from the index and recreate it -							$recreate_indexes[$index_name] = array_diff($index_data, array($column_name)); -						} -					} -				} - -				// Drop default value constraint -				$result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name); -				$statements = array_merge($statements, $result); - -				// Remove the column -				$statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']'; - -				if (!empty($recreate_indexes)) -				{ -					// Recreate indexes after we removed the column -					foreach ($recreate_indexes as $index_name => $index_data) -					{ -						$result = $this->sql_create_index($table_name, $index_name, $index_data); -						$statements = array_merge($statements, $result); -					} -				} - -				$this->return_statements = $old_return_statements; -			break; -  			case 'mysql_40':  			case 'mysql_41':  				$statements[] = 'ALTER TABLE `' . $table_name . '` DROP COLUMN `' . $column_name . '`'; @@ -1877,10 +1388,6 @@ class tools  				$statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN ' . $column_name;  			break; -			case 'postgres': -				$statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN "' . $column_name . '"'; -			break; -  			case 'sqlite':  			case 'sqlite3': @@ -1944,26 +1451,20 @@ class tools  	}  	/** -	* Drop Index -	*/ +	 * {@inheritDoc} +	 */  	function sql_index_drop($table_name, $index_name)  	{  		$statements = array();  		switch ($this->sql_layer)  		{ -			case 'mssql': -			case 'mssqlnative': -				$statements[] = 'DROP INDEX ' . $table_name . '.' . $index_name; -			break; -  			case 'mysql_40':  			case 'mysql_41':  				$statements[] = 'DROP INDEX ' . $index_name . ' ON ' . $table_name;  			break;  			case 'oracle': -			case 'postgres':  			case 'sqlite':  			case 'sqlite3':  				$statements[] = 'DROP INDEX ' . $table_name . '_' . $index_name; @@ -1974,8 +1475,8 @@ class tools  	}  	/** -	* Drop Table -	*/ +	 * {@inheritDoc} +	 */  	function sql_table_drop($table_name)  	{  		$statements = array(); @@ -2005,52 +1506,25 @@ class tools  				}  				$this->db->sql_freeresult($result);  			break; - -			case 'postgres': -				// PGSQL does not "tightly" bind sequences and tables, we must guess... -				$sql = "SELECT relname -					FROM pg_class -					WHERE relkind = 'S' -						AND relname = '{$table_name}_seq'"; -				$result = $this->db->sql_query($sql); - -				// We don't even care about storing the results. We already know the answer if we get rows back. -				if ($this->db->sql_fetchrow($result)) -				{ -					$statements[] =  "DROP SEQUENCE {$table_name}_seq;\n"; -				} -				$this->db->sql_freeresult($result); -			break;  		}  		return $this->_sql_run_sql($statements);  	}  	/** -	* Add primary key -	*/ +	 * {@inheritDoc} +	 */  	function sql_create_primary_key($table_name, $column, $inline = false)  	{  		$statements = array();  		switch ($this->sql_layer)  		{ -			case 'postgres':  			case 'mysql_40':  			case 'mysql_41':  				$statements[] = 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY (' . implode(', ', $column) . ')';  			break; -			case 'mssql': -			case 'mssqlnative': -				$sql = "ALTER TABLE [{$table_name}] WITH NOCHECK ADD "; -				$sql .= "CONSTRAINT [PK_{$table_name}] PRIMARY KEY  CLUSTERED ("; -				$sql .= '[' . implode("],\n\t\t[", $column) . ']'; -				$sql .= ')'; - -				$statements[] = $sql; -			break; -  			case 'oracle':  				$statements[] = 'ALTER TABLE ' . $table_name . ' add CONSTRAINT pk_' . $table_name . ' PRIMARY KEY (' . implode(', ', $column) . ')';  			break; @@ -2111,22 +1585,16 @@ class tools  	}  	/** -	* Add unique index -	*/ +	 * {@inheritDoc} +	 */  	function sql_create_unique_index($table_name, $index_name, $column)  	{  		$statements = array(); -		$table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) -		if (strlen($table_name . '_' . $index_name) - strlen($table_prefix) > 24) -		{ -			$max_length = strlen($table_prefix) + 24; -			trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); -		} +		$this->check_index_name_length($table_name, $index_name);  		switch ($this->sql_layer)  		{ -			case 'postgres':  			case 'oracle':  			case 'sqlite':  			case 'sqlite3': @@ -2137,29 +1605,19 @@ class tools  			case 'mysql_41':  				$statements[] = 'ALTER TABLE ' . $table_name . ' ADD UNIQUE INDEX ' . $index_name . '(' . implode(', ', $column) . ')';  			break; - -			case 'mssql': -			case 'mssqlnative': -				$statements[] = 'CREATE UNIQUE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])'; -			break;  		}  		return $this->_sql_run_sql($statements);  	}  	/** -	* Add index -	*/ +	 * {@inheritDoc} +	 */  	function sql_create_index($table_name, $index_name, $column)  	{  		$statements = array(); -		$table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) -		if (strlen($table_name . $index_name) - strlen($table_prefix) > 24) -		{ -			$max_length = strlen($table_prefix) + 24; -			trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); -		} +		$this->check_index_name_length($table_name, $index_name);  		// remove index length unless MySQL4  		if ('mysql_40' != $this->sql_layer) @@ -2169,7 +1627,6 @@ class tools  		switch ($this->sql_layer)  		{ -			case 'postgres':  			case 'oracle':  			case 'sqlite':  			case 'sqlite3': @@ -2190,99 +1647,79 @@ class tools  			case 'mysql_41':  				$statements[] = 'ALTER TABLE ' . $table_name . ' ADD INDEX ' . $index_name . ' (' . implode(', ', $column) . ')';  			break; - -			case 'mssql': -			case 'mssqlnative': -				$statements[] = 'CREATE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])'; -			break;  		}  		return $this->_sql_run_sql($statements);  	}  	/** -	* List all of the indices that belong to a table, -	* does not count: -	* * UNIQUE indices -	* * PRIMARY keys -	*/ +	 * Check whether the index name is too long +	 * +	 * @param string $table_name +	 * @param string $index_name +	 */ +	protected function check_index_name_length($table_name, $index_name) +	{ +		$table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) +		if (strlen($table_name . $index_name) - strlen($table_prefix) > 24) +		{ +			$max_length = strlen($table_prefix) + 24; +			trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); +		} +	} + +	/** +	 * {@inheritDoc} +	 */  	function sql_list_index($table_name)  	{  		$index_array = array(); -		if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') -		{ -			$sql = "EXEC sp_statistics '$table_name'"; -			$result = $this->db->sql_query($sql); -			while ($row = $this->db->sql_fetchrow($result)) -			{ -				if ($row['TYPE'] == 3) -				{ -					$index_array[] = $row['INDEX_NAME']; -				} -			} -			$this->db->sql_freeresult($result); -		} -		else +		switch ($this->sql_layer)  		{ -			switch ($this->sql_layer) -			{ -				case 'postgres': -					$sql = "SELECT ic.relname as index_name -						FROM pg_class bc, pg_class ic, pg_index i -						WHERE (bc.oid = i.indrelid) -							AND (ic.oid = i.indexrelid) -							AND (bc.relname = '" . $table_name . "') -							AND (i.indisunique != 't') -							AND (i.indisprimary != 't')"; -					$col = 'index_name'; +			case 'mysql_40': +			case 'mysql_41': +				$sql = 'SHOW KEYS +					FROM ' . $table_name; +				$col = 'Key_name';  				break; -				case 'mysql_40': -				case 'mysql_41': -					$sql = 'SHOW KEYS -						FROM ' . $table_name; -					$col = 'Key_name'; +			case 'oracle': +				$sql = "SELECT index_name +					FROM user_indexes +					WHERE table_name = '" . strtoupper($table_name) . "' +						AND generated = 'N' +						AND uniqueness = 'NONUNIQUE'"; +				$col = 'index_name';  				break; -				case 'oracle': -					$sql = "SELECT index_name -						FROM user_indexes -						WHERE table_name = '" . strtoupper($table_name) . "' -							AND generated = 'N' -							AND uniqueness = 'NONUNIQUE'"; -					$col = 'index_name'; +			case 'sqlite': +			case 'sqlite3': +				$sql = "PRAGMA index_info('" . $table_name . "');"; +				$col = 'name';  				break; +		} -				case 'sqlite': -				case 'sqlite3': -					$sql = "PRAGMA index_info('" . $table_name . "');"; -					$col = 'name'; -				break; +		$result = $this->db->sql_query($sql); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique']) +			{ +				continue;  			} -			$result = $this->db->sql_query($sql); -			while ($row = $this->db->sql_fetchrow($result)) +			switch ($this->sql_layer)  			{ -				if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique']) -				{ -					continue; -				} - -				switch ($this->sql_layer) -				{ -					case 'oracle': -					case 'postgres': -					case 'sqlite': -					case 'sqlite3': -						$row[$col] = substr($row[$col], strlen($table_name) + 1); +				case 'oracle': +				case 'sqlite': +				case 'sqlite3': +					$row[$col] = substr($row[$col], strlen($table_name) + 1);  					break; -				} - -				$index_array[] = $row[$col];  			} -			$this->db->sql_freeresult($result); + +			$index_array[] = $row[$col];  		} +		$this->db->sql_freeresult($result);  		return array_map('strtolower', $index_array);  	} @@ -2300,8 +1737,8 @@ class tools  	}  	/** -	* Change column type (not name!) -	*/ +	 * {@inheritDoc} +	 */  	function sql_column_change($table_name, $column_name, $column_data, $inline = false)  	{  		$original_column_data = $column_data; @@ -2310,62 +1747,6 @@ class tools  		switch ($this->sql_layer)  		{ -			case 'mssql': -			case 'mssqlnative': -				// We need the data here -				$old_return_statements = $this->return_statements; -				$this->return_statements = true; - -				$indexes = $this->get_existing_indexes($table_name, $column_name); -				$unique_indexes = $this->get_existing_indexes($table_name, $column_name, true); - -				// Drop any indexes -				if (!empty($indexes) || !empty($unique_indexes)) -				{ -					$drop_indexes = array_merge(array_keys($indexes), array_keys($unique_indexes)); -					foreach ($drop_indexes as $index_name) -					{ -						$result = $this->sql_index_drop($table_name, $index_name); -						$statements = array_merge($statements, $result); -					} -				} - -				// Drop default value constraint -				$result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name); -				$statements = array_merge($statements, $result); - -				// Change the column -				$statements[] = 'ALTER TABLE [' . $table_name . '] ALTER COLUMN [' . $column_name . '] ' . $column_data['column_type_sql']; - -				if (!empty($column_data['default'])) -				{ -					// Add new default value constraint -					$statements[] = 'ALTER TABLE [' . $table_name . '] ADD CONSTRAINT [DF_' . $table_name . '_' . $column_name . '_1] ' . $column_data['default'] . ' FOR [' . $column_name . ']'; -				} - -				if (!empty($indexes)) -				{ -					// Recreate indexes after we changed the column -					foreach ($indexes as $index_name => $index_data) -					{ -						$result = $this->sql_create_index($table_name, $index_name, $index_data); -						$statements = array_merge($statements, $result); -					} -				} - -				if (!empty($unique_indexes)) -				{ -					// Recreate unique indexes after we changed the column -					foreach ($unique_indexes as $index_name => $index_data) -					{ -						$result = $this->sql_create_unique_index($table_name, $index_name, $index_data); -						$statements = array_merge($statements, $result); -					} -				} - -				$this->return_statements = $old_return_statements; -			break; -  			case 'mysql_40':  			case 'mysql_41':  				$statements[] = 'ALTER TABLE `' . $table_name . '` CHANGE `' . $column_name . '` `' . $column_name . '` ' . $column_data['column_type_sql']; @@ -2437,69 +1818,6 @@ class tools  				$this->return_statements = $old_return_statements;  			break; -			case 'postgres': -				$sql = 'ALTER TABLE ' . $table_name . ' '; - -				$sql_array = array(); -				$sql_array[] = 'ALTER COLUMN ' . $column_name . ' TYPE ' . $column_data['column_type']; - -				if (isset($column_data['null'])) -				{ -					if ($column_data['null'] == 'NOT NULL') -					{ -						$sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET NOT NULL'; -					} -					else if ($column_data['null'] == 'NULL') -					{ -						$sql_array[] = 'ALTER COLUMN ' . $column_name . ' DROP NOT NULL'; -					} -				} - -				if (isset($column_data['default'])) -				{ -					$sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; -				} - -				// we don't want to double up on constraints if we change different number data types -				if (isset($column_data['constraint'])) -				{ -					$constraint_sql = "SELECT consrc as constraint_data -								FROM pg_constraint, pg_class bc -								WHERE conrelid = bc.oid -									AND bc.relname = '{$table_name}' -									AND NOT EXISTS ( -										SELECT * -											FROM pg_constraint as c, pg_inherits as i -											WHERE i.inhrelid = pg_constraint.conrelid -												AND c.conname = pg_constraint.conname -												AND c.consrc = pg_constraint.consrc -												AND c.conrelid = i.inhparent -									)"; - -					$constraint_exists = false; - -					$result = $this->db->sql_query($constraint_sql); -					while ($row = $this->db->sql_fetchrow($result)) -					{ -						if (trim($row['constraint_data']) == trim($column_data['constraint'])) -						{ -							$constraint_exists = true; -							break; -						} -					} -					$this->db->sql_freeresult($result); - -					if (!$constraint_exists) -					{ -						$sql_array[] = 'ADD ' . $column_data['constraint']; -					} -				} - -				$sql .= implode(', ', $sql_array); - -				$statements[] = $sql; -			break; -  			case 'sqlite':  			case 'sqlite3': @@ -2568,52 +1886,6 @@ class tools  	}  	/** -	* Get queries to drop the default constraints of a column -	* -	* We need to drop the default constraints of a column, -	* before being able to change their type or deleting them. -	* -	* @param string $table_name -	* @param string $column_name -	* @return array		Array with SQL statements -	*/ -	protected function mssql_get_drop_default_constraints_queries($table_name, $column_name) -	{ -		$statements = array(); -		if ($this->mssql_is_sql_server_2000()) -		{ -			// http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx -			// Deprecated in SQL Server 2005 -			$sql = "SELECT so.name AS def_name -				FROM sysobjects so -				JOIN sysconstraints sc ON so.id = sc.constid -				WHERE object_name(so.parent_obj) = '{$table_name}' -					AND so.xtype = 'D' -					AND sc.colid = (SELECT colid FROM syscolumns -						WHERE id = object_id('{$table_name}') -							AND name = '{$column_name}')"; -		} -		else -		{ -			$sql = "SELECT dobj.name AS def_name -				FROM sys.columns col -					LEFT OUTER JOIN sys.objects dobj ON (dobj.object_id = col.default_object_id AND dobj.type = 'D') -				WHERE col.object_id = object_id('{$table_name}') -					AND col.name = '{$column_name}' -					AND dobj.name IS NOT NULL"; -		} - -		$result = $this->db->sql_query($sql); -		while ($row = $this->db->sql_fetchrow($result)) -		{ -			$statements[] = 'ALTER TABLE [' . $table_name . '] DROP CONSTRAINT [' . $row['def_name'] . ']'; -		} -		$this->db->sql_freeresult($result); - -		return $statements; -	} - -	/**  	* Get a list with existing indexes for the column  	*  	* @param string $table_name @@ -2627,7 +1899,6 @@ class tools  		{  			case 'mysql_40':  			case 'mysql_41': -			case 'postgres':  			case 'sqlite':  			case 'sqlite3':  				// Not supported @@ -2640,40 +1911,6 @@ class tools  		switch ($this->sql_layer)  		{ -			case 'mssql': -			case 'mssqlnative': -				if ($this->mssql_is_sql_server_2000()) -				{ -					// http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx -					// Deprecated in SQL Server 2005 -					$sql = "SELECT DISTINCT ix.name AS phpbb_index_name -					FROM sysindexes ix -					INNER JOIN sysindexkeys ixc -						ON ixc.id = ix.id -							AND ixc.indid = ix.indid -					INNER JOIN syscolumns cols -						ON cols.colid = ixc.colid -							AND cols.id = ix.id -					WHERE ix.id = object_id('{$table_name}') -						AND cols.name = '{$column_name}' -						AND INDEXPROPERTY(ix.id, ix.name, 'IsUnique') = " . ($unique ? '1' : '0'); -				} -				else -				{ -					$sql = "SELECT DISTINCT ix.name AS phpbb_index_name -					FROM sys.indexes ix -					INNER JOIN sys.index_columns ixc -						ON ixc.object_id = ix.object_id -							AND ixc.index_id = ix.index_id -					INNER JOIN sys.columns cols -						ON cols.column_id = ixc.column_id -							AND cols.object_id = ix.object_id -					WHERE ix.object_id = object_id('{$table_name}') -						AND cols.name = '{$column_name}' -						AND ix.is_unique = " . ($unique ? '1' : '0'); -				} -			break; -  			case 'oracle':  				$sql = "SELECT ix.index_name  AS phpbb_index_name, ix.uniqueness AS is_unique  					FROM all_ind_columns ixc, all_indexes ix @@ -2700,36 +1937,6 @@ class tools  		switch ($this->sql_layer)  		{ -			case 'mssql': -			case 'mssqlnative': -				if ($this->mssql_is_sql_server_2000()) -				{ -					$sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name -						FROM sysindexes ix -						INNER JOIN sysindexkeys ixc -							ON ixc.id = ix.id -								AND ixc.indid = ix.indid -						INNER JOIN syscolumns cols -							ON cols.colid = ixc.colid -								AND cols.id = ix.id -						WHERE ix.id = object_id('{$table_name}') -							AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes)); -				} -				else -				{ -					$sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name -						FROM sys.indexes ix -						INNER JOIN sys.index_columns ixc -							ON ixc.object_id = ix.object_id -								AND ixc.index_id = ix.index_id -						INNER JOIN sys.columns cols -							ON cols.column_id = ixc.column_id -								AND cols.object_id = ix.object_id -						WHERE ix.object_id = object_id('{$table_name}') -							AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes)); -				} -			break; -  			case 'oracle':  				$sql = "SELECT index_name AS phpbb_index_name, column_name AS phpbb_column_name  					FROM all_ind_columns @@ -2749,25 +1956,6 @@ class tools  	}  	/** -	* Is the used MS SQL Server a SQL Server 2000? -	* -	* @return bool -	*/ -	protected function mssql_is_sql_server_2000() -	{ -		if ($this->is_sql_server_2000 === null) -		{ -			$sql = "SELECT CAST(SERVERPROPERTY('productversion') AS VARCHAR(25)) AS mssql_version"; -			$result = $this->db->sql_query($sql); -			$properties = $this->db->sql_fetchrow($result); -			$this->db->sql_freeresult($result); -			$this->is_sql_server_2000 = $properties['mssql_version'][0] == '8'; -		} - -		return $this->is_sql_server_2000; -	} - -	/**  	* Returns the Queries which are required to recreate a table including indexes  	*  	* @param string $table_name diff --git a/phpBB/phpbb/db/tools/tools_interface.php b/phpBB/phpbb/db/tools/tools_interface.php new file mode 100644 index 0000000000..f153f73a54 --- /dev/null +++ b/phpBB/phpbb/db/tools/tools_interface.php @@ -0,0 +1,202 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\tools; + +/** + * Interface for a Database Tools for handling cross-db actions such as altering columns, etc. + */ +interface tools_interface +{ +	/** +	 * Handle passed database update array. +	 * Expected structure... +	 * Key being one of the following +	 *	drop_tables: Drop tables +	 *	add_tables: Add tables +	 *	change_columns: Column changes (only type, not name) +	 *	add_columns: Add columns to a table +	 *	drop_keys: Dropping keys +	 *	drop_columns: Removing/Dropping columns +	 *	add_primary_keys: adding primary keys +	 *	add_unique_index: adding an unique index +	 *	add_index: adding an index (can be column:index_size if you need to provide size) +	 * +	 * The values are in this format: +	 *		{TABLE NAME}		=> array( +	 *			{COLUMN NAME}		=> array({COLUMN TYPE}, {DEFAULT VALUE}, {OPTIONAL VARIABLES}), +	 *			{KEY/INDEX NAME}	=> array({COLUMN NAMES}), +	 *		) +	 * +	 * +	 * @param array $schema_changes +	 * @return null +	 */ +	public function perform_schema_changes($schema_changes); + +	/** +	 * Gets a list of tables in the database. +	 * +	 * @return array		Array of table names  (all lower case) +	 */ +	public function sql_list_tables(); + +	/** +	 * Check if table exists +	 * +	 * @param string	$table_name	The table name to check for +	 * @return bool true if table exists, else false +	 */ +	public function sql_table_exists($table_name); + +	/** +	 * Create SQL Table +	 * +	 * @param string	$table_name		The table name to create +	 * @param array		$table_data		Array containing table data. +	 * @return array|true	Statements to run, or true if the statements have been executed +	 */ +	public function sql_create_table($table_name, $table_data); + +	/** +	 * Drop Table +	 * +	 * @param string	$table_name		The table name to drop +	 * @return array|true	Statements to run, or true if the statements have been executed +	 */ +	public function sql_table_drop($table_name); + +	/** +	 * Gets a list of columns of a table. +	 * +	 * @param string $table_name	Table name +	 * @return array		Array of column names (all lower case) +	 */ +	public function sql_list_columns($table_name); + +	/** +	 * Check whether a specified column exist in a table +	 * +	 * @param string	$table_name		Table to check +	 * @param string	$column_name	Column to check +	 * @return bool		True if column exists, false otherwise +	 */ +	public function sql_column_exists($table_name, $column_name); + +	/** +	 * Add new column +	 * +	 * @param string	$table_name		Table to modify +	 * @param string	$column_name	Name of the column to add +	 * @param array		$column_data	Column data +	 * @param bool		$inline			Whether the query should actually be run, +	 *						or return a string for adding the column +	 * @return array|true	Statements to run, or true if the statements have been executed +	 */ +	public function sql_column_add($table_name, $column_name, $column_data, $inline = false); + +	/** +	 * Change column type (not name!) +	 * +	 * @param string	$table_name		Table to modify +	 * @param string	$column_name	Name of the column to modify +	 * @param array		$column_data	Column data +	 * @param bool		$inline			Whether the query should actually be run, +	 *						or return a string for modifying the column +	 * @return array|true	Statements to run, or true if the statements have been executed +	 */ +	public function sql_column_change($table_name, $column_name, $column_data, $inline = false); + +	/** +	 * Drop column +	 * +	 * @param string	$table_name		Table to modify +	 * @param string	$column_name	Name of the column to drop +	 * @param bool		$inline			Whether the query should actually be run, +	 *						or return a string for deleting the column +	 * @return array|true	Statements to run, or true if the statements have been executed +	 */ +	public function sql_column_remove($table_name, $column_name, $inline = false); + +	/** +	 * List all of the indices that belong to a table +	 * +	 * NOTE: does not list +	 * - UNIQUE indices +	 * - PRIMARY keys +	 * +	 * @param string	$table_name		Table to check +	 * @return array		Array with index names +	 */ +	public function sql_list_index($table_name); + +	/** +	 * Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes. +	 * +	 * @param string	$table_name		Table to check the index at +	 * @param string	$index_name		The index name to check +	 * @return bool			True if index exists, else false +	 */ +	public function sql_index_exists($table_name, $index_name); + +	/** +	 * Add index +	 * +	 * @param string	$table_name		Table to modify +	 * @param string	$index_name		Name of the index to create +	 * @param string|array	$column		Either a string with a column name, or an array with columns +	 * @return array|true	Statements to run, or true if the statements have been executed +	 */ +	public function sql_create_index($table_name, $index_name, $column); + +	/** +	 * Drop Index +	 * +	 * @param string	$table_name		Table to modify +	 * @param string	$index_name		Name of the index to delete +	 * @return array|true	Statements to run, or true if the statements have been executed +	 */ +	public function sql_index_drop($table_name, $index_name); + +	/** +	 * Check if a specified index exists in table. +	 * +	 * NOTE: Does not return normal and PRIMARY KEY indexes +	 * +	 * @param string	$table_name		Table to check the index at +	 * @param string	$index_name		The index name to check +	 * @return bool True if index exists, else false +	 */ +	public function sql_unique_index_exists($table_name, $index_name); + +	/** +	 * Add unique index +	 * +	 * @param string	$table_name		Table to modify +	 * @param string	$index_name		Name of the unique index to create +	 * @param string|array	$column		Either a string with a column name, or an array with columns +	 * @return array|true	Statements to run, or true if the statements have been executed +	 */ +	public function sql_create_unique_index($table_name, $index_name, $column); + +	/** +	 * Add primary key +	 * +	 * @param string	$table_name		Table to modify +	 * @param string|array	$column		Either a string with a column name, or an array with columns +	 * @param bool		$inline			Whether the query should actually be run, +	 *						or return a string for creating the key +	 * @return array|true	Statements to run, or true if the statements have been executed +	 */ +	public function sql_create_primary_key($table_name, $column, $inline = false); +} | 
