diff options
Diffstat (limited to 'phpBB/phpbb')
149 files changed, 10808 insertions, 1982 deletions
diff --git a/phpBB/phpbb/auth/auth.php b/phpBB/phpbb/auth/auth.php index b59f0e60ec..92c19fd5f7 100644 --- a/phpBB/phpbb/auth/auth.php +++ b/phpBB/phpbb/auth/auth.php @@ -929,6 +929,7 @@ class auth  	{  		global $db, $user, $phpbb_root_path, $phpEx, $phpbb_container; +		/* @var $provider_collection \phpbb\auth\provider_collection */  		$provider_collection = $phpbb_container->get('auth.provider_collection');  		$provider = $provider_collection->get_provider(); diff --git a/phpBB/phpbb/auth/provider/db.php b/phpBB/phpbb/auth/provider/db.php index d8c5fb72de..1adf85ee05 100644 --- a/phpBB/phpbb/auth/provider/db.php +++ b/phpBB/phpbb/auth/provider/db.php @@ -155,6 +155,7 @@ class db extends \phpbb\auth\provider\base  		// Every auth module is able to define what to do by itself...  		if ($show_captcha)  		{ +			/* @var $captcha_factory \phpbb\captcha\factory */  			$captcha_factory = $this->phpbb_container->get('captcha.factory');  			$captcha = $captcha_factory->get_instance($this->config['captcha_plugin']);  			$captcha->init(CONFIRM_LOGIN); diff --git a/phpBB/phpbb/avatar/driver/driver.php b/phpBB/phpbb/avatar/driver/driver.php index b3ced7edf7..b6fd380bda 100644 --- a/phpBB/phpbb/avatar/driver/driver.php +++ b/phpBB/phpbb/avatar/driver/driver.php @@ -30,6 +30,9 @@ abstract class driver implements \phpbb\avatar\driver\driver_interface  	*/  	protected $config; +	/** @var \fastImageSize\fastImageSize */ +	protected $imagesize; +  	/**  	* Current $phpbb_root_path  	* @var string @@ -73,14 +76,16 @@ abstract class driver implements \phpbb\avatar\driver\driver_interface  	* Construct a driver object  	*  	* @param \phpbb\config\config $config phpBB configuration +	* @param \fastImageSize\fastImageSize $imagesize fastImageSize class  	* @param string $phpbb_root_path Path to the phpBB root  	* @param string $php_ext PHP file extension  	* @param \phpbb\path_helper $path_helper phpBB path helper  	* @param \phpbb\cache\driver\driver_interface $cache Cache driver  	*/ -	public function __construct(\phpbb\config\config $config, $phpbb_root_path, $php_ext, \phpbb\path_helper $path_helper, \phpbb\cache\driver\driver_interface $cache = null) +	public function __construct(\phpbb\config\config $config, \fastImageSize\fastImageSize $imagesize, $phpbb_root_path, $php_ext, \phpbb\path_helper $path_helper, \phpbb\cache\driver\driver_interface $cache = null)  	{  		$this->config = $config; +		$this->imagesize = $imagesize;  		$this->phpbb_root_path = $phpbb_root_path;  		$this->php_ext = $php_ext;  		$this->path_helper = $path_helper; diff --git a/phpBB/phpbb/avatar/driver/gravatar.php b/phpBB/phpbb/avatar/driver/gravatar.php index 2082e0fd02..badbd9421d 100644 --- a/phpBB/phpbb/avatar/driver/gravatar.php +++ b/phpBB/phpbb/avatar/driver/gravatar.php @@ -98,8 +98,8 @@ class gravatar extends \phpbb\avatar\driver\driver  			return false;  		} -		// Make sure getimagesize works... -		if (function_exists('getimagesize') && ($row['avatar_width'] <= 0 || $row['avatar_height'] <= 0)) +		// Get image dimensions if they are not set +		if ($row['avatar_width'] <= 0 || $row['avatar_height'] <= 0)  		{  			/**  			* default to the minimum of the maximum allowed avatar size if the size @@ -108,20 +108,20 @@ class gravatar extends \phpbb\avatar\driver\driver  			$row['avatar_width'] = $row['avatar_height'] = min($this->config['avatar_max_width'], $this->config['avatar_max_height']);  			$url = $this->get_gravatar_url($row); -			if (($row['avatar_width'] <= 0 || $row['avatar_height'] <= 0) && (($image_data = getimagesize($url)) === false)) +			if (($row['avatar_width'] <= 0 || $row['avatar_height'] <= 0) && (($image_data = $this->imagesize->getImageSize($url)) === false))  			{  				$error[] = 'UNABLE_GET_IMAGE_SIZE';  				return false;  			} -			if (!empty($image_data) && ($image_data[0] <= 0 || $image_data[1] <= 0)) +			if (!empty($image_data) && ($image_data['width'] <= 0 || $image_data['width'] <= 0))  			{  				$error[] = 'AVATAR_NO_SIZE';  				return false;  			} -			$row['avatar_width'] = ($row['avatar_width'] && $row['avatar_height']) ? $row['avatar_width'] : $image_data[0]; -			$row['avatar_height'] = ($row['avatar_width'] && $row['avatar_height']) ? $row['avatar_height'] : $image_data[1]; +			$row['avatar_width'] = ($row['avatar_width'] && $row['avatar_height']) ? $row['avatar_width'] : $image_data['width']; +			$row['avatar_height'] = ($row['avatar_width'] && $row['avatar_height']) ? $row['avatar_height'] : $image_data['height'];  		}  		if ($row['avatar_width'] <= 0 || $row['avatar_height'] <= 0) diff --git a/phpBB/phpbb/avatar/driver/local.php b/phpBB/phpbb/avatar/driver/local.php index 36087f8ba0..88a139f81e 100644 --- a/phpBB/phpbb/avatar/driver/local.php +++ b/phpBB/phpbb/avatar/driver/local.php @@ -172,13 +172,15 @@ class local extends \phpbb\avatar\driver\driver  				// Match all images in the gallery folder  				if (preg_match('#^[^&\'"<>]+\.(?:' . implode('|', $this->allowed_extensions) . ')$#i', $image) && is_file($file_path . '/' . $image))  				{ -					if (function_exists('getimagesize')) +					$dims = $this->imagesize->getImageSize($file_path . '/' . $image); + +					if ($dims === false)  					{ -						$dims = getimagesize($file_path . '/' . $image); +						$dims = array(0, 0);  					}  					else  					{ -						$dims = array(0, 0); +						$dims = array($dims['width'], $dims['height']);  					}  					$cat = ($path == $file_path) ? $user->lang['NO_AVATAR_CATEGORY'] : str_replace("$path/", '', $file_path);  					$avatar_list[$cat][$image] = array( diff --git a/phpBB/phpbb/avatar/driver/remote.php b/phpBB/phpbb/avatar/driver/remote.php index 4b0ee3f06f..90443c9b4e 100644 --- a/phpBB/phpbb/avatar/driver/remote.php +++ b/phpBB/phpbb/avatar/driver/remote.php @@ -92,25 +92,22 @@ class remote extends \phpbb\avatar\driver\driver  			return false;  		} -		// Make sure getimagesize works... -		if (function_exists('getimagesize')) +		// Get image dimensions +		if (($width <= 0 || $height <= 0) && (($image_data = $this->imagesize->getImageSize($url)) === false))  		{ -			if (($width <= 0 || $height <= 0) && (($image_data = @getimagesize($url)) === false)) -			{ -				$error[] = 'UNABLE_GET_IMAGE_SIZE'; -				return false; -			} - -			if (!empty($image_data) && ($image_data[0] <= 0 || $image_data[1] <= 0)) -			{ -				$error[] = 'AVATAR_NO_SIZE'; -				return false; -			} +			$error[] = 'UNABLE_GET_IMAGE_SIZE'; +			return false; +		} -			$width = ($width && $height) ? $width : $image_data[0]; -			$height = ($width && $height) ? $height : $image_data[1]; +		if (!empty($image_data) && ($image_data['width'] <= 0 || $image_data['height'] <= 0)) +		{ +			$error[] = 'AVATAR_NO_SIZE'; +			return false;  		} +		$width = ($width && $height) ? $width : $image_data['width']; +		$height = ($width && $height) ? $height : $image_data['height']; +  		if ($width <= 0 || $height <= 0)  		{  			$error[] = 'AVATAR_NO_SIZE'; @@ -172,15 +169,15 @@ class remote extends \phpbb\avatar\driver\driver  			return false;  		} -		if (!empty($image_data) && (!isset($types[$image_data[2]]) || !in_array($extension, $types[$image_data[2]]))) +		if (!empty($image_data) && (!isset($types[$image_data['type']]) || !in_array($extension, $types[$image_data['type']])))  		{ -			if (!isset($types[$image_data[2]])) +			if (!isset($types[$image_data['type']]))  			{  				$error[] = 'UNABLE_GET_IMAGE_SIZE';  			}  			else  			{ -				$error[] = array('IMAGE_FILETYPE_MISMATCH', $types[$image_data[2]][0], $extension); +				$error[] = array('IMAGE_FILETYPE_MISMATCH', $types[$image_data['type']][0], $extension);  			}  			return false; diff --git a/phpBB/phpbb/avatar/driver/upload.php b/phpBB/phpbb/avatar/driver/upload.php index ee36243844..4fdaee9561 100644 --- a/phpBB/phpbb/avatar/driver/upload.php +++ b/phpBB/phpbb/avatar/driver/upload.php @@ -19,6 +19,11 @@ namespace phpbb\avatar\driver;  class upload extends \phpbb\avatar\driver\driver  {  	/** +	 * @var \phpbb\filesystem\filesystem_interface +	 */ +	protected $filesystem; + +	/**  	* @var \phpbb\mimetype\guesser  	*/  	protected $mimetype_guesser; @@ -29,15 +34,17 @@ class upload extends \phpbb\avatar\driver\driver  	* @param \phpbb\config\config $config phpBB configuration  	* @param string $phpbb_root_path Path to the phpBB root  	* @param string $php_ext PHP file extension -	* @param \phpbb_path_helper $path_helper phpBB path helper +	* @param \phpbb\filesystem\filesystem_interface phpBB filesystem helper +	* @param \phpbb\path_helper $path_helper phpBB path helper  	* @param \phpbb\mimetype\guesser $mimetype_guesser Mimetype guesser  	* @param \phpbb\cache\driver\driver_interface $cache Cache driver  	*/ -	public function __construct(\phpbb\config\config $config, $phpbb_root_path, $php_ext, \phpbb\path_helper $path_helper, \phpbb\mimetype\guesser $mimetype_guesser, \phpbb\cache\driver\driver_interface $cache = null) +	public function __construct(\phpbb\config\config $config, $phpbb_root_path, $php_ext, \phpbb\filesystem\filesystem_interface $filesystem, \phpbb\path_helper $path_helper, \phpbb\mimetype\guesser $mimetype_guesser, \phpbb\cache\driver\driver_interface $cache = null)  	{  		$this->config = $config;  		$this->phpbb_root_path = $phpbb_root_path;  		$this->php_ext = $php_ext; +		$this->filesystem = $filesystem;  		$this->path_helper = $path_helper;  		$this->mimetype_guesser = $mimetype_guesser;  		$this->cache = $cache; @@ -90,7 +97,7 @@ class upload extends \phpbb\avatar\driver\driver  			include($this->phpbb_root_path . 'includes/functions_upload.' . $this->php_ext);  		} -		$upload = new \fileupload('AVATAR_', $this->allowed_extensions, $this->config['avatar_filesize'], $this->config['avatar_min_width'], $this->config['avatar_min_height'], $this->config['avatar_max_width'], $this->config['avatar_max_height'], (isset($this->config['mime_triggers']) ? explode('|', $this->config['mime_triggers']) : false)); +		$upload = new \fileupload($this->filesystem, 'AVATAR_', $this->allowed_extensions, $this->config['avatar_filesize'], $this->config['avatar_min_width'], $this->config['avatar_min_height'], $this->config['avatar_max_width'], $this->config['avatar_max_height'], (isset($this->config['mime_triggers']) ? explode('|', $this->config['mime_triggers']) : false));  		$url = $request->variable('avatar_upload_url', '');  		$upload_file = $request->file('avatar_upload_file'); @@ -211,6 +218,6 @@ class upload extends \phpbb\avatar\driver\driver  	*/  	protected function can_upload()  	{ -		return (file_exists($this->phpbb_root_path . $this->config['avatar_path']) && phpbb_is_writable($this->phpbb_root_path . $this->config['avatar_path']) && (@ini_get('file_uploads') || strtolower(@ini_get('file_uploads')) == 'on')); +		return (file_exists($this->phpbb_root_path . $this->config['avatar_path']) && $this->filesystem->is_writable($this->phpbb_root_path . $this->config['avatar_path']) && (@ini_get('file_uploads') || strtolower(@ini_get('file_uploads')) == 'on'));  	}  } diff --git a/phpBB/phpbb/cache/driver/base.php b/phpBB/phpbb/cache/driver/base.php index 4c20ad916d..55cd4668de 100644 --- a/phpBB/phpbb/cache/driver/base.php +++ b/phpBB/phpbb/cache/driver/base.php @@ -50,6 +50,7 @@ abstract class base implements \phpbb\cache\driver\driver_interface  			}  			else if (strpos($filename, 'container_') === 0 ||  				strpos($filename, 'url_matcher') === 0 || +				strpos($filename, 'url_generator') === 0 ||  				strpos($filename, 'sql_') === 0 ||  				strpos($filename, 'data_') === 0)  			{ @@ -90,14 +91,14 @@ abstract class base implements \phpbb\cache\driver\driver_interface  	{  		// Remove extra spaces and tabs  		$query = preg_replace('/[\n\r\s\t]+/', ' ', $query); +		$query_id = md5($query); -		if (($rowset = $this->_read('sql_' . md5($query))) === false) +		if (($result = $this->_read('sql_' . $query_id)) === false)  		{  			return false;  		} -		$query_id = sizeof($this->sql_rowset); -		$this->sql_rowset[$query_id] = $rowset; +		$this->sql_rowset[$query_id] = $result;  		$this->sql_row_pointer[$query_id] = 0;  		return $query_id; @@ -176,13 +177,9 @@ abstract class base implements \phpbb\cache\driver\driver_interface  	*/  	function remove_file($filename, $check = false)  	{ -		if (!function_exists('phpbb_is_writable')) -		{ -			global $phpbb_root_path, $phpEx; -			include($phpbb_root_path . 'includes/functions.' . $phpEx); -		} +		global $phpbb_filesystem; -		if ($check && !phpbb_is_writable($this->cache_dir)) +		if ($check && !$phpbb_filesystem->is_writable($this->cache_dir))  		{  			// E_USER_ERROR - not using language entry - intended.  			trigger_error('Unable to remove files within ' . $this->cache_dir . '. Please check directory permissions.', E_USER_ERROR); diff --git a/phpBB/phpbb/cache/driver/null.php b/phpBB/phpbb/cache/driver/dummy.php index a45cf97862..1f74f6dd77 100644 --- a/phpBB/phpbb/cache/driver/null.php +++ b/phpBB/phpbb/cache/driver/dummy.php @@ -14,9 +14,9 @@  namespace phpbb\cache\driver;  /** -* ACM Null Caching +* ACM dummy Caching  */ -class null extends \phpbb\cache\driver\base +class dummy extends \phpbb\cache\driver\base  {  	/**  	* Set cache path @@ -52,8 +52,10 @@ class null extends \phpbb\cache\driver\base  	*/  	function tidy()  	{ +		global $config; +  		// This cache always has a tidy room. -		set_config('cache_last_gc', time(), true); +		$config->set('cache_last_gc', time(), false);  	}  	/** diff --git a/phpBB/phpbb/cache/driver/eaccelerator.php b/phpBB/phpbb/cache/driver/eaccelerator.php index 1697758acc..740855144f 100644 --- a/phpBB/phpbb/cache/driver/eaccelerator.php +++ b/phpBB/phpbb/cache/driver/eaccelerator.php @@ -44,9 +44,11 @@ class eaccelerator extends \phpbb\cache\driver\memory  	*/  	function tidy()  	{ +		global $config; +  		eaccelerator_gc(); -		set_config('cache_last_gc', time(), true); +		$config->set('cache_last_gc', time(), false);  	}  	/** diff --git a/phpBB/phpbb/cache/driver/file.php b/phpBB/phpbb/cache/driver/file.php index 9a7c4aec7f..bb055d3acf 100644 --- a/phpBB/phpbb/cache/driver/file.php +++ b/phpBB/phpbb/cache/driver/file.php @@ -21,14 +21,26 @@ class file extends \phpbb\cache\driver\base  	var $var_expires = array();  	/** +	 * @var	\phpbb\filesystem\filesystem_interface +	 */ +	protected $filesystem; + +	/**  	* Set cache path  	*  	* @param string $cache_dir Define the path to the cache directory (default: $phpbb_root_path . 'cache/')  	*/  	function __construct($cache_dir = null)  	{ -		global $phpbb_root_path; -		$this->cache_dir = !is_null($cache_dir) ? $cache_dir : $phpbb_root_path . 'cache/'; +		global $phpbb_root_path, $phpbb_container; + +		$this->cache_dir = !is_null($cache_dir) ? $cache_dir : $phpbb_root_path . 'cache/' . $phpbb_container->getParameter('core.environment') . '/'; +		$this->filesystem = new \phpbb\filesystem\filesystem(); + +		if (!is_dir($this->cache_dir)) +		{ +			@mkdir($this->cache_dir, 0777, true); +		}  	}  	/** @@ -63,14 +75,8 @@ class file extends \phpbb\cache\driver\base  		if (!$this->_write('data_global'))  		{ -			if (!function_exists('phpbb_is_writable')) -			{ -				global $phpbb_root_path; -				include($phpbb_root_path . 'includes/functions.' . $phpEx); -			} -  			// Now, this occurred how often? ... phew, just tell the user then... -			if (!phpbb_is_writable($this->cache_dir)) +			if (!$this->filesystem->is_writable($this->cache_dir))  			{  				// We need to use die() here, because else we may encounter an infinite loop (the message handler calls $cache->unload())  				die('Fatal: ' . $this->cache_dir . ' is NOT writable.'); @@ -89,7 +95,7 @@ class file extends \phpbb\cache\driver\base  	*/  	function tidy()  	{ -		global $phpEx; +		global $config, $phpEx;  		$dir = @opendir($this->cache_dir); @@ -143,7 +149,7 @@ class file extends \phpbb\cache\driver\base  			}  		} -		set_config('cache_last_gc', time(), true); +		$config->set('cache_last_gc', time(), false);  	}  	/** @@ -306,7 +312,7 @@ class file extends \phpbb\cache\driver\base  		// Remove extra spaces and tabs  		$query = preg_replace('/[\n\r\s\t]+/', ' ', $query); -		$query_id = sizeof($this->sql_rowset); +		$query_id = md5($query);  		$this->sql_rowset[$query_id] = array();  		$this->sql_row_pointer[$query_id] = 0; @@ -316,7 +322,7 @@ class file extends \phpbb\cache\driver\base  		}  		$db->sql_freeresult($query_result); -		if ($this->_write('sql_' . md5($query), $this->sql_rowset[$query_id], $ttl + time(), $query)) +		if ($this->_write('sql_' . $query_id, $this->sql_rowset[$query_id], $ttl + time(), $query))  		{  			return $query_id;  		} @@ -568,13 +574,14 @@ class file extends \phpbb\cache\driver\base  			fclose($handle); -			if (!function_exists('phpbb_chmod')) +			try  			{ -				global $phpbb_root_path; -				include($phpbb_root_path . 'includes/functions.' . $phpEx); +				$this->filesystem->phpbb_chmod($file, CHMOD_READ | CHMOD_WRITE); +			} +			catch (\phpbb\filesystem\exception\filesystem_exception $e) +			{ +				// Do nothing  			} - -			phpbb_chmod($file, CHMOD_READ | CHMOD_WRITE);  			$return_value = true;  		} diff --git a/phpBB/phpbb/cache/driver/memory.php b/phpBB/phpbb/cache/driver/memory.php index 0b0e323e3d..baae22d809 100644 --- a/phpBB/phpbb/cache/driver/memory.php +++ b/phpBB/phpbb/cache/driver/memory.php @@ -81,9 +81,10 @@ abstract class memory extends \phpbb\cache\driver\base  	*/  	function tidy()  	{ -		// cache has auto GC, no need to have any code here :) +		global $config; -		set_config('cache_last_gc', time(), true); +		// cache has auto GC, no need to have any code here :) +		$config->set('cache_last_gc', time(), false);  	}  	/** @@ -203,7 +204,7 @@ abstract class memory extends \phpbb\cache\driver\base  	{  		// Remove extra spaces and tabs  		$query = preg_replace('/[\n\r\s\t]+/', ' ', $query); -		$hash = md5($query); +		$query_id = md5($query);  		// determine which tables this query belongs to  		// Some queries use backticks, namely the get_database_size() query @@ -244,14 +245,13 @@ abstract class memory extends \phpbb\cache\driver\base  				$temp = array();  			} -			$temp[$hash] = true; +			$temp[$query_id] = true;  			// This must never expire  			$this->_write('sql_' . $table_name, $temp, 0);  		}  		// store them in the right place -		$query_id = sizeof($this->sql_rowset);  		$this->sql_rowset[$query_id] = array();  		$this->sql_row_pointer[$query_id] = 0; @@ -261,7 +261,7 @@ abstract class memory extends \phpbb\cache\driver\base  		}  		$db->sql_freeresult($query_result); -		$this->_write('sql_' . $hash, $this->sql_rowset[$query_id], $ttl); +		$this->_write('sql_' . $query_id, $this->sql_rowset[$query_id], $ttl);  		return $query_id;  	} diff --git a/phpBB/phpbb/captcha/plugins/captcha_abstract.php b/phpBB/phpbb/captcha/plugins/captcha_abstract.php index 24ed7f939d..b29f144f97 100644 --- a/phpBB/phpbb/captcha/plugins/captcha_abstract.php +++ b/phpBB/phpbb/captcha/plugins/captcha_abstract.php @@ -34,12 +34,12 @@ abstract class captcha_abstract  	function init($type)  	{ -		global $config, $db, $user; +		global $config, $db, $user, $request;  		// read input -		$this->confirm_id = request_var('confirm_id', ''); -		$this->confirm_code = request_var('confirm_code', ''); -		$refresh = request_var('refresh_vc', false) && $config['confirm_refresh']; +		$this->confirm_id = $request->variable('confirm_id', ''); +		$this->confirm_code = $request->variable('confirm_code', ''); +		$refresh = $request->variable('refresh_vc', false) && $config['confirm_refresh'];  		$this->type = (int) $type; @@ -117,7 +117,7 @@ abstract class captcha_abstract  	function get_demo_template($id)  	{ -		global $config, $user, $template, $phpbb_admin_path, $phpEx; +		global $config, $user, $template, $request, $phpbb_admin_path, $phpEx;  		$variables = ''; @@ -125,7 +125,7 @@ abstract class captcha_abstract  		{  			foreach ($this->captcha_vars as $captcha_var => $template_var)  			{ -				$variables .= '&' . rawurlencode($captcha_var) . '=' . request_var($captcha_var, (int) $config[$captcha_var]); +				$variables .= '&' . rawurlencode($captcha_var) . '=' . $request->variable($captcha_var, (int) $config[$captcha_var]);  			}  		} @@ -195,7 +195,7 @@ abstract class captcha_abstract  	{  		global $config, $db, $user; -		if (empty($user->lang)) +		if (!$user->is_setup())  		{  			$user->setup();  		} @@ -350,7 +350,9 @@ abstract class captcha_abstract  	function is_solved()  	{ -		if (request_var('confirm_code', false) && $this->solved === 0) +		global $request; + +		if ($request->variable('confirm_code', false) && $this->solved === 0)  		{  			$this->validate();  		} diff --git a/phpBB/phpbb/captcha/plugins/gd.php b/phpBB/phpbb/captcha/plugins/gd.php index f6200b5b2f..1727dcc1bb 100644 --- a/phpBB/phpbb/captcha/plugins/gd.php +++ b/phpBB/phpbb/captcha/plugins/gd.php @@ -53,7 +53,7 @@ class gd extends captcha_abstract  	function acp_page($id, &$module)  	{ -		global $db, $user, $auth, $template; +		global $db, $user, $auth, $template, $phpbb_log, $request;  		global $config, $phpbb_root_path, $phpbb_admin_path, $phpEx;  		$user->add_lang('acp/board'); @@ -70,21 +70,21 @@ class gd extends captcha_abstract  		$form_key = 'acp_captcha';  		add_form_key($form_key); -		$submit = request_var('submit', ''); +		$submit = $request->variable('submit', '');  		if ($submit && check_form_key($form_key))  		{  			$captcha_vars = array_keys($this->captcha_vars);  			foreach ($captcha_vars as $captcha_var)  			{ -				$value = request_var($captcha_var, 0); +				$value = $request->variable($captcha_var, 0);  				if ($value >= 0)  				{ -					set_config($captcha_var, $value); +					$config->set($captcha_var, $value);  				}  			} -			add_log('admin', 'LOG_CONFIG_VISUAL'); +			$phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_CONFIG_VISUAL');  			trigger_error($user->lang['CONFIG_UPDATED'] . adm_back_link($module->u_action));  		}  		else if ($submit) @@ -95,7 +95,7 @@ class gd extends captcha_abstract  		{  			foreach ($this->captcha_vars as $captcha_var => $template_var)  			{ -				$var = (isset($_REQUEST[$captcha_var])) ? request_var($captcha_var, 0) : $config[$captcha_var]; +				$var = (isset($_REQUEST[$captcha_var])) ? $request->variable($captcha_var, 0) : $config[$captcha_var];  				$template->assign_var($template_var, $var);  			} @@ -109,7 +109,7 @@ class gd extends captcha_abstract  	function execute_demo()  	{ -		global $config; +		global $config, $request;  		$config_old = $config; @@ -121,7 +121,7 @@ class gd extends captcha_abstract  		foreach ($this->captcha_vars as $captcha_var => $template_var)  		{ -			$config->set($captcha_var, request_var($captcha_var, (int) $config[$captcha_var])); +			$config->set($captcha_var, $request->variable($captcha_var, (int) $config[$captcha_var]));  		}  		parent::execute_demo();  		$config = $config_old; diff --git a/phpBB/phpbb/captcha/plugins/qa.php b/phpBB/phpbb/captcha/plugins/qa.php index 04052b3406..4df8a86432 100644 --- a/phpBB/phpbb/captcha/plugins/qa.php +++ b/phpBB/phpbb/captcha/plugins/qa.php @@ -58,14 +58,14 @@ class qa  	*/  	function init($type)  	{ -		global $config, $db, $user; +		global $config, $db, $user, $request;  		// load our language file  		$user->add_lang('captcha_qa');  		// read input -		$this->confirm_id = request_var('qa_confirm_id', ''); -		$this->answer = utf8_normalize_nfc(request_var('qa_answer', '', true)); +		$this->confirm_id = $request->variable('qa_confirm_id', ''); +		$this->answer = $request->variable('qa_answer', '', true);  		$this->type = (int) $type;  		$this->question_lang = $user->lang_name; @@ -113,9 +113,9 @@ class qa  	*/  	public function is_installed()  	{ -		global $db; +		global $phpbb_container; -		$db_tool = new \phpbb\db\tools($db); +		$db_tool = $phpbb_container->get('dbal.tools');  		return $db_tool->sql_table_exists($this->table_captcha_questions);  	} @@ -306,10 +306,9 @@ class qa  	*/  	function install()  	{ -		global $db; - -		$db_tool = new \phpbb\db\tools($db); +		global $phpbb_container; +		$db_tool = $phpbb_container->get('dbal.tools');  		$schemas = array(  				$this->table_captcha_questions		=> array (  					'COLUMNS' => array( @@ -350,7 +349,7 @@ class qa  				),  		); -		foreach($schemas as $table => $schema) +		foreach ($schemas as $table => $schema)  		{  			if (!$db_tool->sql_table_exists($table))  			{ @@ -542,9 +541,9 @@ class qa  	*/  	function check_answer()  	{ -		global $db; +		global $db, $request; -		$answer = ($this->question_strict) ? utf8_normalize_nfc(request_var('qa_answer', '', true)) : utf8_clean_string(utf8_normalize_nfc(request_var('qa_answer', '', true))); +		$answer = ($this->question_strict) ? $request->variable('qa_answer', '', true) : utf8_clean_string($request->variable('qa_answer', '', true));  		$sql = 'SELECT answer_text  			FROM ' . $this->table_captcha_answers . ' @@ -596,7 +595,9 @@ class qa  	*/  	function is_solved()  	{ -		if (request_var('qa_answer', false) && $this->solved === 0) +		global $request; + +		if ($request->variable('qa_answer', false) && $this->solved === 0)  		{  			$this->validate();  		} @@ -609,8 +610,7 @@ class qa  	*/  	function acp_page($id, &$module)  	{ -		global $user, $template; -		global $config; +		global $config, $request, $phpbb_log, $template, $user;  		$user->add_lang('acp/board');  		$user->add_lang('captcha_qa'); @@ -625,9 +625,9 @@ class qa  		$form_key = 'acp_captcha';  		add_form_key($form_key); -		$submit = request_var('submit', false); -		$question_id = request_var('question_id', 0); -		$action = request_var('action', ''); +		$submit = $request->variable('submit', false); +		$question_id = $request->variable('question_id', 0); +		$action = $request->variable('action', '');  		// we have two pages, so users might want to navigate from one to the other  		$list_url = $module->u_action . "&configure=1&select_captcha=" . $this->get_service_name(); @@ -732,7 +732,7 @@ class qa  						$this->acp_add_question($question_input);  					} -					add_log('admin', 'LOG_CONFIG_VISUAL'); +					$phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_CONFIG_VISUAL');  					trigger_error($user->lang['CONFIG_UPDATED'] . adm_back_link($list_url));  				}  			} @@ -818,7 +818,9 @@ class qa  	*/  	function acp_get_question_input()  	{ -		$answers = utf8_normalize_nfc(request_var('answers', '', true)); +		global $request; + +		$answers = $request->variable('answers', '', true);  		// Convert answers into array and filter if answers are set  		if (strlen($answers)) @@ -829,9 +831,9 @@ class qa  		}  		$question = array( -			'question_text'	=> request_var('question_text', '', true), -			'strict'		=> request_var('strict', false), -			'lang_iso'		=> request_var('lang_iso', ''), +			'question_text'	=> $request->variable('question_text', '', true), +			'strict'		=> $request->variable('strict', false), +			'lang_iso'		=> $request->variable('lang_iso', ''),  			'answers'		=> $answers,  		);  		return $question; diff --git a/phpBB/phpbb/captcha/plugins/recaptcha.php b/phpBB/phpbb/captcha/plugins/recaptcha.php index 584f3afec1..98132ab47d 100644 --- a/phpBB/phpbb/captcha/plugins/recaptcha.php +++ b/phpBB/phpbb/captcha/plugins/recaptcha.php @@ -37,12 +37,12 @@ class recaptcha extends captcha_abstract  	function init($type)  	{ -		global $config, $db, $user; +		global $config, $db, $user, $request;  		$user->add_lang('captcha_recaptcha');  		parent::init($type); -		$this->challenge = request_var('recaptcha_challenge_field', ''); -		$this->response = request_var('recaptcha_response_field', ''); +		$this->challenge = $request->variable('recaptcha_challenge_field', ''); +		$this->response = $request->variable('recaptcha_response_field', '');  	}  	public function is_available() @@ -75,7 +75,7 @@ class recaptcha extends captcha_abstract  	function acp_page($id, &$module)  	{ -		global $config, $db, $template, $user; +		global $config, $db, $template, $user, $phpbb_log, $request;  		$captcha_vars = array(  			'recaptcha_pubkey'				=> 'RECAPTCHA_PUBKEY', @@ -87,21 +87,21 @@ class recaptcha extends captcha_abstract  		$form_key = 'acp_captcha';  		add_form_key($form_key); -		$submit = request_var('submit', ''); +		$submit = $request->variable('submit', '');  		if ($submit && check_form_key($form_key))  		{  			$captcha_vars = array_keys($captcha_vars);  			foreach ($captcha_vars as $captcha_var)  			{ -				$value = request_var($captcha_var, ''); +				$value = $request->variable($captcha_var, '');  				if ($value)  				{ -					set_config($captcha_var, $value); +					$config->set($captcha_var, $value);  				}  			} -			add_log('admin', 'LOG_CONFIG_VISUAL'); +			$phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_CONFIG_VISUAL');  			trigger_error($user->lang['CONFIG_UPDATED'] . adm_back_link($module->u_action));  		}  		else if ($submit) @@ -112,7 +112,7 @@ class recaptcha extends captcha_abstract  		{  			foreach ($captcha_vars as $captcha_var => $template_var)  			{ -				$var = (isset($_REQUEST[$captcha_var])) ? request_var($captcha_var, '') : ((isset($config[$captcha_var])) ? $config[$captcha_var] : ''); +				$var = (isset($_REQUEST[$captcha_var])) ? $request->variable($captcha_var, '') : ((isset($config[$captcha_var])) ? $config[$captcha_var] : '');  				$template->assign_var($template_var, $var);  			} diff --git a/phpBB/phpbb/composer.json b/phpBB/phpbb/composer.json index 513d7e4559..175be4b0ab 100644 --- a/phpBB/phpbb/composer.json +++ b/phpBB/phpbb/composer.json @@ -22,6 +22,6 @@  		"classmap": [""]  	},  	"require": { -		"php": ">=5.3.3" +		"php": ">=5.3.9"      }  } diff --git a/phpBB/phpbb/console/command/db/migrate.php b/phpBB/phpbb/console/command/db/migrate.php index 87c2a057d1..2490bf1310 100644 --- a/phpBB/phpbb/console/command/db/migrate.php +++ b/phpBB/phpbb/console/command/db/migrate.php @@ -35,13 +35,17 @@ class migrate extends \phpbb\console\command\command  	/** @var string phpBB root path */  	protected $phpbb_root_path; -	function __construct(\phpbb\user $user, \phpbb\db\migrator $migrator, \phpbb\extension\manager $extension_manager, \phpbb\config\config $config, \phpbb\cache\service $cache, \phpbb\log\log $log, $phpbb_root_path) +	/** @var  \phpbb\filesystem\filesystem_interface */ +	protected $filesystem; + +	function __construct(\phpbb\user $user, \phpbb\db\migrator $migrator, \phpbb\extension\manager $extension_manager, \phpbb\config\config $config, \phpbb\cache\service $cache, \phpbb\log\log $log, \phpbb\filesystem\filesystem_interface $filesystem, $phpbb_root_path)  	{  		$this->migrator = $migrator;  		$this->extension_manager = $extension_manager;  		$this->config = $config;  		$this->cache = $cache;  		$this->log = $log; +		$this->filesystem = $filesystem;  		$this->phpbb_root_path = $phpbb_root_path;  		parent::__construct($user);  		$this->user->add_lang(array('common', 'install', 'migrator')); @@ -57,7 +61,7 @@ class migrate extends \phpbb\console\command\command  	protected function execute(InputInterface $input, OutputInterface $output)  	{ -		$this->migrator->set_output_handler(new \phpbb\db\log_wrapper_migrator_output_handler($this->user, new console_migrator_output_handler($this->user, $output), $this->phpbb_root_path . 'store/migrations_' . time() . '.log')); +		$this->migrator->set_output_handler(new \phpbb\db\log_wrapper_migrator_output_handler($this->user, new console_migrator_output_handler($this->user, $output), $this->phpbb_root_path . 'store/migrations_' . time() . '.log', $this->filesystem));  		$this->migrator->create_migrations_table(); diff --git a/phpBB/phpbb/controller/exception.php b/phpBB/phpbb/controller/exception.php index 437558b06a..e227c7c37b 100644 --- a/phpBB/phpbb/controller/exception.php +++ b/phpBB/phpbb/controller/exception.php @@ -16,6 +16,6 @@ namespace phpbb\controller;  /**  * Controller exception class  */ -class exception extends \RuntimeException +class exception extends \phpbb\exception\runtime_exception  {  } diff --git a/phpBB/phpbb/controller/helper.php b/phpBB/phpbb/controller/helper.php index dc802751fb..1518169458 100644 --- a/phpBB/phpbb/controller/helper.php +++ b/phpBB/phpbb/controller/helper.php @@ -14,7 +14,6 @@  namespace phpbb\controller;  use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Generator\UrlGenerator;  use Symfony\Component\Routing\Generator\UrlGeneratorInterface;  use Symfony\Component\Routing\RequestContext; @@ -41,6 +40,12 @@ class helper  	*/  	protected $config; +	/** +	 * phpBB router +	 * @var \phpbb\routing\router +	 */ +	protected $router; +  	/* @var \phpbb\symfony_request */  	protected $symfony_request; @@ -48,7 +53,7 @@ class helper  	protected $request;  	/** -	* @var \phpbb\filesystem The filesystem object +	* @var \phpbb\filesystem\filesystem_interface The filesystem object  	*/  	protected $filesystem; @@ -70,26 +75,24 @@ class helper  	* @param \phpbb\template\template $template Template object  	* @param \phpbb\user $user User object  	* @param \phpbb\config\config $config Config object -	* @param \phpbb\controller\provider $provider Path provider -	* @param \phpbb\extension\manager $manager Extension manager object +	* @param \phpbb\routing\router $router phpBB router  	* @param \phpbb\symfony_request $symfony_request Symfony Request object  	* @param \phpbb\request\request_interface $request phpBB request object -	* @param \phpbb\filesystem $filesystem The filesystem object +	* @param \phpbb\filesystem\filesystem_interface $filesystem The filesystem object  	* @param string $phpbb_root_path phpBB root path  	* @param string $php_ext PHP file extension  	*/ -	public function __construct(\phpbb\template\template $template, \phpbb\user $user, \phpbb\config\config $config, \phpbb\controller\provider $provider, \phpbb\extension\manager $manager, \phpbb\symfony_request $symfony_request, \phpbb\request\request_interface $request, \phpbb\filesystem $filesystem, $phpbb_root_path, $php_ext) +	public function __construct(\phpbb\template\template $template, \phpbb\user $user, \phpbb\config\config $config, \phpbb\routing\router $router, \phpbb\symfony_request $symfony_request, \phpbb\request\request_interface $request, \phpbb\filesystem\filesystem_interface $filesystem, $phpbb_root_path, $php_ext)  	{  		$this->template = $template;  		$this->user = $user;  		$this->config = $config; +		$this->router = $router;  		$this->symfony_request = $symfony_request;  		$this->request = $request;  		$this->filesystem = $filesystem;  		$this->phpbb_root_path = $phpbb_root_path;  		$this->php_ext = $php_ext; -		$provider->find_routing_files($manager->get_finder()); -		$this->route_collection = $provider->find($phpbb_root_path)->get_routes();  	}  	/** @@ -168,8 +171,8 @@ class helper  		$context->setBaseUrl($base_url); -		$url_generator = new UrlGenerator($this->route_collection, $context); -		$route_url = $url_generator->generate($route, $params, $reference_type); +		$this->router->setContext($context); +		$route_url = $this->router->generate($route, $params, $reference_type);  		if ($is_amp)  		{ @@ -221,6 +224,20 @@ class helper  	}  	/** +	 * Assigns automatic refresh time meta tag in template +	 * +	 * @param	int		$time	time in seconds, when redirection should occur +	 * @param	string	$url	the URL where the user should be redirected +	 * @return	null +	 */ +	public function assign_meta_refresh_var($time, $url) +	{ +		$this->template->assign_vars(array( +			'META' => '<meta http-equiv="refresh" content="' . $time . '; url=' . $url . '" />', +		)); +	} + +	/**  	* Return the current url  	*  	* @return string diff --git a/phpBB/phpbb/controller/provider.php b/phpBB/phpbb/controller/provider.php deleted file mode 100644 index 7e26848290..0000000000 --- a/phpBB/phpbb/controller/provider.php +++ /dev/null @@ -1,92 +0,0 @@ -<?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\controller; - -use Symfony\Component\Routing\RouteCollection; -use Symfony\Component\Routing\Loader\YamlFileLoader; -use Symfony\Component\Config\FileLocator; - -/** -* Controller interface -*/ -class provider -{ -	/** -	* YAML file(s) containing route information -	* @var array -	*/ -	protected $routing_files; - -	/** -	* Collection of the routes in phpBB and all found extensions -	* @var RouteCollection -	*/ -	protected $routes; - -	/** -	* Construct method -	* -	* @param array $routing_files Array of strings containing paths -	*							to YAML files holding route information -	*/ -	public function __construct($routing_files = array()) -	{ -		$this->routing_files = $routing_files; -	} - -	/** -	* Find the list of routing files -	* -	* @param \phpbb\finder $finder -	* @return null -	*/ -	public function find_routing_files(\phpbb\finder $finder) -	{ -		// We hardcode the path to the core config directory -		// because the finder cannot find it -		$this->routing_files = array_merge($this->routing_files, array('config/routing.yml'), array_keys($finder -				->directory('/config') -				->suffix('routing.yml') -				->find() -		)); -	} - -	/** -	* Find a list of controllers -	* -	* @param string $base_path Base path to prepend to file paths -	* @return provider -	*/ -	public function find($base_path = '') -	{ -		$this->routes = new RouteCollection; -		foreach ($this->routing_files as $file_path) -		{ -			$loader = new YamlFileLoader(new FileLocator(phpbb_realpath($base_path))); -			$this->routes->addCollection($loader->load($file_path)); -		} - -		return $this; -	} - -	/** -	* Get the list of routes -	* -	* @return RouteCollection Get the route collection -	*/ -	public function get_routes() -	{ -		return $this->routes; -	} -} diff --git a/phpBB/phpbb/controller/resolver.php b/phpBB/phpbb/controller/resolver.php index 948a6a218c..4f432c3323 100644 --- a/phpBB/phpbb/controller/resolver.php +++ b/phpBB/phpbb/controller/resolver.php @@ -23,12 +23,6 @@ use Symfony\Component\HttpFoundation\Request;  class resolver implements ControllerResolverInterface  {  	/** -	* User object -	* @var \phpbb\user -	*/ -	protected $user; - -	/**  	* ContainerInterface object  	* @var ContainerInterface  	*/ @@ -55,14 +49,12 @@ class resolver implements ControllerResolverInterface  	/**  	* Construct method  	* -	* @param \phpbb\user $user User Object  	* @param ContainerInterface $container ContainerInterface object  	* @param string $phpbb_root_path Relative path to phpBB root  	* @param \phpbb\template\template $template  	*/ -	public function __construct(\phpbb\user $user, ContainerInterface $container, $phpbb_root_path, \phpbb\template\template $template = null) +	public function __construct(ContainerInterface $container, $phpbb_root_path, \phpbb\template\template $template = null)  	{ -		$this->user = $user;  		$this->container = $container;  		$this->template = $template;  		$this->type_cast_helper = new \phpbb\request\type_cast_helper(); @@ -82,20 +74,20 @@ class resolver implements ControllerResolverInterface  		if (!$controller)  		{ -			throw new \phpbb\controller\exception($this->user->lang['CONTROLLER_NOT_SPECIFIED']); +			throw new \phpbb\controller\exception('CONTROLLER_NOT_SPECIFIED');  		}  		// Require a method name along with the service name  		if (stripos($controller, ':') === false)  		{ -			throw new \phpbb\controller\exception($this->user->lang['CONTROLLER_METHOD_NOT_SPECIFIED']); +			throw new \phpbb\controller\exception('CONTROLLER_METHOD_NOT_SPECIFIED');  		}  		list($service, $method) = explode(':', $controller);  		if (!$this->container->has($service))  		{ -			throw new \phpbb\controller\exception($this->user->lang('CONTROLLER_SERVICE_UNDEFINED', $service)); +			throw new \phpbb\controller\exception('CONTROLLER_SERVICE_UNDEFINED', array($service));  		}  		$controller_object = $this->container->get($service); @@ -166,7 +158,7 @@ class resolver implements ControllerResolverInterface  			}  			else  			{ -				throw new \phpbb\controller\exception($this->user->lang('CONTROLLER_ARGUMENT_VALUE_MISSING', $param->getPosition() + 1, get_class($object) . ':' . $method, $param->name)); +				throw new \phpbb\controller\exception('CONTROLLER_ARGUMENT_VALUE_MISSING', array($param->getPosition() + 1, get_class($object) . ':' . $method, $param->name));  			}  		} diff --git a/phpBB/phpbb/cron/task/core/prune_forum.php b/phpBB/phpbb/cron/task/core/prune_forum.php index ba68565197..abf91aee19 100644 --- a/phpBB/phpbb/cron/task/core/prune_forum.php +++ b/phpBB/phpbb/cron/task/core/prune_forum.php @@ -31,7 +31,7 @@ class prune_forum extends \phpbb\cron\task\base implements \phpbb\cron\task\para  	* If $forum_data is given, it is assumed to contain necessary information  	* about a single forum that is to be pruned.  	* -	* If $forum_data is not given, forum id will be retrieved via request_var +	* If $forum_data is not given, forum id will be retrieved via $request->variable()  	* and a database query will be performed to load the necessary information  	* about the forum.  	*/ diff --git a/phpBB/phpbb/cron/task/core/prune_shadow_topics.php b/phpBB/phpbb/cron/task/core/prune_shadow_topics.php index 97a4b0ea86..0ab59f9ed5 100644 --- a/phpBB/phpbb/cron/task/core/prune_shadow_topics.php +++ b/phpBB/phpbb/cron/task/core/prune_shadow_topics.php @@ -33,7 +33,7 @@ class prune_shadow_topics extends \phpbb\cron\task\base implements \phpbb\cron\t  	* If $forum_data is given, it is assumed to contain necessary information  	* about a single forum that is to be pruned.  	* -	* If $forum_data is not given, forum id will be retrieved via request_var +	* If $forum_data is not given, forum id will be retrieved via $request->variable()  	* and a database query will be performed to load the necessary information  	* about the forum.  	*/ diff --git a/phpBB/phpbb/cron/task/core/tidy_plupload.php b/phpBB/phpbb/cron/task/core/tidy_plupload.php index b6aeecf4b4..d7364374af 100644 --- a/phpBB/phpbb/cron/task/core/tidy_plupload.php +++ b/phpBB/phpbb/cron/task/core/tidy_plupload.php @@ -67,6 +67,8 @@ class tidy_plupload extends \phpbb\cron\task\base  	*/  	public function run()  	{ +		global $user, $phpbb_log; +  		// Remove old temporary file (perhaps failed uploads?)  		$last_valid_timestamp = time() - $this->max_file_age;  		try @@ -88,13 +90,11 @@ class tidy_plupload extends \phpbb\cron\task\base  		}  		catch (\UnexpectedValueException $e)  		{ -			add_log( -				'critical', -				'LOG_PLUPLOAD_TIDY_FAILED', +			$phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_PLUPLOAD_TIDY_FAILED', false, array(  				$this->plupload_upload_path,  				$e->getMessage(),  				$e->getTraceAsString() -			); +			));  		}  		$this->config->set('plupload_last_gc', time(), true); diff --git a/phpBB/phpbb/db/driver/driver.php b/phpBB/phpbb/db/driver/driver.php index 9fc04d47a1..8d360fc3e2 100644 --- a/phpBB/phpbb/db/driver/driver.php +++ b/phpBB/phpbb/db/driver/driver.php @@ -271,7 +271,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 +302,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 +310,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 +339,7 @@ abstract class driver implements driver_interface  			$query_id = $this->query_result;  		} -		if ($query_id !== false) +		if ($query_id)  		{  			if ($rownum !== false)  			{ @@ -363,8 +363,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 +374,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) . '\'');  	} 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..89e1b68aac 100644 --- a/phpBB/phpbb/db/driver/oracle.php +++ b/phpBB/phpbb/db/driver/oracle.php @@ -439,12 +439,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 +504,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 +555,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 +588,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 +633,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 +798,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 4e3e0d3329..f5c2dd225b 100644 --- a/phpBB/phpbb/db/driver/sqlite3.php +++ b/phpBB/phpbb/db/driver/sqlite3.php @@ -147,6 +147,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); @@ -388,9 +393,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..d0aa78f1f5 --- /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..05f7b8ac95 --- /dev/null +++ b/phpBB/phpbb/db/extractor/oracle_extractor.php @@ -0,0 +1,265 @@ +<?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); +		} + +		$sql_data = ''; + +		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/log_wrapper_migrator_output_handler.php b/phpBB/phpbb/db/log_wrapper_migrator_output_handler.php index 94c293dc45..4c85bf4d67 100644 --- a/phpBB/phpbb/db/log_wrapper_migrator_output_handler.php +++ b/phpBB/phpbb/db/log_wrapper_migrator_output_handler.php @@ -38,16 +38,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\filesystem\filesystem_interface	phpBB filesystem object  	 */ -	public function __construct(user $user, migrator_output_handler_interface $migrator, $log_file) +	public function __construct(user $user, migrator_output_handler_interface $migrator, $log_file, \phpbb\filesystem\filesystem_interface $filesystem)  	{  		$this->user = $user;  		$this->migrator = $migrator; +		$this->filesystem = $filesystem;  		$this->file_open($log_file);  	} @@ -58,7 +65,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');  		} 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..9f6e3efb91 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'; 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/style_update_p1.php b/phpBB/phpbb/db/migration/data/v310/style_update_p1.php index e8d3a3af64..918a565e06 100644 --- a/phpBB/phpbb/db/migration/data/v310/style_update_p1.php +++ b/phpBB/phpbb/db/migration/data/v310/style_update_p1.php @@ -62,6 +62,8 @@ class style_update_p1 extends \phpbb\db\migration\migration  	public function styles_update()  	{ +		global $config; +  		// Get list of valid 3.1 styles  		$available_styles = array('prosilver'); @@ -163,7 +165,7 @@ class style_update_p1 extends \phpbb\db\migration\migration  			$default_style = $this->db->sql_fetchfield($result);  			$this->db->sql_freeresult($result); -			set_config('default_style', $default_style); +			$config->set('default_style', $default_style);  			$sql = 'UPDATE ' . USERS_TABLE . ' SET user_style = 0';  			$this->sql_query($sql); diff --git a/phpBB/phpbb/db/migration/migration.php b/phpBB/phpbb/db/migration/migration.php index 5f120333e1..2304c8e44c 100644 --- a/phpBB/phpbb/db/migration/migration.php +++ b/phpBB/phpbb/db/migration/migration.php @@ -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; 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..7003844bc4 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; diff --git a/phpBB/phpbb/db/migration/tool/module.php b/phpBB/phpbb/db/migration/tool/module.php index 035625b095..b6f0372181 100644 --- a/phpBB/phpbb/db/migration/tool/module.php +++ b/phpBB/phpbb/db/migration/tool/module.php @@ -171,6 +171,8 @@ class module implements \phpbb\db\migration\tool\tool_interface  	*/  	public function add($class, $parent = 0, $data = array())  	{ +		global $user, $phpbb_log; +  		// Allows '' to be sent as 0  		$parent = $parent ?: 0; @@ -266,7 +268,7 @@ class module implements \phpbb\db\migration\tool\tool_interface  		{  			// 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', $user->data['user_id'], $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']) diff --git a/phpBB/phpbb/db/migrator.php b/phpBB/phpbb/db/migrator.php index 7fc3e787e2..6902913c64 100644 --- a/phpBB/phpbb/db/migrator.php +++ b/phpBB/phpbb/db/migrator.php @@ -32,7 +32,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 +92,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; 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..6e58171040 --- /dev/null +++ b/phpBB/phpbb/db/tools/mssql.php @@ -0,0 +1,793 @@ +<?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]', +				'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)', +			), +		); +	} + +	/** +	* 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] ' . $this->db->sql_escape($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..8b61625c3c --- /dev/null +++ b/phpBB/phpbb/db/tools/postgres.php @@ -0,0 +1,613 @@ +<?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', +				'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 775deccc30..1d7b2ddfff 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,17 +36,11 @@ 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( @@ -109,66 +103,6 @@ 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)', @@ -258,36 +192,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', -			),  		);  	} @@ -298,12 +202,6 @@ class tools  	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'); - -	/**  	* This is set to true if user only wants to return the 'to-be-executed' SQL statement(s) (as an array).  	* This mode has no effect on some methods (inserting of data for example). This is expressed within the methods command.  	*/ @@ -344,15 +242,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 +260,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 +285,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 +305,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 +323,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 +345,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 +359,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 +377,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 +393,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 +421,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 +479,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 +860,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 +870,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 +884,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 +930,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 +981,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 +999,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 +1041,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 +1056,6 @@ class tools  					}  				break; -				case 'postgres':  				case 'sqlite':  				case 'sqlite3':  					$row[$col] = substr($row[$col], strlen($table_name) + 1); @@ -1458,50 +1130,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} "; @@ -1555,51 +1183,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; @@ -1641,6 +1224,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); @@ -1692,8 +1276,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); @@ -1701,12 +1285,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'] : ''; @@ -1718,33 +1296,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)  				{ @@ -1810,59 +1361,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 . '`'; @@ -1872,10 +1378,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': @@ -1939,26 +1441,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; @@ -1969,8 +1465,8 @@ class tools  	}  	/** -	* Drop Table -	*/ +	 * {@inheritDoc} +	 */  	function sql_table_drop($table_name)  	{  		$statements = array(); @@ -2000,52 +1496,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; @@ -2106,22 +1575,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': @@ -2132,29 +1595,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) @@ -2164,7 +1617,6 @@ class tools  		switch ($this->sql_layer)  		{ -			case 'postgres':  			case 'oracle':  			case 'sqlite':  			case 'sqlite3': @@ -2185,99 +1637,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);  	} @@ -2295,8 +1727,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; @@ -2305,62 +1737,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] ' . $this->db->sql_escape($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']; @@ -2432,69 +1808,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': @@ -2563,52 +1876,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 @@ -2622,7 +1889,6 @@ class tools  		{  			case 'mysql_40':  			case 'mysql_41': -			case 'postgres':  			case 'sqlite':  			case 'sqlite3':  				// Not supported @@ -2635,40 +1901,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 @@ -2695,36 +1927,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 @@ -2744,25 +1946,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); +} diff --git a/phpBB/phpbb/di/container_builder.php b/phpBB/phpbb/di/container_builder.php index 638c13e86d..99576f9020 100644 --- a/phpBB/phpbb/di/container_builder.php +++ b/phpBB/phpbb/di/container_builder.php @@ -13,16 +13,25 @@  namespace phpbb\di; +use Symfony\Component\Config\ConfigCache; +use Symfony\Component\Config\FileLocator;  use Symfony\Component\DependencyInjection\ContainerBuilder;  use Symfony\Component\DependencyInjection\Dumper\PhpDumper; -use Symfony\Component\HttpKernel\DependencyInjection\RegisterListenersPass; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; +use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass;  class container_builder  { -	/** @var string phpBB Root Path */ +	/** +	 * @var string phpBB Root Path +	 */  	protected $phpbb_root_path; -	/** @var string php file extension  */ +	/** +	 * @var string php file extension +	 */  	protected $php_ext;  	/** @@ -112,6 +121,11 @@ class container_builder  	protected $config_php_file;  	/** +	 * @var string +	 */ +	protected $cache_dir; + +	/**  	* Constructor  	*  	* @param \phpbb\config_php_file $config_php_file @@ -133,23 +147,30 @@ class container_builder  	public function get_container()  	{  		$container_filename = $this->get_container_filename(); -		if (!defined('DEBUG_CONTAINER') && $this->dump_container && file_exists($container_filename)) +		$config_cache = new ConfigCache($container_filename, defined('DEBUG')); +		if ($this->dump_container && $config_cache->isFresh())  		{ -			require($container_filename); +			require($config_cache->getPath());  			$this->container = new \phpbb_cache_container();  		}  		else  		{ -			if ($this->config_path === null) -			{ -				$this->config_path = $this->phpbb_root_path . 'config'; -			} -			$container_extensions = array(new \phpbb\di\extension\core($this->config_path)); +			$container_extensions = array(new \phpbb\di\extension\core($this->get_config_path()));  			if ($this->use_extensions)  			{  				$installed_exts = $this->get_installed_extensions(); -				$container_extensions[] = new \phpbb\di\extension\ext($installed_exts); +				foreach ($installed_exts as $ext_name => $path) +				{ +					$extension_class = '\\' . str_replace('/', '\\', $ext_name) . '\\di\\extension'; + +					if (!class_exists($extension_class)) +					{ +						$extension_class = '\phpbb\extension\di\extension_base'; +					} + +					$container_extensions[] = new $extension_class($ext_name, $path); +				}  			}  			if ($this->inject_config) @@ -171,6 +192,10 @@ class container_builder  				}  			} +			$filesystem = new \phpbb\filesystem\filesystem(); +			$loader = new YamlFileLoader($this->container, new FileLocator($filesystem->realpath($this->get_config_path()))); +			$loader->load($this->container->getParameter('core.environment') . '/config.yml'); +  			$this->inject_custom_parameters();  			if ($this->compile_container) @@ -178,9 +203,9 @@ class container_builder  				$this->container->compile();  			} -			if ($this->dump_container && !defined('DEBUG_CONTAINER')) +			if ($this->dump_container)  			{ -				$this->dump_container($container_filename); +				$this->dump_container($config_cache);  			}  		} @@ -267,6 +292,16 @@ class container_builder  	}  	/** +	 * Returns the path to the container configuration (default: root_path/config) +	 * +	 * @return string +	 */ +	protected function get_config_path() +	{ +		return $this->config_path ?: $this->phpbb_root_path . 'config'; +	} + +	/**  	* Set custom parameters to inject into the container.  	*  	* @param array $custom_parameters @@ -277,11 +312,31 @@ class container_builder  	}  	/** +	 * Set the path to the cache directory. +	 * +	 * @param string $cache_dir Path to the cache directory +	 */ +	public function set_cache_dir($cache_dir) +	{ +		$this->cache_dir = $cache_dir; +	} + +	/** +	 * Returns the path to the cache directory (default: root_path/cache/environment). +	 * +	 * @return string Path to the cache directory. +	 */ +	protected function get_cache_dir() +	{ +		return $this->cache_dir ?: $this->phpbb_root_path . 'cache/' . $this->get_environment() . '/'; +	} + +	/**  	* Dump the container to the disk.  	* -	* @param string $container_filename The name of the file. +	* @param ConfigCache $cache The config cache  	*/ -	protected function dump_container($container_filename) +	protected function dump_container($cache)  	{  		$dumper = new PhpDumper($this->container);  		$cached_container_dump = $dumper->dump(array( @@ -289,7 +344,7 @@ class container_builder  			'base_class'    => 'Symfony\\Component\\DependencyInjection\\ContainerBuilder',  		)); -		file_put_contents($container_filename, $cached_container_dump); +		$cache->write($cached_container_dump, $this->container->getResources());  	}  	/** @@ -362,34 +417,73 @@ class container_builder  	*/  	protected function create_container(array $extensions)  	{ -		$container = new ContainerBuilder(); +		$container = new ContainerBuilder(new ParameterBag($this->get_core_parameters())); + +		$extensions_alias = array();  		foreach ($extensions as $extension)  		{  			$container->registerExtension($extension); -			$container->loadFromExtension($extension->getAlias()); +			$extensions_alias[] = $extension->getAlias(); +			//$container->loadFromExtension($extension->getAlias());  		} +		$container->getCompilerPassConfig()->setMergePass(new MergeExtensionConfigurationPass($extensions_alias)); +  		return $container;  	}  	/** -	* Inject the customs parameters into the container -	*/ +	 * Inject the customs parameters into the container +	 */  	protected function inject_custom_parameters()  	{ -		if ($this->custom_parameters === null) +		if ($this->custom_parameters !== null)  		{ -			$this->custom_parameters = array( -				'core.root_path' => $this->phpbb_root_path, -				'core.php_ext' => $this->php_ext, -			); +			foreach ($this->custom_parameters as $key => $value) +			{ +				$this->container->setParameter($key, $value); +			}  		} +	} -		foreach ($this->custom_parameters as $key => $value) +	/** +	 * Returns the core parameters. +	 * +	 * @return array An array of core parameters +	 */ +	protected function get_core_parameters() +	{ +		return array_merge( +			array( +				'core.root_path'     => $this->phpbb_root_path, +				'core.php_ext'       => $this->php_ext, +				'core.environment'   => $this->get_environment(), +				'core.debug'         => DEBUG, +			), +			$this->get_env_parameters() +		); +	} + +	/** +	 * Gets the environment parameters. +	 * +	 * Only the parameters starting with "PHPBB__" are considered. +	 * +	 * @return array An array of parameters +	 */ +	protected function get_env_parameters() +	{ +		$parameters = array(); +		foreach ($_SERVER as $key => $value)  		{ -			$this->container->setParameter($key, $value); +			if (0 === strpos($key, 'PHPBB__')) +			{ +				$parameters[strtolower(str_replace('__', '.', substr($key, 9)))] = $value; +			}  		} + +		return $parameters;  	}  	/** @@ -400,6 +494,16 @@ class container_builder  	protected function get_container_filename()  	{  		$filename = str_replace(array('/', '.'), array('slash', 'dot'), $this->phpbb_root_path); -		return $this->phpbb_root_path . 'cache/container_' . $filename . '.' . $this->php_ext; +		return $this->get_cache_dir() . 'container_' . $filename . '.' . $this->php_ext; +	} + +	/** +	 * Return the name of the current environment. +	 * +	 * @return string +	 */ +	protected function get_environment() +	{ +		return PHPBB_ENVIRONMENT;  	}  } diff --git a/phpBB/phpbb/di/extension/container_configuration.php b/phpBB/phpbb/di/extension/container_configuration.php new file mode 100644 index 0000000000..ee58ec2b74 --- /dev/null +++ b/phpBB/phpbb/di/extension/container_configuration.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\di\extension; + +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\ConfigurationInterface; + +class container_configuration implements ConfigurationInterface +{ + +	/** +	 * Generates the configuration tree builder. +	 * +	 * @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder +	 */ +	public function getConfigTreeBuilder() +	{ +		$treeBuilder = new TreeBuilder(); +		$rootNode = $treeBuilder->root('core'); +		$rootNode +			->children() +				->booleanNode('require_dev_dependencies')->defaultValue(false)->end() +				->arrayNode('twig') +					->addDefaultsIfNotSet() +					->children() +						->booleanNode('enable_debug_extension')->defaultValue(false)->end() +					->end() +				->end() +			->end() +		; +		return $treeBuilder; +	} +} diff --git a/phpBB/phpbb/di/extension/core.php b/phpBB/phpbb/di/extension/core.php index ca4fa5c082..c71dc61280 100644 --- a/phpBB/phpbb/di/extension/core.php +++ b/phpBB/phpbb/di/extension/core.php @@ -13,10 +13,11 @@  namespace phpbb\di\extension; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Config\Resource\FileResource;  use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\HttpKernel\DependencyInjection\Extension;  use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; -use Symfony\Component\Config\FileLocator; +use Symfony\Component\HttpKernel\DependencyInjection\Extension;  /**  * Container core extension @@ -24,42 +25,75 @@ use Symfony\Component\Config\FileLocator;  class core extends Extension  {  	/** -	* Config path -	* @var string -	*/ +	 * Config path +	 * @var string +	 */  	protected $config_path;  	/** -	* Constructor -	* -	* @param string $config_path Config path -	*/ +	 * Constructor +	 * +	 * @param string $config_path Config path +	 */  	public function __construct($config_path)  	{  		$this->config_path = $config_path;  	}  	/** -	* Loads a specific configuration. -	* -	* @param array            $config    An array of configuration values -	* @param ContainerBuilder $container A ContainerBuilder instance -	* -	* @throws \InvalidArgumentException When provided tag is not defined in this extension -	*/ -	public function load(array $config, ContainerBuilder $container) +	 * Loads a specific configuration. +	 * +	 * @param array            $configs   An array of configuration values +	 * @param ContainerBuilder $container A ContainerBuilder instance +	 * +	 * @throws \InvalidArgumentException When provided tag is not defined in this extension +	 */ +	public function load(array $configs, ContainerBuilder $container) +	{ +		$filesystem = new \phpbb\filesystem\filesystem(); +		$loader = new YamlFileLoader($container, new FileLocator($filesystem->realpath($this->config_path))); +		$loader->load($container->getParameter('core.environment') . '/container/environment.yml'); + +		$config = $this->getConfiguration($configs, $container); +		$config = $this->processConfiguration($config, $configs); + +		if ($config['require_dev_dependencies']) +		{ +			if (!class_exists('Goutte\Client', true)) +			{ +				trigger_error( +					'Composer development dependencies have not been set up for the ' . $container->getParameter('core.environment') . ' environment yet, run ' . +					"'php ../composer.phar install --dev' from the phpBB directory to do so.", +					E_USER_ERROR +				); +			} +		} + +		if ($config['twig']['enable_debug_extension']) +		{ +			$definition = $container->getDefinition('template.twig.extensions.debug'); +			$definition->addTag('twig.extension'); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function getConfiguration(array $config, ContainerBuilder $container)  	{ -		$loader = new YamlFileLoader($container, new FileLocator(phpbb_realpath($this->config_path))); -		$loader->load('services.yml'); +		$r = new \ReflectionClass('\phpbb\di\extension\container_configuration'); +		$container->addResource(new FileResource($r->getFileName())); + +		return new container_configuration();  	}  	/** -	* Returns the recommended alias to use in XML. -	* -	* This alias is also the mandatory prefix to use when using YAML. -	* -	* @return string The alias -	*/ +	 * Returns the recommended alias to use in XML. +	 * +	 * This alias is also the mandatory prefix to use when using YAML. +	 * +	 * @return string The alias +	 */  	public function getAlias()  	{  		return 'core'; diff --git a/phpBB/phpbb/di/extension/ext.php b/phpBB/phpbb/di/extension/ext.php deleted file mode 100644 index 718c992d2e..0000000000 --- a/phpBB/phpbb/di/extension/ext.php +++ /dev/null @@ -1,67 +0,0 @@ -<?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\di\extension; - -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\HttpKernel\DependencyInjection\Extension; -use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; -use Symfony\Component\Config\FileLocator; - -/** -* Container ext extension -*/ -class ext extends Extension -{ -	protected $paths = array(); - -	public function __construct($enabled_extensions) -	{ -		foreach ($enabled_extensions as $ext => $path) -		{ -			$this->paths[] = $path; -		} -	} - -	/** -	* Loads a specific configuration. -	* -	* @param array            $config    An array of configuration values -	* @param ContainerBuilder $container A ContainerBuilder instance -	* -	* @throws \InvalidArgumentException When provided tag is not defined in this extension -	*/ -	public function load(array $config, ContainerBuilder $container) -	{ -		foreach ($this->paths as $path) -		{ -			if (file_exists($path . '/config/services.yml')) -			{ -				$loader = new YamlFileLoader($container, new FileLocator(phpbb_realpath($path . '/config'))); -				$loader->load('services.yml'); -			} -		} -	} - -	/** -	* Returns the recommended alias to use in XML. -	* -	* This alias is also the mandatory prefix to use when using YAML. -	* -	* @return string The alias -	*/ -	public function getAlias() -	{ -		return 'ext'; -	} -} diff --git a/phpBB/phpbb/event/kernel_exception_subscriber.php b/phpBB/phpbb/event/kernel_exception_subscriber.php index eb7831ad34..0a8a0183dc 100644 --- a/phpBB/phpbb/event/kernel_exception_subscriber.php +++ b/phpBB/phpbb/event/kernel_exception_subscriber.php @@ -106,7 +106,7 @@ class kernel_exception_subscriber implements EventSubscriberInterface  		$event->setResponse($response);  	} -	public static function getSubscribedEvents() +	static public function getSubscribedEvents()  	{  		return array(  			KernelEvents::EXCEPTION		=> 'on_kernel_exception', diff --git a/phpBB/phpbb/event/kernel_request_subscriber.php b/phpBB/phpbb/event/kernel_request_subscriber.php deleted file mode 100644 index ee9f29a59d..0000000000 --- a/phpBB/phpbb/event/kernel_request_subscriber.php +++ /dev/null @@ -1,82 +0,0 @@ -<?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\event; - -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpKernel\KernelEvents; -use Symfony\Component\HttpKernel\Event\GetResponseEvent; -use Symfony\Component\HttpKernel\EventListener\RouterListener; -use Symfony\Component\Routing\RequestContext; - -class kernel_request_subscriber implements EventSubscriberInterface -{ -	/** -	* Extension manager object -	* @var \phpbb\extension\manager -	*/ -	protected $manager; - -	/** -	* PHP file extension -	* @var string -	*/ -	protected $php_ext; - -	/** -	* Root path -	* @var string -	*/ -	protected $root_path; - -	/** -	* Construct method -	* -	* @param \phpbb\extension\manager $manager Extension manager object -	* @param string $root_path Root path -	* @param string $php_ext PHP file extension -	*/ -	public function __construct(\phpbb\extension\manager $manager, $root_path, $php_ext) -	{ -		$this->root_path = $root_path; -		$this->php_ext = $php_ext; -		$this->manager = $manager; -	} - -	/** -	* This listener is run when the KernelEvents::REQUEST event is triggered -	* -	* This is responsible for setting up the routing information -	* -	* @param GetResponseEvent $event -	* @throws \BadMethodCallException -	* @return null -	*/ -	public function on_kernel_request(GetResponseEvent $event) -	{ -		$request = $event->getRequest(); -		$context = new RequestContext(); -		$context->fromRequest($request); - -		$matcher = phpbb_get_url_matcher($this->manager, $context, $this->root_path, $this->php_ext); -		$router_listener = new RouterListener($matcher, $context); -		$router_listener->onKernelRequest($event); -	} - -	public static function getSubscribedEvents() -	{ -		return array( -			KernelEvents::REQUEST		=> 'on_kernel_request', -		); -	} -} diff --git a/phpBB/phpbb/event/kernel_terminate_subscriber.php b/phpBB/phpbb/event/kernel_terminate_subscriber.php index 3a709f73fd..f0d0a2f595 100644 --- a/phpBB/phpbb/event/kernel_terminate_subscriber.php +++ b/phpBB/phpbb/event/kernel_terminate_subscriber.php @@ -32,7 +32,7 @@ class kernel_terminate_subscriber implements EventSubscriberInterface  		exit_handler();  	} -	public static function getSubscribedEvents() +	static public function getSubscribedEvents()  	{  		return array(  			KernelEvents::TERMINATE		=> array('on_kernel_terminate', ~PHP_INT_MAX), diff --git a/phpBB/phpbb/event/md_exporter.php b/phpBB/phpbb/event/md_exporter.php index 7f94ca9299..05e898a157 100644 --- a/phpBB/phpbb/event/md_exporter.php +++ b/phpBB/phpbb/event/md_exporter.php @@ -99,7 +99,7 @@ class md_exporter  	{  		$this->crawl_eventsmd($md_file, 'styles'); -		$styles = array('prosilver', 'subsilver2'); +		$styles = array('prosilver');  		foreach ($styles as $style)  		{  			$file_list = $this->get_recursive_file_list( @@ -221,7 +221,7 @@ class md_exporter  				$wiki_page = '= Template Events =' . "\n";  			}  			$wiki_page .= '{| class="zebra sortable" cellspacing="0" cellpadding="5"' . "\n"; -			$wiki_page .= '! Identifier !! Prosilver Placement (If applicable) !! Subsilver Placement (If applicable) !! Added in Release !! Explanation' . "\n"; +			$wiki_page .= '! Identifier !! Prosilver Placement (If applicable) !! Added in Release !! Explanation' . "\n";  		}  		foreach ($this->events as $event_name => $event) @@ -235,7 +235,7 @@ class md_exporter  			}  			else  			{ -				$wiki_page .= implode(', ', $event['files']['prosilver']) . ' || ' . implode(', ', $event['files']['subsilver2']); +				$wiki_page .= implode(', ', $event['files']['prosilver']);  			}  			$wiki_page .= " || {$event['since']} || " . str_replace("\n", ' ', $event['description']) . "\n"; @@ -288,7 +288,6 @@ class md_exporter  	{  		$files_list = array(  			'prosilver'		=> array(), -			'subsilver2'	=> array(),  			'adm'			=> array(),  		); @@ -308,10 +307,6 @@ class md_exporter  				{  					$files_list['prosilver'][] = substr($file, strlen('styles/prosilver/template/'));  				} -				else if (($this->filter !== 'adm') && strpos($file, 'styles/subsilver2/template/') === 0) -				{ -					$files_list['subsilver2'][] = substr($file, strlen('styles/subsilver2/template/')); -				}  				else if (($this->filter === 'adm') && strpos($file, 'adm/style/') === 0)  				{  					$files_list['adm'][] = substr($file, strlen('adm/style/')); diff --git a/phpBB/phpbb/extension/di/extension_base.php b/phpBB/phpbb/extension/di/extension_base.php new file mode 100644 index 0000000000..ba74615e70 --- /dev/null +++ b/phpBB/phpbb/extension/di/extension_base.php @@ -0,0 +1,138 @@ +<?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\extension\di; + +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; +use Symfony\Component\HttpKernel\DependencyInjection\Extension; + +/** + * Container core extension + */ +class extension_base extends Extension +{ +	/** +	 * Name of the extension (vendor/name) +	 * +	 * @var string +	 */ +	protected $extension_name; + +	/** +	 * Path to the extension. +	 * +	 * @var string +	 */ +	protected $ext_path; + +	/** +	 * Constructor +	 * +	 * @param string $extension_name Name of the extension (vendor/name) +	 * @param string $ext_path       Path to the extension +	 */ +	public function __construct($extension_name, $ext_path) +	{ +		$this->extension_name = $extension_name; +		$this->ext_path = $ext_path; +	} + +	/** +	 * Loads a specific configuration. +	 * +	 * @param array            $configs   An array of configuration values +	 * @param ContainerBuilder $container A ContainerBuilder instance +	 * +	 * @throws \InvalidArgumentException When provided tag is not defined in this extension +	 */ +	public function load(array $configs, ContainerBuilder $container) +	{ +		$this->load_services($container); +	} + +	/** +	 * Loads the services.yml file. +	 * +	 * @param ContainerBuilder $container A ContainerBuilder instance +	 */ +	protected function load_services(ContainerBuilder $container) +	{ +		$services_directory = false; +		$services_file = false; + +		if (file_exists($this->ext_path . 'config/' . $container->getParameter('core.environment') . '/container/environment.yml')) +		{ +			$services_directory = $this->ext_path . 'config/' . $container->getParameter('core.environment') . '/container/'; +			$services_file = 'environment.yml'; +		} +		else if (!is_dir($this->ext_path . 'config/' . $container->getParameter('core.environment'))) +		{ +			if (file_exists($this->ext_path . 'config/default/container/environment.yml')) +			{ +				$services_directory = $this->ext_path . 'config/default/container/'; +				$services_file = 'environment.yml'; +			} +			else if (!is_dir($this->ext_path . 'config/default') && file_exists($this->ext_path . '/config/services.yml')) +			{ +				$services_directory = $this->ext_path . 'config'; +				$services_file = 'services.yml'; +			} +		} + +		if ($services_directory && $services_file) +		{ +			$filesystem = new \phpbb\filesystem\filesystem(); +			$loader = new YamlFileLoader($container, new FileLocator($filesystem->realpath($services_directory))); +			$loader->load($services_file); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function getConfiguration(array $config, ContainerBuilder $container) +	{ +		$reflected = new \ReflectionClass($this); +		$namespace = $reflected->getNamespaceName(); + +		$class = $namespace . '\\di\configuration'; +		if (class_exists($class)) +		{ +			$r = new \ReflectionClass($class); +			$container->addResource(new FileResource($r->getFileName())); + +			if (!method_exists($class, '__construct')) +			{ +				$configuration = new $class(); + +				return $configuration; +			} +		} + +	} + +	/** +	 * Returns the recommended alias to use in XML. +	 * +	 * This alias is also the mandatory prefix to use when using YAML. +	 * +	 * @return string The alias +	 */ +	public function getAlias() +	{ +		return str_replace('/', '_', $this->extension_name); +	} +} diff --git a/phpBB/phpbb/extension/exception.php b/phpBB/phpbb/extension/exception.php index 3f7d251a4e..9050449bf1 100644 --- a/phpBB/phpbb/extension/exception.php +++ b/phpBB/phpbb/extension/exception.php @@ -16,10 +16,6 @@ namespace phpbb\extension;  /**   * Exception class for metadata   */ -class exception extends \UnexpectedValueException +class exception extends \phpbb\exception\runtime_exception  { -	public function __toString() -	{ -		return $this->getMessage(); -	}  } diff --git a/phpBB/phpbb/extension/manager.php b/phpBB/phpbb/extension/manager.php index 76f0e3558e..98d2d27278 100644 --- a/phpBB/phpbb/extension/manager.php +++ b/phpBB/phpbb/extension/manager.php @@ -26,7 +26,6 @@ class manager  	protected $db;  	protected $config;  	protected $cache; -	protected $user;  	protected $php_ext;  	protected $extensions;  	protected $extension_table; @@ -39,15 +38,14 @@ class manager  	* @param ContainerInterface $container A container  	* @param \phpbb\db\driver\driver_interface $db A database connection  	* @param \phpbb\config\config $config Config object -	* @param \phpbb\filesystem $filesystem -	* @param \phpbb\user $user User object +	* @param \phpbb\filesystem\filesystem_interface $filesystem  	* @param string $extension_table The name of the table holding extensions  	* @param string $phpbb_root_path Path to the phpbb includes directory.  	* @param string $php_ext php file extension, defaults to php  	* @param \phpbb\cache\driver\driver_interface $cache A cache instance or null  	* @param string $cache_name The name of the cache variable, defaults to _ext  	*/ -	public function __construct(ContainerInterface $container, \phpbb\db\driver\driver_interface $db, \phpbb\config\config $config, \phpbb\filesystem $filesystem, \phpbb\user $user, $extension_table, $phpbb_root_path, $php_ext = 'php', \phpbb\cache\driver\driver_interface $cache = null, $cache_name = '_ext') +	public function __construct(ContainerInterface $container, \phpbb\db\driver\driver_interface $db, \phpbb\config\config $config, \phpbb\filesystem\filesystem_interface $filesystem, $extension_table, $phpbb_root_path, $php_ext = 'php', \phpbb\cache\driver\driver_interface $cache = null, $cache_name = '_ext')  	{  		$this->cache = $cache;  		$this->cache_name = $cache_name; @@ -58,7 +56,6 @@ class manager  		$this->filesystem = $filesystem;  		$this->phpbb_root_path = $phpbb_root_path;  		$this->php_ext = $php_ext; -		$this->user = $user;  		$this->extensions = ($this->cache) ? $this->cache->get($this->cache_name) : false; @@ -154,7 +151,7 @@ class manager  	*/  	public function create_extension_metadata_manager($name, \phpbb\template\template $template)  	{ -		return new \phpbb\extension\metadata_manager($name, $this->config, $this, $template, $this->user, $this->phpbb_root_path); +		return new \phpbb\extension\metadata_manager($name, $this->config, $this, $template, $this->phpbb_root_path);  	}  	/** @@ -464,15 +461,17 @@ class manager  	* All enabled and disabled extensions are considered configured. A purged  	* extension that is no longer in the database is not configured.  	* +	* @param bool $phpbb_relative Whether the path should be relative to phpbb root +	*  	* @return array An array with extension names as keys and and the  	*               database stored extension information as values  	*/ -	public function all_configured() +	public function all_configured($phpbb_relative = true)  	{  		$configured = array();  		foreach ($this->extensions as $name => $data)  		{ -			$data['ext_path'] = $this->phpbb_root_path . $data['ext_path']; +			$data['ext_path'] = ($phpbb_relative ? $this->phpbb_root_path : '') . $data['ext_path'];  			$configured[$name] = $data;  		}  		return $configured; @@ -480,18 +479,19 @@ class manager  	/**  	* Retrieves all enabled extensions. +	* @param bool $phpbb_relative Whether the path should be relative to phpbb root  	*  	* @return array An array with extension names as keys and and the  	*               database stored extension information as values  	*/ -	public function all_enabled() +	public function all_enabled($phpbb_relative = true)  	{  		$enabled = array();  		foreach ($this->extensions as $name => $data)  		{  			if ($data['ext_active'])  			{ -				$enabled[$name] = $this->phpbb_root_path . $data['ext_path']; +				$enabled[$name] = ($phpbb_relative ? $this->phpbb_root_path : '') . $data['ext_path'];  			}  		}  		return $enabled; @@ -500,17 +500,19 @@ class manager  	/**  	* Retrieves all disabled extensions.  	* +	* @param bool $phpbb_relative Whether the path should be relative to phpbb root +	*  	* @return array An array with extension names as keys and and the  	*               database stored extension information as values  	*/ -	public function all_disabled() +	public function all_disabled($phpbb_relative = true)  	{  		$disabled = array();  		foreach ($this->extensions as $name => $data)  		{  			if (!$data['ext_active'])  			{ -				$disabled[$name] = $this->phpbb_root_path . $data['ext_path']; +				$disabled[$name] = ($phpbb_relative ? $this->phpbb_root_path : '') . $data['ext_path'];  			}  		}  		return $disabled; diff --git a/phpBB/phpbb/extension/metadata_manager.php b/phpBB/phpbb/extension/metadata_manager.php index a64d88fe39..4f080647c8 100644 --- a/phpBB/phpbb/extension/metadata_manager.php +++ b/phpBB/phpbb/extension/metadata_manager.php @@ -37,12 +37,6 @@ class metadata_manager  	protected $template;  	/** -	* phpBB User instance -	* @var \phpbb\user -	*/ -	protected $user; - -	/**  	* phpBB root path  	* @var string  	*/ @@ -73,15 +67,13 @@ class metadata_manager  	* @param \phpbb\config\config		$config				phpBB Config instance  	* @param \phpbb\extension\manager	$extension_manager	An instance of the phpBB extension manager  	* @param \phpbb\template\template	$template			phpBB Template instance -	* @param \phpbb\user 		$user 				User instance  	* @param string				$phpbb_root_path	Path to the phpbb includes directory.  	*/ -	public function __construct($ext_name, \phpbb\config\config $config, \phpbb\extension\manager $extension_manager, \phpbb\template\template $template, \phpbb\user $user, $phpbb_root_path) +	public function __construct($ext_name, \phpbb\config\config $config, \phpbb\extension\manager $extension_manager, \phpbb\template\template $template, $phpbb_root_path)  	{  		$this->config = $config;  		$this->extension_manager = $extension_manager;  		$this->template = $template; -		$this->user = $user;  		$this->phpbb_root_path = $phpbb_root_path;  		$this->ext_name = $ext_name; @@ -149,7 +141,7 @@ class metadata_manager  		if (!file_exists($this->metadata_file))  		{ -			throw new \phpbb\extension\exception($this->user->lang('FILE_NOT_FOUND', $this->metadata_file)); +			throw new \phpbb\extension\exception('FILE_NOT_FOUND', array($this->metadata_file));  		}  	} @@ -163,18 +155,18 @@ class metadata_manager  	{  		if (!file_exists($this->metadata_file))  		{ -			throw new \phpbb\extension\exception($this->user->lang('FILE_NOT_FOUND', $this->metadata_file)); +			throw new \phpbb\extension\exception('FILE_NOT_FOUND', array($this->metadata_file));  		}  		else  		{  			if (!($file_contents = file_get_contents($this->metadata_file)))  			{ -				throw new \phpbb\extension\exception($this->user->lang('FILE_CONTENT_ERR', $this->metadata_file)); +				throw new \phpbb\extension\exception('FILE_CONTENT_ERR', array($this->metadata_file));  			}  			if (($metadata = json_decode($file_contents, true)) === null)  			{ -				throw new \phpbb\extension\exception($this->user->lang('FILE_JSON_DECODE_ERR', $this->metadata_file)); +				throw new \phpbb\extension\exception('FILE_JSON_DECODE_ERR', array($this->metadata_file));  			}  			array_walk_recursive($metadata, array($this, 'sanitize_json')); @@ -246,12 +238,12 @@ class metadata_manager  				{  					if (!isset($this->metadata[$name]))  					{ -						throw new \phpbb\extension\exception($this->user->lang('META_FIELD_NOT_SET', $name)); +						throw new \phpbb\extension\exception('META_FIELD_NOT_SET', array($name));  					}  					if (!preg_match($fields[$name], $this->metadata[$name]))  					{ -						throw new \phpbb\extension\exception($this->user->lang('META_FIELD_INVALID', $name)); +						throw new \phpbb\extension\exception('META_FIELD_INVALID', array($name));  					}  				}  			break; @@ -270,14 +262,14 @@ class metadata_manager  	{  		if (empty($this->metadata['authors']))  		{ -			throw new \phpbb\extension\exception($this->user->lang('META_FIELD_NOT_SET', 'authors')); +			throw new \phpbb\extension\exception('META_FIELD_NOT_SET', array('authors'));  		}  		foreach ($this->metadata['authors'] as $author)  		{  			if (!isset($author['name']))  			{ -				throw new \phpbb\extension\exception($this->user->lang('META_FIELD_NOT_SET', 'author name')); +				throw new \phpbb\extension\exception('META_FIELD_NOT_SET', array('author name'));  			}  		} diff --git a/phpBB/phpbb/filesystem.php b/phpBB/phpbb/filesystem.php index 77517082e5..af56d78845 100644 --- a/phpBB/phpbb/filesystem.php +++ b/phpBB/phpbb/filesystem.php @@ -14,37 +14,8 @@  namespace phpbb;  /** -* A class with various functions that are related to paths, files and the filesystem -*/ -class filesystem + * @deprecated	3.2.0-dev	(To be removed 3.3.0) use \phpbb\filesystem\filesystem instead + */ +class filesystem extends \phpbb\filesystem\filesystem  { -	/** -	* Eliminates useless . and .. components from specified path. -	* -	* @param string $path Path to clean -	* @return string Cleaned path -	*/ -	public function clean_path($path) -	{ -		$exploded = explode('/', $path); -		$filtered = array(); -		foreach ($exploded as $part) -		{ -			if ($part === '.' && !empty($filtered)) -			{ -				continue; -			} - -			if ($part === '..' && !empty($filtered) && $filtered[sizeof($filtered) - 1] !== '.' && $filtered[sizeof($filtered) - 1] !== '..') -			{ -				array_pop($filtered); -			} -			else -			{ -				$filtered[] = $part; -			} -		} -		$path = implode('/', $filtered); -		return $path; -	}  } diff --git a/phpBB/phpbb/filesystem/exception/filesystem_exception.php b/phpBB/phpbb/filesystem/exception/filesystem_exception.php new file mode 100644 index 0000000000..d68fa9adf3 --- /dev/null +++ b/phpBB/phpbb/filesystem/exception/filesystem_exception.php @@ -0,0 +1,42 @@ +<?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\filesystem\exception; + +class filesystem_exception extends \phpbb\exception\runtime_exception +{ +	/** +	 * Constructor +	 * +	 * @param string		$message	The Exception message to throw (must be a language variable). +	 * @param string		$filename	The file that caused the error. +	 * @param array			$parameters	The parameters to use with the language var. +	 * @param \Exception	$previous	The previous runtime_exception used for the runtime_exception chaining. +	 * @param integer		$code		The Exception code. +	 */ +	public function __construct($message = "", $filename = '', $parameters = array(), \Exception $previous = null, $code = 0) +	{ +		parent::__construct($message, array_merge(array('filename' => $filename), $parameters), $previous, $code); +	} + +	/** +	 * Returns the filename that triggered the error +	 * +	 * @return string +	 */ +	public function get_filename() +	{ +		$parameters = parent::get_parameters(); +		return $parameters['filename']; +	} +} diff --git a/phpBB/phpbb/filesystem/filesystem.php b/phpBB/phpbb/filesystem/filesystem.php new file mode 100644 index 0000000000..2112882d1d --- /dev/null +++ b/phpBB/phpbb/filesystem/filesystem.php @@ -0,0 +1,916 @@ +<?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\filesystem; + +use phpbb\filesystem\exception\filesystem_exception; + +/** + * A class with various functions that are related to paths, files and the filesystem + */ +class filesystem implements filesystem_interface +{ +	/** +	 * Store some information about file ownership for phpBB's chmod function +	 * +	 * @var array +	 */ +	protected $chmod_info; + +	/** +	 * Stores current working directory +	 * +	 * @var string|bool		current working directory or false if it cannot be recovered +	 */ +	protected $working_directory; + +	/** +	 * Symfony's Filesystem component +	 * +	 * @var \Symfony\Component\Filesystem\Filesystem +	 */ +	protected $symfony_filesystem; + +	/** +	 * Constructor +	 */ +	public function __construct() +	{ +		$this->chmod_info			= array(); +		$this->symfony_filesystem	= new \Symfony\Component\Filesystem\Filesystem(); +		$this->working_directory	= null; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function chgrp($files, $group, $recursive = false) +	{ +		try +		{ +			$this->symfony_filesystem->chgrp($files, $group, $recursive); +		} +		catch (\Symfony\Component\Filesystem\Exception\IOException $e) +		{ +			// Try to recover filename +			// By the time this is written that is at the end of the message +			$error = trim($e->getMessage()); +			$file = substr($error, strrpos($error, ' ')); + +			throw new filesystem_exception('CANNOT_CHANGE_FILE_GROUP', $file, array(), $e); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function chmod($files, $perms = null, $recursive = false, $force_chmod_link = false) +	{ +		if (is_null($perms)) +		{ +			// Default to read permission for compatibility reasons +			$perms = self::CHMOD_READ; +		} + +		// Check if we got a permission flag +		if ($perms > self::CHMOD_ALL) +		{ +			$file_perm = $perms; + +			// Extract permissions +			//$owner = ($file_perm >> 6) & 7; // This will be ignored +			$group = ($file_perm >> 3) & 7; +			$other = ($file_perm >> 0) & 7; + +			// Does any permissions provided? if so we add execute bit for directories +			$group = ($group !== 0) ? ($group | self::CHMOD_EXECUTE) : $group; +			$other = ($other !== 0) ? ($other | self::CHMOD_EXECUTE) : $other; + +			// Compute directory permissions +			$dir_perm = (self::CHMOD_ALL << 6) + ($group << 3) + ($other << 3); +		} +		else +		{ +			// Add execute bit to owner if execute bit is among perms +			$owner_perm	= (self::CHMOD_READ | self::CHMOD_WRITE) | ($perms & self::CHMOD_EXECUTE); +			$file_perm	= ($owner_perm << 6) + ($perms << 3) + ($perms << 0); + +			// Compute directory permissions +			$perm = ($perms !== 0) ? ($perms | self::CHMOD_EXECUTE) : $perms; +			$dir_perm = (($owner_perm | self::CHMOD_EXECUTE) << 6) + ($perm << 3) + ($perm << 0); +		} + +		// Symfony's filesystem component does not support extra execution flags on directories +		// so we need to implement it again +		foreach ($this->to_iterator($files) as $file) +		{ +			if ($recursive && is_dir($file) && !is_link($file)) +			{ +				$this->chmod(new \FilesystemIterator($file), $perms, true); +			} + +			// Don't chmod links as mostly those require 0777 and that cannot be changed +			if (is_dir($file) || (is_link($file) && $force_chmod_link)) +			{ +				if (true !== @chmod($file, $dir_perm)) +				{ +					throw new filesystem_exception('CANNOT_CHANGE_FILE_PERMISSIONS', $file,  array()); +				} +			} +			else if (is_file($file)) +			{ +				if (true !== @chmod($file, $file_perm)) +				{ +					throw new filesystem_exception('CANNOT_CHANGE_FILE_PERMISSIONS', $file,  array()); +				} +			} +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function chown($files, $user, $recursive = false) +	{ +		try +		{ +			$this->symfony_filesystem->chown($files, $user, $recursive); +		} +		catch (\Symfony\Component\Filesystem\Exception\IOException $e) +		{ +			// Try to recover filename +			// By the time this is written that is at the end of the message +			$error = trim($e->getMessage()); +			$file = substr($error, strrpos($error, ' ')); + +			throw new filesystem_exception('CANNOT_CHANGE_FILE_GROUP', $file, array(), $e); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function clean_path($path) +	{ +		$exploded = explode('/', $path); +		$filtered = array(); +		foreach ($exploded as $part) +		{ +			if ($part === '.' && !empty($filtered)) +			{ +				continue; +			} + +			if ($part === '..' && !empty($filtered) && $filtered[sizeof($filtered) - 1] !== '.' && $filtered[sizeof($filtered) - 1] !== '..') +			{ +				array_pop($filtered); +			} +			else +			{ +				$filtered[] = $part; +			} +		} +		$path = implode('/', $filtered); +		return $path; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function copy($origin_file, $target_file, $override = false) +	{ +		try +		{ +			$this->symfony_filesystem->copy($origin_file, $target_file, $override); +		} +		catch (\Symfony\Component\Filesystem\Exception\IOException $e) +		{ +			throw new filesystem_exception('CANNOT_COPY_FILES', '', array(), $e); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function dump_file($filename, $content) +	{ +		try +		{ +			$this->symfony_filesystem->dumpFile($filename, $content); +		} +		catch (\Symfony\Component\Filesystem\Exception\IOException $e) +		{ +			throw new filesystem_exception('CANNOT_DUMP_FILE', $filename, array(), $e); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function exists($files) +	{ +		return $this->symfony_filesystem->exists($files); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function is_absolute_path($path) +	{ +		return (isset($path[0]) && $path[0] === '/' || preg_match('#^[a-z]:[/\\\]#i', $path)) ? true : false; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function is_readable($files, $recursive = false) +	{ +		foreach ($this->to_iterator($files) as $file) +		{ +			if ($recursive && is_dir($file) && !is_link($file)) +			{ +				if (!$this->is_readable(new \FilesystemIterator($file), true)) +				{ +					return false; +				} +			} + +			if (!is_readable($file)) +			{ +				return false; +			} +		} + +		return true; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function is_writable($files, $recursive = false) +	{ +		if (defined('PHP_WINDOWS_VERSION_MAJOR') || !function_exists('is_writable')) +		{ +			foreach ($this->to_iterator($files) as $file) +			{ +				if ($recursive && is_dir($file) && !is_link($file)) +				{ +					if (!$this->is_writable(new \FilesystemIterator($file), true)) +					{ +						return false; +					} +				} + +				if (!$this->phpbb_is_writable($file)) +				{ +					return false; +				} +			} +		} +		else +		{ +			// use built in is_writable +			foreach ($this->to_iterator($files) as $file) +			{ +				if ($recursive && is_dir($file) && !is_link($file)) +				{ +					if (!$this->is_writable(new \FilesystemIterator($file), true)) +					{ +						return false; +					} +				} + +				if (!is_writable($file)) +				{ +					return false; +				} +			} +		} + +		return true; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function make_path_relative($end_path, $start_path) +	{ +		return $this->symfony_filesystem->makePathRelative($end_path, $start_path); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function mirror($origin_dir, $target_dir, \Traversable $iterator = null, $options = array()) +	{ +		try +		{ +			$this->symfony_filesystem->mirror($origin_dir, $target_dir, $iterator, $options); +		} +		catch (\Symfony\Component\Filesystem\Exception\IOException $e) +		{ +			$msg = $e->getMessage(); +			$filename = substr($msg, strpos($msg, '"'), strrpos($msg, '"')); + +			throw new filesystem_exception('CANNOT_MIRROR_DIRECTORY', $filename, array(), $e); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function mkdir($dirs, $mode = 0777) +	{ +		try +		{ +			$this->symfony_filesystem->mkdir($dirs, $mode); +		} +		catch (\Symfony\Component\Filesystem\Exception\IOException $e) +		{ +			$msg = $e->getMessage(); +			$filename = substr($msg, strpos($msg, '"'), strrpos($msg, '"')); + +			throw new filesystem_exception('CANNOT_CREATE_DIRECTORY', $filename, array(), $e); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function phpbb_chmod($files, $perms = null, $recursive = false, $force_chmod_link = false) +	{ +		if (is_null($perms)) +		{ +			// Default to read permission for compatibility reasons +			$perms = self::CHMOD_READ; +		} + +		if (empty($this->chmod_info)) +		{ +			if (!function_exists('fileowner') || !function_exists('filegroup')) +			{ +				$this->chmod_info['process'] = false; +			} +			else +			{ +				$common_php_owner	= @fileowner(__FILE__); +				$common_php_group	= @filegroup(__FILE__); + +				// And the owner and the groups PHP is running under. +				$php_uid	= (function_exists('posic_getuid')) ? @posix_getuid() : false; +				$php_gids	= (function_exists('posix_getgroups')) ? @posix_getgroups() : false; + +				// If we are unable to get owner/group, then do not try to set them by guessing +				if (!$php_uid || empty($php_gids) || !$common_php_owner || !$common_php_group) +				{ +					$this->chmod_info['process'] = false; +				} +				else +				{ +					$this->chmod_info = array( +						'process'		=> true, +						'common_owner'	=> $common_php_owner, +						'common_group'	=> $common_php_group, +						'php_uid'		=> $php_uid, +						'php_gids'		=> $php_gids, +					); +				} +			} +		} + +		if ($this->chmod_info['process']) +		{ +			try +			{ +				foreach ($this->to_iterator($files) as $file) +				{ +					$file_uid = @fileowner($file); +					$file_gid = @filegroup($file); + +					// Change owner +					if ($file_uid !== $this->chmod_info['common_owner']) +					{ +						$this->chown($file, $this->chmod_info['common_owner'], $recursive); +					} + +					// Change group +					if ($file_gid !== $this->chmod_info['common_group']) +					{ +						$this->chgrp($file, $this->chmod_info['common_group'], $recursive); +					} + +					clearstatcache(); +					$file_uid = @fileowner($file); +					$file_gid = @filegroup($file); +				} +			} +			catch (filesystem_exception $e) +			{ +				$this->chmod_info['process'] = false; +			} +		} + +		// Still able to process? +		if ($this->chmod_info['process']) +		{ +			if ($file_uid === $this->chmod_info['php_uid']) +			{ +				$php = 'owner'; +			} +			else if (in_array($file_gid, $this->chmod_info['php_gids'])) +			{ +				$php = 'group'; +			} +			else +			{ +				// Since we are setting the everyone bit anyway, no need to do expensive operations +				$this->chmod_info['process'] = false; +			} +		} + +		// We are not able to determine or change something +		if (!$this->chmod_info['process']) +		{ +			$php = 'other'; +		} + +		switch ($php) +		{ +			case 'owner': +				try +				{ +					$this->chmod($files, $perms, $recursive, $force_chmod_link); +					clearstatcache(); +					if ($this->is_readable($files) && $this->is_writable($files)) +					{ +						break; +					} +				} +				catch (filesystem_exception $e) +				{ +					// Do nothing +				} +			case 'group': +				try +				{ +					$this->chmod($files, $perms, $recursive, $force_chmod_link); +					clearstatcache(); +					if ((!($perms & self::CHMOD_READ) || $this->is_readable($files, $recursive)) && (!($perms & self::CHMOD_WRITE) || $this->is_writable($files, $recursive))) +					{ +						break; +					} +				} +				catch (filesystem_exception $e) +				{ +					// Do nothing +				} +			case 'other': +			default: +				$this->chmod($files, $perms, $recursive, $force_chmod_link); +			break; +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function realpath($path) +	{ +		if (!function_exists('realpath')) +		{ +			return $this->phpbb_own_realpath($path); +		} + +		$realpath = realpath($path); + +		// Strangely there are provider not disabling realpath but returning strange values. :o +		// We at least try to cope with them. +		if ((!$this->is_absolute_path($path) && $realpath === $path) || $realpath === false) +		{ +			return $this->phpbb_own_realpath($path); +		} + +		// Check for DIRECTORY_SEPARATOR at the end (and remove it!) +		if (substr($realpath, -1) === DIRECTORY_SEPARATOR) +		{ +			$realpath = substr($realpath, 0, -1); +		} + +		return $realpath; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function remove($files) +	{ +		try +		{ +			$this->symfony_filesystem->remove($files); +		} +		catch (\Symfony\Component\Filesystem\Exception\IOException $e) +		{ +			// Try to recover filename +			// By the time this is written that is at the end of the message +			$error = trim($e->getMessage()); +			$file = substr($error, strrpos($error, ' ')); + +			throw new filesystem_exception('CANNOT_DELETE_FILES', $file, array(), $e); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function rename($origin, $target, $overwrite = false) +	{ +		try +		{ +			$this->symfony_filesystem->rename($origin, $target, $overwrite); +		} +		catch (\Symfony\Component\Filesystem\Exception\IOException $e) +		{ +			$msg = $e->getMessage(); +			$filename = substr($msg, strpos($msg, '"'), strrpos($msg, '"')); + +			throw new filesystem_exception('CANNOT_RENAME_FILE', $filename, array(), $e); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function symlink($origin_dir, $target_dir, $copy_on_windows = false) +	{ +		try +		{ +			$this->symfony_filesystem->symlink($origin_dir, $target_dir, $copy_on_windows); +		} +		catch (\Symfony\Component\Filesystem\Exception\IOException $e) +		{ +			throw new filesystem_exception('CANNOT_CREATE_SYMLINK', $origin_dir, array(), $e); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function touch($files, $time = null, $access_time = null) +	{ +		try +		{ +			$this->symfony_filesystem->touch($files, $time, $access_time); +		} +		catch (\Symfony\Component\Filesystem\Exception\IOException $e) +		{ +			// Try to recover filename +			// By the time this is written that is at the end of the message +			$error = trim($e->getMessage()); +			$file = substr($error, strrpos($error, ' ')); + +			throw new filesystem_exception('CANNOT_TOUCH_FILES', $file, array(), $e); +		} +	} + +	/** +	 * phpBB's implementation of is_writable +	 * +	 * @todo Investigate if is_writable is still buggy +	 * +	 * @param string	$file	file/directory to check if writable +	 * +	 * @return bool	true if the given path is writable +	 */ +	protected function phpbb_is_writable($file) +	{ +		if (file_exists($file)) +		{ +			// Canonicalise path to absolute path +			$file = $this->realpath($file); + +			if (is_dir($file)) +			{ +				// Test directory by creating a file inside the directory +				$result = @tempnam($file, 'i_w'); + +				if (is_string($result) && file_exists($result)) +				{ +					unlink($result); + +					// Ensure the file is actually in the directory (returned realpathed) +					return (strpos($result, $file) === 0) ? true : false; +				} +			} +			else +			{ +				$handle = @fopen($file, 'c'); + +				if (is_resource($handle)) +				{ +					fclose($handle); +					return true; +				} +			} +		} +		else +		{ +			// file does not exist test if we can write to the directory +			$dir = dirname($file); + +			if (file_exists($dir) && is_dir($dir) && $this->phpbb_is_writable($dir)) +			{ +				return true; +			} +		} + +		return false; +	} + +	/** +	 * Try to resolve real path when PHP's realpath failes to do so +	 * +	 * @param string	$path +	 * @return bool|string +	 */ +	protected function phpbb_own_realpath($path) +	{ +		// Replace all directory separators with '/' +		$path = str_replace(DIRECTORY_SEPARATOR, '/', $path); + +		$is_absolute_path = false; +		$path_prefix = ''; + +		if ($this->is_absolute_path($path)) +		{ +			$is_absolute_path = true; +		} +		else +		{ +			// Resolve working directory and store it +			if (is_null($this->working_directory)) +			{ +				if (function_exists('getcwd')) +				{ +					$this->working_directory = str_replace(DIRECTORY_SEPARATOR, '/', getcwd()); +				} + +				// +				// From this point on we really just guessing +				// If chdir were called we screwed +				// +				else if (function_exists('debug_backtrace')) +				{ +					$call_stack = debug_backtrace(0); +					$this->working_directory = str_replace(DIRECTORY_SEPARATOR, '/', dirname($call_stack[sizeof($call_stack) - 1]['file'])); +				} +				else +				{ +					// +					// Assuming that the working directory is phpBB root +					// we could use this as a fallback, when phpBB will use controllers +					// everywhere this will be a safe assumption +					// +					//$dir_parts = explode(DIRECTORY_SEPARATOR, __DIR__); +					//$namespace_parts = explode('\\', trim(__NAMESPACE__, '\\')); + +					//$namespace_part_count = sizeof($namespace_parts); + +					// Check if we still loading from root +					//if (array_slice($dir_parts, -$namespace_part_count) === $namespace_parts) +					//{ +					//	$this->working_directory = implode('/', array_slice($dir_parts, 0, -$namespace_part_count)); +					//} +					//else +					//{ +					//	$this->working_directory = false; +					//} + +					$this->working_directory = false; +				} +			} + +			if ($this->working_directory !== false) +			{ +				$is_absolute_path = true; +				$path = $this->working_directory . '/' . $path; +			} +		} + +		if ($is_absolute_path) +		{ +			if (defined('PHP_WINDOWS_VERSION_MAJOR')) +			{ +				$path_prefix = $path[0] . ':'; +				$path = substr($path, 2); +			} +			else +			{ +				$path_prefix = ''; +			} +		} + +		$resolved_path = $this->resolve_path($path, $path_prefix, $is_absolute_path); +		if ($resolved_path === false) +		{ +			return false; +		} + +		if (!@file_exists($resolved_path) || (!@is_dir($resolved_path . '/') && !is_file($resolved_path))) +		{ +			return false; +		} + +		// Return OS specific directory separators +		$resolved = str_replace('/', DIRECTORY_SEPARATOR, $resolved_path); + +		// Check for DIRECTORY_SEPARATOR at the end (and remove it!) +		if (substr($resolved, -1) === DIRECTORY_SEPARATOR) +		{ +			return substr($resolved, 0, -1); +		} + +		return $resolved; +	} + +	/** +	 * Convert file(s) to \Traversable object +	 * +	 * This is the same function as Symfony's toIterator, but that is private +	 * so we cannot use it. +	 * +	 * @param string|array|\Traversable	$files	filename/list of filenames +	 * @return \Traversable +	 */ +	protected function to_iterator($files) +	{ +		if (!$files instanceof \Traversable) +		{ +			$files = new \ArrayObject(is_array($files) ? $files : array($files)); +		} + +		return $files; +	} + +	/** +	 * Try to resolve symlinks in path +	 * +	 * @param string	$path			The path to resolve +	 * @param string	$prefix			The path prefix (on windows the drive letter) +	 * @param bool 		$absolute		Whether or not the path is absolute +	 * @param bool		$return_array	Whether or not to return path parts +	 * +	 * @return string|array|bool	returns the resolved path or an array of parts of the path if $return_array is true +	 * 								or false if path cannot be resolved +	 */ +	protected function resolve_path($path, $prefix = '', $absolute = false, $return_array = false) +	{ +		if ($return_array) +		{ +			$path = str_replace(DIRECTORY_SEPARATOR, '/', $path); +		} + +		trim ($path, '/'); +		$path_parts = explode('/', $path); +		$resolved = array(); +		$resolved_path = $prefix; +		$file_found = false; + +		foreach ($path_parts as $path_part) +		{ +			if ($file_found) +			{ +				return false; +			} + +			if (empty($path_part) || ($path_part === '.' && ($absolute || !empty($resolved)))) +			{ +				continue; +			} +			else if ($absolute && $path_part === '..') +			{ +				if (empty($resolved)) +				{ +					// No directories above root +					return false; +				} + +				array_pop($resolved); +				$resolved_path = false; +			} +			else if ($path_part === '..' && !empty($resolved) && !in_array($resolved[sizeof($resolved) - 1], array('.', '..'))) +			{ +				array_pop($resolved); +				$resolved_path = false; +			} +			else +			{ +				if ($resolved_path === false) +				{ +					if (empty($resolved)) +					{ +						$resolved_path = ($absolute) ? $prefix . '/' . $path_part : $path_part; +					} +					else +					{ +						$tmp_array = $resolved; +						if ($absolute) +						{ +							array_unshift($tmp_array, $prefix); +						} + +						$resolved_path = implode('/', $tmp_array); +					} +				} + +				$current_path = $resolved_path . '/' . $path_part; + +				// Resolve symlinks +				if (is_link($current_path)) +				{ +					if (!function_exists('readlink')) +					{ +						return false; +					} + +					$link = readlink($current_path); + +					// Is link has an absolute path in it? +					if ($this->is_absolute_path($link)) +					{ +						if (defined('PHP_WINDOWS_VERSION_MAJOR')) +						{ +							$prefix = $link[0] . ':'; +							$link = substr($link, 2); +						} +						else +						{ +							$prefix = ''; +						} + +						$resolved = $this->resolve_path($link, $prefix, true, true); +						$absolute = true; +					} +					else +					{ +						$resolved = $this->resolve_path($resolved_path . '/' . $link, $prefix, $absolute, true); +					} + +					if (!$resolved) +					{ +						return false; +					} + +					$resolved_path = false; +				} +				else if (is_dir($current_path . '/')) +				{ +					$resolved[] = $path_part; +					$resolved_path = $current_path; +				} +				else if (is_file($current_path)) +				{ +					$resolved[] = $path_part; +					$resolved_path = $current_path; +					$file_found = true; +				} +				else +				{ +					return false; +				} +			} +		} + +		// If at the end of the path there were a .. or . +		// we need to build the path again. +		// Only doing this when a string is expected in return +		if ($resolved_path === false && $return_array === false) +		{ +			if (empty($resolved)) +			{ +				$resolved_path = ($absolute) ? $prefix . '/' : './'; +			} +			else +			{ +				$tmp_array = $resolved; +				if ($absolute) +				{ +					array_unshift($tmp_array, $prefix); +				} + +				$resolved_path = implode('/', $tmp_array); +			} +		} + +		return ($return_array) ? $resolved : $resolved_path; +	} +} diff --git a/phpBB/phpbb/filesystem/filesystem_interface.php b/phpBB/phpbb/filesystem/filesystem_interface.php new file mode 100644 index 0000000000..21ad8252f8 --- /dev/null +++ b/phpBB/phpbb/filesystem/filesystem_interface.php @@ -0,0 +1,284 @@ +<?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\filesystem; + +/** + * Interface for phpBB's filesystem service + */ +interface filesystem_interface +{ +	/** +	 * chmod all permissions flag +	 * +	 * @var int +	 */ +	const CHMOD_ALL = 7; + +	/** +	 * chmod read permissions flag +	 * +	 * @var int +	 */ +	const CHMOD_READ = 4; + +	/** +	 * chmod write permissions flag +	 * +	 * @var int +	 */ +	const CHMOD_WRITE = 2; + +	/** +	 * chmod execute permissions flag +	 * +	 * @var int +	 */ +	const CHMOD_EXECUTE = 1; + +	/** +	 * Change owner group of files/directories +	 * +	 * @param string|array|\Traversable	$files		The file(s)/directorie(s) to change group +	 * @param string					$group		The group that should own the files/directories +	 * @param bool 						$recursive	If the group should be changed recursively +	 * @throws \phpbb\filesystem\exception\filesystem_exception	the filename which triggered the error can be +	 * 															retrieved by filesystem_exception::get_filename() +	 */ +	public function chgrp($files, $group, $recursive = false); + +	/** +	 * Global function for chmodding directories and files for internal use +	 * +	 * The function accepts filesystem_interface::CHMOD_ flags in the permission argument +	 * or the user can specify octal values (or any integer if it makes sense). All directories will have +	 * an execution bit appended, if the user group (owner, group or other) has any bit specified. +	 * +	 * @param string|array|\Traversable	$file				The file/directory to be chmodded +	 * @param int						$perms				Permissions to set +	 * @param bool						$recursive			If the permissions should be changed recursively +	 * @param bool						$force_chmod_link	Try to apply permissions to symlinks as well +	 * +	 * @throws \phpbb\filesystem\exception\filesystem_exception	the filename which triggered the error can be +	 * 															retrieved by filesystem_exception::get_filename() +	 */ +	public function chmod($files, $perms = null, $recursive = false, $force_chmod_link = false); + +	/** +	 * Change owner group of files/directories +	 * +	 * @param string|array|\Traversable	$files		The file(s)/directorie(s) to change group +	 * @param string					$user		The owner user name +	 * @param bool 						$recursive	Whether change the owner recursively or not +	 * +	 * @throws \phpbb\filesystem\exception\filesystem_exception	the filename which triggered the error can be +	 * 															retrieved by filesystem_exception::get_filename() +	 */ +	public function chown($files, $user, $recursive = false); + +	/** +	 * Eliminates useless . and .. components from specified path. +	 * +	 * @param string $path Path to clean +	 * +	 * @return string Cleaned path +	 */ +	public function clean_path($path); + +	/** +	 * Copies a file. +	 * +	 * This method only copies the file if the origin file is newer than the target file. +	 * +	 * By default, if the target already exists, it is not overridden. +	 * +	 * @param string	$origin_file	The original filename +	 * @param string	$target_file	The target filename +	 * @param bool		$override		Whether to override an existing file or not +	 * +	 * @throws \phpbb\filesystem\exception\filesystem_exception When the file cannot be copied +	 */ +	public function copy($origin_file, $target_file, $override = false); + +	/** +	 * Atomically dumps content into a file. +	 * +	 * @param string	$filename	The file to be written to. +	 * @param string	$content	The data to write into the file. +	 * +	 * @throws \phpbb\filesystem\exception\filesystem_exception When the file cannot be written +	 */ +	public function dump_file($filename, $content); + +	/** +	 * Checks the existence of files or directories. +	 * +	 * @param string|array|\Traversable	$files	files/directories to check +	 * +	 * @return bool	Returns true if all files/directories exist, false otherwise +	 */ +	public function exists($files); + +	/** +	 * Checks if a path is absolute or not +	 * +	 * @param string	$path	Path to check +	 * +	 * @return	bool	true if the path is absolute, false otherwise +	 */ +	public function is_absolute_path($path); + +	/** +	 * Checks if files/directories are readable +	 * +	 * @param string|array|\Traversable	$files		files/directories to check +	 * @param bool						$recursive	Whether or not directories should be checked recursively +	 * +	 * @return bool True when the files/directories are readable, otherwise false. +	 */ +	public function is_readable($files, $recursive = false); + +	/** +	 * Test if a file/directory is writable +	 * +	 * @param string|array|\Traversable	$files		files/directories to perform write test on +	 * @param bool						$recursive	Whether or not directories should be checked recursively +	 * +	 * @return bool True when the files/directories are writable, otherwise false. +	 */ +	public function is_writable($files, $recursive = false); + +	/** +	 * Given an existing path, convert it to a path relative to a given starting path +	 * +	 * @param string $end_path		Absolute path of target +	 * @param string $start_path	Absolute path where traversal begins +	 * +	 * @return string Path of target relative to starting path +	 */ +	public function make_path_relative($end_path, $start_path); + +	/** +	 * Mirrors a directory to another. +	 * +	 * @param string		$origin_dir	The origin directory +	 * @param string		$target_dir	The target directory +	 * @param \Traversable	$iterator	A Traversable instance +	 * @param array			$options	An array of boolean options +	 *									Valid options are: +	 *										- $options['override'] Whether to override an existing file on copy or not (see copy()) +	 *										- $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink()) +	 *										- $options['delete'] Whether to delete files that are not in the source directory (defaults to false) +	 * +	 * @throws \phpbb\filesystem\exception\filesystem_exception When the file cannot be copied. +	 * 															The filename which triggered the error can be +	 * 															retrieved by filesystem_exception::get_filename() +	 */ +	public function mirror($origin_dir, $target_dir, \Traversable $iterator = null, $options = array()); + +	/** +	 * Creates a directory recursively. +	 * +	 * @param string|array|\Traversable	$dirs	The directory path +	 * @param int						$mode	The directory mode +	 * +	 * @throws \phpbb\filesystem\exception\filesystem_exception On any directory creation failure +	 * 															The filename which triggered the error can be +	 * 															retrieved by filesystem_exception::get_filename() +	 */ +	public function mkdir($dirs, $mode = 0777); + +	/** +	 * Global function for chmodding directories and files for internal use +	 * +	 * This function determines owner and group whom the file belongs to and user and group of PHP and then set safest possible file permissions. +	 * The function determines owner and group from common.php file and sets the same to the provided file. +	 * The function uses bit fields to build the permissions. +	 * The function sets the appropiate execute bit on directories. +	 * +	 * Supported constants representing bit fields are: +	 * +	 * filesystem_interface::CHMOD_ALL - all permissions (7) +	 * filesystem_interface::CHMOD_READ - read permission (4) +	 * filesystem_interface::CHMOD_WRITE - write permission (2) +	 * filesystem_interface::CHMOD_EXECUTE - execute permission (1) +	 * +	 * NOTE: The function uses POSIX extension and fileowner()/filegroup() functions. If any of them is disabled, this function tries to build proper permissions, by calling is_readable() and is_writable() functions. +	 * +	 * @param string|array|\Traversable	$file				The file/directory to be chmodded +	 * @param int						$perms				Permissions to set +	 * @param bool						$recursive			If the permissions should be changed recursively +	 * @param bool						$force_chmod_link	Try to apply permissions to symlinks as well +	 * +	 * @throws \phpbb\filesystem\exception\filesystem_exception	the filename which triggered the error can be +	 * 															retrieved by filesystem_exception::get_filename() +	 */ +	public function phpbb_chmod($file, $perms = null, $recursive = false, $force_chmod_link = false); + +	/** +	 * A wrapper for PHP's realpath +	 * +	 * Try to resolve realpath when PHP's realpath is not available, or +	 * known to be buggy. +	 * +	 * @param string	$path	Path to resolve +	 * +	 * @return string	Resolved path +	 */ +	public function realpath($path); + +	/** +	 * Removes files or directories. +	 * +	 * @param string|array|\Traversable	$files	A filename, an array of files, or a \Traversable instance to remove +	 * +	 * @throws \phpbb\filesystem\exception\filesystem_exception When removal fails. +	 * 															The filename which triggered the error can be +	 * 															retrieved by filesystem_exception::get_filename() +	 */ +	public function remove($files); + +	/** +	 * Renames a file or a directory. +	 * +	 * @param string	$origin		The origin filename or directory +	 * @param string	$target		The new filename or directory +	 * @param bool		$overwrite	Whether to overwrite the target if it already exists +	 * +	 * @throws \phpbb\filesystem\exception\filesystem_exception	When target file or directory already exists, +	 * 															or origin cannot be renamed. +	 */ +	public function rename($origin, $target, $overwrite = false); + +	/** +	 * Creates a symbolic link or copy a directory. +	 * +	 * @param string	$origin_dir			The origin directory path +	 * @param string	$target_dir			The symbolic link name +	 * @param bool		$copy_on_windows	Whether to copy files if on Windows +	 * +	 * @throws \phpbb\filesystem\exception\filesystem_exception When symlink fails +	 */ +	public function symlink($origin_dir, $target_dir, $copy_on_windows = false); + +	/** +	 * Sets access and modification time of file. +	 * +	 * @param string|array|\Traversable	$files			A filename, an array of files, or a \Traversable instance to create +	 * @param int						$time			The touch time as a Unix timestamp +	 * @param int						$access_time	The access time as a Unix timestamp +	 * +	 * @throws \phpbb\filesystem\exception\filesystem_exception	When touch fails +	 */ +	public function touch($files, $time = null, $access_time = null); +} diff --git a/phpBB/phpbb/finder.php b/phpBB/phpbb/finder.php index 28f28825ba..58bc27084e 100644 --- a/phpBB/phpbb/finder.php +++ b/phpBB/phpbb/finder.php @@ -48,14 +48,14 @@ class finder  	/**  	* Creates a new finder instance with its dependencies  	* -	* @param \phpbb\filesystem $filesystem Filesystem instance +	* @param \phpbb\filesystem\filesystem_interface $filesystem Filesystem instance  	* @param string $phpbb_root_path Path to the phpbb root directory  	* @param \phpbb\cache\driver\driver_interface $cache A cache instance or null  	* @param string $php_ext php file extension  	* @param string $cache_name The name of the cache variable, defaults to  	*                           _ext_finder  	*/ -	public function __construct(\phpbb\filesystem $filesystem, $phpbb_root_path = '', \phpbb\cache\driver\driver_interface $cache = null, $php_ext = 'php', $cache_name = '_ext_finder') +	public function __construct(\phpbb\filesystem\filesystem_interface $filesystem, $phpbb_root_path = '', \phpbb\cache\driver\driver_interface $cache = null, $php_ext = 'php', $cache_name = '_ext_finder')  	{  		$this->filesystem = $filesystem;  		$this->phpbb_root_path = $phpbb_root_path; diff --git a/phpBB/phpbb/help/controller/help.php b/phpBB/phpbb/help/controller/help.php new file mode 100644 index 0000000000..9cc3b0c8b4 --- /dev/null +++ b/phpBB/phpbb/help/controller/help.php @@ -0,0 +1,160 @@ +<?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\help\controller; + +use phpbb\exception\http_exception; + +class help +{ +	/** @var \phpbb\controller\helper */ +	protected $helper; + +	/** @var \phpbb\event\dispatcher_interface  */ +	protected $dispatcher; + +	/** @var \phpbb\template\template */ +	protected $template; + +	/** @var \phpbb\user */ +	protected $user; + +	/** @var string */ +	protected $root_path; + +	/** @var string */ +	protected $php_ext; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\controller\helper	$helper +	 * @param \phpbb\event\dispatcher_interface	$dispatcher +	 * @param \phpbb\template\template	$template +	 * @param \phpbb\user				$user +	 * @param string					$root_path +	 * @param string					$php_ext +	 */ +	public function __construct(\phpbb\controller\helper $helper, \phpbb\event\dispatcher_interface $dispatcher, \phpbb\template\template $template, \phpbb\user $user, $root_path, $php_ext) +	{ +		$this->helper = $helper; +		$this->dispatcher = $dispatcher; +		$this->template = $template; +		$this->user = $user; +		$this->root_path = $root_path; +		$this->php_ext = $php_ext; +	} + +	/** +	 * Controller for /help/{mode} routes +	 * +	 * @param string		$mode +	 * @return \Symfony\Component\HttpFoundation\Response A Symfony Response object +	 * @throws http_exception when the $mode is not known by any extension +	 */ +	public function handle($mode) +	{ +		switch ($mode) +		{ +			case 'faq': +			case 'bbcode': +				$page_title = ($mode === 'faq') ? $this->user->lang['FAQ_EXPLAIN'] : $this->user->lang['BBCODE_GUIDE']; +				$this->user->add_lang($mode, false, true); +			break; + +			default: +				$page_title = $this->user->lang['FAQ_EXPLAIN']; +				$ext_name = $lang_file = ''; + +				/** +				 * You can use this event display a custom help page +				 * +				 * @event core.faq_mode_validation +				 * @var	string	page_title		Title of the page +				 * @var	string	mode			FAQ that is going to be displayed +				 * @var	string	lang_file		Language file containing the help data +				 * @var	string	ext_name		Vendor and extension name where the help +				 *								language file can be loaded from +				 * @since 3.1.4-RC1 +				 */ +				$vars = array( +					'page_title', +					'mode', +					'lang_file', +					'ext_name', +				); +				extract($this->dispatcher->trigger_event('core.faq_mode_validation', compact($vars))); + +				if ($ext_name === '' || $lang_file === '') +				{ +					throw new http_exception(404, 'Not Found'); +				} + +				$this->user->add_lang($lang_file, false, true, $ext_name); +			break; + +		} + +		$this->template->assign_vars(array( +			'L_FAQ_TITLE'				=> $page_title, +			'S_IN_FAQ'					=> true, +		)); + +		$this->assign_to_template($this->user->help); + +		make_jumpbox(append_sid("{$this->root_path}viewforum.{$this->php_ext}")); +		return $this->helper->render('faq_body.html', $page_title); +	} + +	/** +	 * Assigns the help data to the template blocks +	 * +	 * @param array $help_data +	 * @return null +	 */ +	protected function assign_to_template(array $help_data) +	{ +		// Pull the array data from the lang pack +		$switch_column = $found_switch = false; +		foreach ($help_data as $help_ary) +		{ +			if ($help_ary[0] == '--') +			{ +				if ($help_ary[1] == '--') +				{ +					$switch_column = true; +					$found_switch = true; +					continue; +				} + +				$this->template->assign_block_vars('faq_block', array( +					'BLOCK_TITLE'		=> $help_ary[1], +					'SWITCH_COLUMN'		=> $switch_column, +				)); + +				if ($switch_column) +				{ +					$switch_column = false; +				} +				continue; +			} + +			$this->template->assign_block_vars('faq_block.faq_row', array( +				'FAQ_QUESTION'		=> $help_ary[0], +				'FAQ_ANSWER'		=> $help_ary[1], +			)); +		} + +		$this->template->assign_var('SWITCH_COLUMN_MANUALLY', !$found_switch); +	} +} diff --git a/phpBB/phpbb/language/exception/invalid_plural_rule_exception.php b/phpBB/phpbb/language/exception/invalid_plural_rule_exception.php new file mode 100644 index 0000000000..94e3466208 --- /dev/null +++ b/phpBB/phpbb/language/exception/invalid_plural_rule_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\language\exception; + +/** + * Thrown when nonexistent plural rule is specified + */ +class invalid_plural_rule_exception extends language_exception +{ + +} diff --git a/phpBB/phpbb/language/exception/language_exception.php b/phpBB/phpbb/language/exception/language_exception.php new file mode 100644 index 0000000000..b1258414aa --- /dev/null +++ b/phpBB/phpbb/language/exception/language_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\language\exception; + +/** + * Base exception class for language exceptions + */ +class language_exception extends \phpbb\exception\runtime_exception +{ + +} diff --git a/phpBB/phpbb/language/exception/language_file_not_found.php b/phpBB/phpbb/language/exception/language_file_not_found.php new file mode 100644 index 0000000000..89364267eb --- /dev/null +++ b/phpBB/phpbb/language/exception/language_file_not_found.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\language\exception; + +/** + * This exception is thrown when the language file is not found + */ +class language_file_not_found extends language_exception +{ + +} diff --git a/phpBB/phpbb/language/language.php b/phpBB/phpbb/language/language.php new file mode 100644 index 0000000000..3298908365 --- /dev/null +++ b/phpBB/phpbb/language/language.php @@ -0,0 +1,571 @@ +<?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\language; + +use phpbb\language\exception\invalid_plural_rule_exception; + +/** + * Wrapper class for loading translations + */ +class language +{ +	/** +	 * Global fallback language +	 * +	 * ISO code of the language to fallback to when the specified language entries +	 * cannot be found. +	 * +	 * @var string +	 */ +	const FALLBACK_LANGUAGE = 'en'; + +	/** +	 * @var array	List of common language files +	 */ +	protected $common_language_files; + +	/** +	 * @var bool +	 */ +	protected $common_language_files_loaded; + +	/** +	 * @var string	ISO code of the default board language +	 */ +	protected $default_language; + +	/** +	 * @var string	ISO code of the User's language +	 */ +	protected $user_language; + +	/** +	 * @var array	Language fallback array (the order is important) +	 */ +	protected $language_fallback; + +	/** +	 * @var array	Array of language variables +	 */ +	protected $lang; + +	/** +	 * @var array	Loaded language sets +	 */ +	protected $loaded_language_sets; + +	/** +	 * @var \phpbb\language\language_file_loader Language file loader +	 */ +	protected $loader; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\language\language_file_loader	$loader			Language file loader +	 * @param array|null							$common_modules	Array of common language modules to load (optional) +	 */ +	public function __construct(language_file_loader $loader, $common_modules = null) +	{ +		$this->loader = $loader; + +		// Set up default information +		$this->user_language		= false; +		$this->default_language		= false; +		$this->lang					= array( +			// For BC with user::help array +			'__help' => array(), +		); +		$this->loaded_language_sets	= array( +			'core'	=> array(), +			'ext'	=> array(), +		); + +		// Common language files +		if (is_array($common_modules)) +		{ +			$this->common_language_files = $common_modules; +		} +		else +		{ +			$this->common_language_files = array( +				'common', +			); +		} + +		$this->common_language_files_loaded = false; + +		$this->language_fallback = array(self::FALLBACK_LANGUAGE); +	} + +	/** +	 * Function to set user's language to display. +	 * +	 * @param string	$user_lang_iso	ISO code of the User's language +	 */ +	public function set_user_language($user_lang_iso) +	{ +		$this->user_language = $user_lang_iso; + +		$this->set_fallback_array(); +	} + +	/** +	 * Function to set the board's default language to display. +	 * +	 * @param string	$default_lang_iso	ISO code of the board's default language +	 */ +	public function set_default_language($default_lang_iso) +	{ +		$this->default_language = $default_lang_iso; + +		$this->set_fallback_array(); +	} + +	/** +	 * Returns language array +	 * +	 * Note: This function is needed for the BC purposes, until \phpbb\user::lang[] is +	 *       not removed. +	 * +	 * @return array	Array of loaded language strings +	 */ +	public function get_lang_array() +	{ +		// Load common language files if they not loaded yet +		if (!$this->common_language_files_loaded) +		{ +			$this->load_common_language_files(); +		} + +		return $this->lang; +	} + +	/** +	 * Add Language Items +	 * +	 * Note: $use_help is assigned where needed (only use them to force inclusion). +	 * +	 * Examples: +	 * <code> +	 * $component = array('posting'); +	 * $component = array('posting', 'viewtopic') +	 * $component = 'posting' +	 * </code> +	 * +	 * @param string|array	$component		The name of the language component to load +	 * @param string|null	$extension_name	Name of the extension to load component from, or null for core file +	 */ +	public function add_lang($component, $extension_name = null) +	{ +		// Load common language files if they not loaded yet +		// This needs to be here to correctly merge language arrays +		if (!$this->common_language_files_loaded) +		{ +			$this->load_common_language_files(); +		} + +		if (!is_array($component)) +		{ +			if (!is_null($extension_name)) +			{ +				$this->load_extension($extension_name, $component); +			} +			else +			{ +				$this->load_core_file($component); +			} +		} +		else +		{ +			foreach ($component as $lang_file) +			{ +				$this->add_lang($lang_file, $extension_name); +			} +		} +	} + +	/** +	 * Advanced language substitution +	 * +	 * Function to mimic sprintf() with the possibility of using phpBB's language system to substitute nullar/singular/plural forms. +	 * Params are the language key and the parameters to be substituted. +	 * This function/functionality is inspired by SHS` and Ashe. +	 * +	 * Example call: <samp>$user->lang('NUM_POSTS_IN_QUEUE', 1);</samp> +	 * +	 * If the first parameter is an array, the elements are used as keys and subkeys to get the language entry: +	 * Example: <samp>$user->lang(array('datetime', 'AGO'), 1)</samp> uses $user->lang['datetime']['AGO'] as language entry. +	 * +	 * @return string	Return localized string or the language key if the translation is not available +	 */ +	public function lang() +	{ +		// Load common language files if they not loaded yet +		if (!$this->common_language_files_loaded) +		{ +			$this->load_common_language_files(); +		} + +		$args = func_get_args(); +		$key = $args[0]; + +		if (is_array($key)) +		{ +			$lang = &$this->lang[array_shift($key)]; + +			foreach ($key as $_key) +			{ +				$lang = &$lang[$_key]; +			} +		} +		else +		{ +			$lang = &$this->lang[$key]; +		} + +		// Return if language string does not exist +		if (!isset($lang) || (!is_string($lang) && !is_array($lang))) +		{ +			return $key; +		} + +		// If the language entry is a string, we simply mimic sprintf() behaviour +		if (is_string($lang)) +		{ +			if (sizeof($args) == 1) +			{ +				return $lang; +			} + +			// Replace key with language entry and simply pass along... +			$args[0] = $lang; +			return call_user_func_array('sprintf', $args); +		} +		else if (sizeof($lang) == 0) +		{ +			// If the language entry is an empty array, we just return the language key +			return $args[0]; +		} + +		// It is an array... now handle different nullar/singular/plural forms +		$key_found = false; + +		// We now get the first number passed and will select the key based upon this number +		for ($i = 1, $num_args = sizeof($args); $i < $num_args; $i++) +		{ +			if (is_int($args[$i]) || is_float($args[$i])) +			{ +				if ($args[$i] == 0 && isset($lang[0])) +				{ +					// We allow each translation using plural forms to specify a version for the case of 0 things, +					// so that "0 users" may be displayed as "No users". +					$key_found = 0; +					break; +				} +				else +				{ +					$use_plural_form = $this->get_plural_form($args[$i]); +					if (isset($lang[$use_plural_form])) +					{ +						// The key we should use exists, so we use it. +						$key_found = $use_plural_form; +					} +					else +					{ +						// If the key we need to use does not exist, we fall back to the previous one. +						$numbers = array_keys($lang); + +						foreach ($numbers as $num) +						{ +							if ($num > $use_plural_form) +							{ +								break; +							} + +							$key_found = $num; +						} +					} +					break; +				} +			} +		} + +		// Ok, let's check if the key was found, else use the last entry (because it is mostly the plural form) +		if ($key_found === false) +		{ +			$numbers = array_keys($lang); +			$key_found = end($numbers); +		} + +		// Use the language string we determined and pass it to sprintf() +		$args[0] = $lang[$key_found]; +		return call_user_func_array('sprintf', $args); +	} + +	/** +	 * Loads common language files +	 */ +	protected function load_common_language_files() +	{ +		if (!$this->common_language_files_loaded) +		{ +			foreach ($this->common_language_files as $lang_file) +			{ +				$this->load_core_file($lang_file); +			} + +			$this->common_language_files_loaded = true; +		} +	} + +	/** +	 * Determine which plural form we should use. +	 * +	 * For some languages this is not as simple as for English. +	 * +	 * @param int|float		$number		The number we want to get the plural case for. Float numbers are floored. +	 * @param int|bool		$force_rule	False to use the plural rule of the language package +	 *									or an integer to force a certain plural rule +	 * +	 * @return int	The plural-case we need to use for the number plural-rule combination +	 * +	 * @throws \phpbb\language\exception\invalid_plural_rule_exception	When $force_rule has an invalid value +	 */ +	public function get_plural_form($number, $force_rule = false) +	{ +		$number			= (int) $number; +		$plural_rule	= ($force_rule !== false) ? $force_rule : ((isset($this->lang['PLURAL_RULE'])) ? $this->lang['PLURAL_RULE'] : 1); + +		if ($plural_rule > 15 || $plural_rule < 0) +		{ +			throw new invalid_plural_rule_exception('INVALID_PLURAL_RULE', array( +				'plural_rule' => $plural_rule, +			)); +		} + +		/** +		 * The following plural rules are based on a list published by the Mozilla Developer Network +		 * https://developer.mozilla.org/en/Localization_and_Plurals +		 */ +		switch ($plural_rule) +		{ +			case 0: +				/** +				 * Families: Asian (Chinese, Japanese, Korean, Vietnamese), Persian, Turkic/Altaic (Turkish), Thai, Lao +				 * 1 - everything: 0, 1, 2, ... +				 */ +				return 1; + +			case 1: +				/** +				 * Families: Germanic (Danish, Dutch, English, Faroese, Frisian, German, Norwegian, Swedish), Finno-Ugric (Estonian, Finnish, Hungarian), Language isolate (Basque), Latin/Greek (Greek), Semitic (Hebrew), Romanic (Italian, Portuguese, Spanish, Catalan) +				 * 1 - 1 +				 * 2 - everything else: 0, 2, 3, ... +				 */ +				return ($number === 1) ? 1 : 2; + +			case 2: +				/** +				 * Families: Romanic (French, Brazilian Portuguese) +				 * 1 - 0, 1 +				 * 2 - everything else: 2, 3, ... +				 */ +				return (($number === 0) || ($number === 1)) ? 1 : 2; + +			case 3: +				/** +				 * Families: Baltic (Latvian) +				 * 1 - 0 +				 * 2 - ends in 1, not 11: 1, 21, ... 101, 121, ... +				 * 3 - everything else: 2, 3, ... 10, 11, 12, ... 20, 22, ... +				 */ +				return ($number === 0) ? 1 : ((($number % 10 === 1) && ($number % 100 != 11)) ? 2 : 3); + +			case 4: +				/** +				 * Families: Celtic (Scottish Gaelic) +				 * 1 - is 1 or 11: 1, 11 +				 * 2 - is 2 or 12: 2, 12 +				 * 3 - others between 3 and 19: 3, 4, ... 10, 13, ... 18, 19 +				 * 4 - everything else: 0, 20, 21, ... +				 */ +				return ($number === 1 || $number === 11) ? 1 : (($number === 2 || $number === 12) ? 2 : (($number >= 3 && $number <= 19) ? 3 : 4)); + +			case 5: +				/** +				 * Families: Romanic (Romanian) +				 * 1 - 1 +				 * 2 - is 0 or ends in 01-19: 0, 2, 3, ... 19, 101, 102, ... 119, 201, ... +				 * 3 - everything else: 20, 21, ... +				 */ +				return ($number === 1) ? 1 : ((($number === 0) || (($number % 100 > 0) && ($number % 100 < 20))) ? 2 : 3); + +			case 6: +				/** +				 * Families: Baltic (Lithuanian) +				 * 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, ... +				 * 2 - ends in 0 or ends in 10-20: 0, 10, 11, 12, ... 19, 20, 30, 40, ... +				 * 3 - everything else: 2, 3, ... 8, 9, 22, 23, ... 29, 32, 33, ... +				 */ +				return (($number % 10 === 1) && ($number % 100 != 11)) ? 1 : ((($number % 10 < 2) || (($number % 100 >= 10) && ($number % 100 < 20))) ? 2 : 3); + +			case 7: +				/** +				 * Families: Slavic (Croatian, Serbian, Russian, Ukrainian) +				 * 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, ... +				 * 2 - ends in 2-4, not 12-14: 2, 3, 4, 22, 23, 24, 32, ... +				 * 3 - everything else: 0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 26, ... +				 */ +				return (($number % 10 === 1) && ($number % 100 != 11)) ? 1 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 2 : 3); + +			case 8: +				/** +				 * Families: Slavic (Slovak, Czech) +				 * 1 - 1 +				 * 2 - 2, 3, 4 +				 * 3 - everything else: 0, 5, 6, 7, ... +				 */ +				return ($number === 1) ? 1 : ((($number >= 2) && ($number <= 4)) ? 2 : 3); + +			case 9: +				/** +				 * Families: Slavic (Polish) +				 * 1 - 1 +				 * 2 - ends in 2-4, not 12-14: 2, 3, 4, 22, 23, 24, 32, ... 104, 122, ... +				 * 3 - everything else: 0, 5, 6, ... 11, 12, 13, 14, 15, ... 20, 21, 25, ... +				 */ +				return ($number === 1) ? 1 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 2 : 3); + +			case 10: +				/** +				 * Families: Slavic (Slovenian, Sorbian) +				 * 1 - ends in 01: 1, 101, 201, ... +				 * 2 - ends in 02: 2, 102, 202, ... +				 * 3 - ends in 03-04: 3, 4, 103, 104, 203, 204, ... +				 * 4 - everything else: 0, 5, 6, 7, 8, 9, 10, 11, ... +				 */ +				return ($number % 100 === 1) ? 1 : (($number % 100 === 2) ? 2 : ((($number % 100 === 3) || ($number % 100 === 4)) ? 3 : 4)); + +			case 11: +				/** +				 * Families: Celtic (Irish Gaeilge) +				 * 1 - 1 +				 * 2 - 2 +				 * 3 - is 3-6: 3, 4, 5, 6 +				 * 4 - is 7-10: 7, 8, 9, 10 +				 * 5 - everything else: 0, 11, 12, ... +				 */ +				return ($number === 1) ? 1 : (($number === 2) ? 2 : (($number >= 3 && $number <= 6) ? 3 : (($number >= 7 && $number <= 10) ? 4 : 5))); + +			case 12: +				/** +				 * Families: Semitic (Arabic) +				 * 1 - 1 +				 * 2 - 2 +				 * 3 - ends in 03-10: 3, 4, ... 10, 103, 104, ... 110, 203, 204, ... +				 * 4 - ends in 11-99: 11, ... 99, 111, 112, ... +				 * 5 - everything else: 100, 101, 102, 200, 201, 202, ... +				 * 6 - 0 +				 */ +				return ($number === 1) ? 1 : (($number === 2) ? 2 : ((($number % 100 >= 3) && ($number % 100 <= 10)) ? 3 : ((($number % 100 >= 11) && ($number % 100 <= 99)) ? 4 : (($number != 0) ? 5 : 6)))); + +			case 13: +				/** +				 * Families: Semitic (Maltese) +				 * 1 - 1 +				 * 2 - is 0 or ends in 01-10: 0, 2, 3, ... 9, 10, 101, 102, ... +				 * 3 - ends in 11-19: 11, 12, ... 18, 19, 111, 112, ... +				 * 4 - everything else: 20, 21, ... +				 */ +				return ($number === 1) ? 1 : ((($number === 0) || (($number % 100 > 1) && ($number % 100 < 11))) ? 2 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 3 : 4)); + +			case 14: +				/** +				 * Families: Slavic (Macedonian) +				 * 1 - ends in 1: 1, 11, 21, ... +				 * 2 - ends in 2: 2, 12, 22, ... +				 * 3 - everything else: 0, 3, 4, ... 10, 13, 14, ... 20, 23, ... +				 */ +				return ($number % 10 === 1) ? 1 : (($number % 10 === 2) ? 2 : 3); + +			case 15: +				/** +				 * Families: Icelandic +				 * 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, 131, ... +				 * 2 - everything else: 0, 2, 3, ... 10, 11, 12, ... 20, 22, ... +				 */ +				return (($number % 10 === 1) && ($number % 100 != 11)) ? 1 : 2; +		} +	} + +	/** +	 * Returns language fallback data +	 * +	 * @return array +	 */ +	protected function set_fallback_array() +	{ +		$fallback_array = array(); + +		if ($this->user_language !== false) +		{ +			$fallback_array[] = $this->user_language; +		} + +		if ($this->default_language !== false) +		{ +			$fallback_array[] = $this->default_language; +		} + +		$fallback_array[] = self::FALLBACK_LANGUAGE; + +		$this->language_fallback = $fallback_array; +	} + +	/** +	 * Load core language file +	 * +	 * @param string	$component	Name of the component to load +	 */ +	protected function load_core_file($component) +	{ +		// Check if the component is already loaded +		if (isset($this->loaded_language_sets['core'][$component])) +		{ +			return; +		} + +		$this->loader->load($component, $this->language_fallback, $this->lang); +		$this->loaded_language_sets['core'][$component] = true; +	} + +	/** +	 * Load extension language file +	 * +	 * @param string	$extension_name	Name of the extension to load language from +	 * @param string	$component		Name of the component to load +	 */ +	protected function load_extension($extension_name, $component) +	{ +		// Check if the component is already loaded +		if (isset($this->loaded_language_sets['ext'][$extension_name][$component])) +		{ +			return; +		} + +		$this->loader->load_extension($extension_name, $component, $this->language_fallback, $this->lang); +		$this->loaded_language_sets['ext'][$extension_name][$component] = true; +	} +} diff --git a/phpBB/phpbb/language/language_file_helper.php b/phpBB/phpbb/language/language_file_helper.php new file mode 100644 index 0000000000..18d7b62e21 --- /dev/null +++ b/phpBB/phpbb/language/language_file_helper.php @@ -0,0 +1,71 @@ +<?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\language; + +use Symfony\Component\Finder\Finder; + +/** + * Helper class for language file related functions + */ +class language_file_helper +{ +	/** +	 * @var string	Path to phpBB's root +	 */ +	protected $phpbb_root_path; + +	/** +	 * Constructor +	 * +	 * @param string	$phpbb_root_path	Path to phpBB's root +	 */ +	public function __construct($phpbb_root_path) +	{ +		$this->phpbb_root_path = $phpbb_root_path; +	} + +	/** +	 * Returns available languages +	 * +	 * @return array +	 */ +	public function get_available_languages() +	{ +		// Find available language packages +		$finder = new Finder(); +		$finder->files() +			->name('iso.txt') +			->depth('== 1') +			->in($this->phpbb_root_path . 'language'); + +		$available_languages = array(); +		foreach ($finder as $file) +		{ +			$path = $file->getRelativePath(); +			$info = explode("\n", $file->getContents()); + +			$available_languages[] = array( +				// Get the name of the directory containing iso.txt +				'iso' => $path, + +				// Recover data from file +				'name' => trim($info[0]), +				'local_name' => trim($info[1]), +				'author' => trim($info[2]) +			); +		} + +		return $available_languages; +	} +} diff --git a/phpBB/phpbb/language/language_file_loader.php b/phpBB/phpbb/language/language_file_loader.php new file mode 100644 index 0000000000..510a29279a --- /dev/null +++ b/phpBB/phpbb/language/language_file_loader.php @@ -0,0 +1,212 @@ +<?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\language; + +use \phpbb\language\exception\language_file_not_found; + +/** + * Language file loader + */ +class language_file_loader +{ +	/** +	 * @var string	Path to phpBB's root +	 */ +	protected $phpbb_root_path; + +	/** +	 * @var string	Extension of PHP files +	 */ +	protected $php_ext; + +	/** +	 * @var \phpbb\extension\manager	Extension manager +	 */ +	protected $extension_manager; + +	/** +	 * Constructor +	 * +	 * @param string	$phpbb_root_path	Path to phpBB's root +	 * @param string	$php_ext			Extension of PHP files +	 */ +	public function __construct($phpbb_root_path, $php_ext) +	{ +		$this->phpbb_root_path	= $phpbb_root_path; +		$this->php_ext			= $php_ext; + +		$this->extension_manager = null; +	} + +	/** +	 * Extension manager setter +	 * +	 * @param \phpbb\extension\manager	$extension_manager	Extension manager +	 */ +	public function set_extension_manager(\phpbb\extension\manager $extension_manager) +	{ +		$this->extension_manager = $extension_manager; +	} + +	/** +	 * Loads language array for the given component +	 * +	 * @param string		$component	Name of the language component +	 * @param string|array	$locale		ISO code of the language to load, or array of ISO codes if you want to +	 * 									specify additional language fallback steps +	 * @param array			$lang		Array reference containing language strings +	 */ +	public function load($component, $locale, &$lang) +	{ +		$locale = (array) $locale; + +		// Determine path to language directory +		$path = $this->phpbb_root_path . 'language/'; + +		$this->load_file($path, $component, $locale, $lang); +	} + +	/** +	 * Loads language array for the given extension component +	 * +	 * @param string		$extension	Name of the extension +	 * @param string		$component	Name of the language component +	 * @param string|array	$locale		ISO code of the language to load, or array of ISO codes if you want to +	 * 									specify additional language fallback steps +	 * @param array			$lang		Array reference containing language strings +	 */ +	public function load_extension($extension, $component, $locale, &$lang) +	{ +		// Check if extension manager was loaded +		if ($this->extension_manager === null) +		{ +			// If not, let's return +			return; +		} + +		$locale = (array) $locale; + +		// Determine path to language directory +		$path = $this->extension_manager->get_extension_path($extension, true) . 'language/'; + +		$this->load_file($path, $component, $locale, $lang); +	} + +	/** +	 * Prepares language file loading +	 * +	 * @param string	$path		Path to search for file in +	 * @param string	$component	Name of the language component +	 * @param array		$locale		Array containing language fallback options +	 * @param array		$lang		Array reference of language strings +	 */ +	protected function load_file($path, $component, $locale, &$lang) +	{ +		// This is BC stuff and not the best idea as it makes language fallback +		// implementation quite hard like below. +		if (strpos($this->phpbb_root_path . $component, $path) === 0) +		{ +			// Filter out the path +			$path_diff = str_replace($path, '', dirname($this->phpbb_root_path . $component)); +			$language_file = basename($component, '.' . $this->php_ext); +			$component = ''; + +			// This step is needed to resolve language/en/subdir style $component +			// $path already points to the language base directory so we need to eliminate +			// the first directory from the path (that should be the language directory) +			$path_diff_parts = explode('/', $path_diff); + +			if (sizeof($path_diff_parts) > 1) +			{ +				array_shift($path_diff_parts); +				$component = implode('/', $path_diff_parts) . '/'; +			} + +			$component .= $language_file; +		} + +		// Determine filename +		$filename = $component . '.' . $this->php_ext; + +		// Determine path to file +		$file_path = $this->get_language_file_path($path, $filename, $locale); + +		// Load language array +		$this->load_language_file($file_path, $lang); +	} + +	/** +	 * This function implements language fallback logic +	 * +	 * @param string	$path		Path to language directory +	 * @param string	$filename	Filename to load language strings from +	 * +	 * @return string	Relative path to language file +	 * +	 * @throws \phpbb\language\exception\language_file_not_exists	When the path to the file cannot be resolved +	 */ +	protected function get_language_file_path($path, $filename, $locales) +	{ +		// Language fallback logic +		foreach ($locales as $locale) +		{ +			$language_file_path = $path . $locale . '/' . $filename; + +			// If we are in install, try to use the updated version, when available +			if (defined('IN_INSTALL')) +			{ +				$install_language_path = str_replace('language/', 'install/update/new/language/', $language_file_path); +				if (file_exists($install_language_path)) +				{ +					return $install_language_path; +				} +			} + +			if (file_exists($language_file_path)) +			{ +				return $language_file_path; +			} +		} + +		// The language file is not exist +		throw new language_file_not_found('Language file ' . $language_file_path . ' couldn\'t be opened.'); +	} + +	/** +	 * Loads language file +	 * +	 * @param string	$path	Path to language file to load +	 * @param array		$lang	Reference of the array of language strings +	 */ +	protected function load_language_file($path, &$lang) +	{ +		// BC code for language files with help +		$help = array(); + +		// Do not suppress error if in DEBUG mode +		if (defined('DEBUG')) +		{ +			include $path; +		} +		else +		{ +			@include $path; +		} + +		if (!empty($help)) +		{ +			$lang['__help'] = array_merge($lang['__help'], $help); +		} +	} +} diff --git a/phpBB/phpbb/log/null.php b/phpBB/phpbb/log/dummy.php index baa78895ea..5c2d145e15 100644 --- a/phpBB/phpbb/log/null.php +++ b/phpBB/phpbb/log/dummy.php @@ -14,9 +14,9 @@  namespace phpbb\log;  /** -* Null logger +* Dummy logger  */ -class null implements log_interface +class dummy implements log_interface  {  	/**  	* {@inheritdoc} diff --git a/phpBB/phpbb/log/log.php b/phpBB/phpbb/log/log.php index 0c5205530b..4bb2e7a75a 100644 --- a/phpBB/phpbb/log/log.php +++ b/phpBB/phpbb/log/log.php @@ -27,7 +27,7 @@ class log implements \phpbb\log\log_interface  	/**  	* An array with the disabled log types. Logs of such types will not be -	* added when add_log() is called. +	* added when add() is called.  	* @var array  	*/  	protected $disabled_types; @@ -223,14 +223,14 @@ class log implements \phpbb\log\log_interface  			return false;  		} -		if ($log_time == false) +		if ($log_time === false)  		{  			$log_time = time();  		}  		$sql_ary = array( -			'user_id'		=> $user_id, -			'log_ip'		=> $log_ip, +			'user_id'		=> $user_id ? (int) $user_id : ANONYMOUS, +			'log_ip'		=> empty($log_ip) ? '' : $log_ip,  			'log_time'		=> $log_time,  			'log_operation'	=> $log_operation,  		); diff --git a/phpBB/phpbb/log/log_interface.php b/phpBB/phpbb/log/log_interface.php index 5932f722aa..86286e6f88 100644 --- a/phpBB/phpbb/log/log_interface.php +++ b/phpBB/phpbb/log/log_interface.php @@ -32,8 +32,8 @@ interface log_interface  	* Disable log  	*  	* This function allows disabling the log system or parts of it, for this -	* page call. When add_log is called and the type is disabled, -	* the log will not be added to the database. +	* page call. When add() is called and the type is disabled, the log will +	* not be added to the database.  	*  	* @param	mixed	$type	The log type we want to disable. Empty to  	*						disable all logs. Can also be an array of types. @@ -57,12 +57,12 @@ interface log_interface  	/**  	* Adds a log entry to the database  	* -	* @param	string	$mode				The mode defines which log_type is used and from which log the entry is retrieved -	* @param	int		$user_id			User ID of the user -	* @param	string	$log_ip				IP address of the user -	* @param	string	$log_operation		Name of the operation -	* @param	int		$log_time			Timestamp when the log entry was added, if empty time() will be used -	* @param	array	$additional_data	More arguments can be added, depending on the log_type +	* @param	string		$mode				The mode defines which log_type is used and from which log the entry is retrieved +	* @param	int			$user_id			User ID of the user +	* @param	string		$log_ip				IP address of the user +	* @param	string		$log_operation		Name of the operation +	* @param	int|bool	$log_time			Timestamp when the log entry was added. If false, time() will be used +	* @param	array		$additional_data	More arguments can be added, depending on the log_type  	*  	* @return	int|bool		Returns the log_id, if the entry was added to the database, false otherwise.  	*/ diff --git a/phpBB/phpbb/notification/exception.php b/phpBB/phpbb/notification/exception.php index 83c4526df7..e416438061 100644 --- a/phpBB/phpbb/notification/exception.php +++ b/phpBB/phpbb/notification/exception.php @@ -17,10 +17,6 @@ namespace phpbb\notification;  * Notifications exception  */ -class exception extends \Exception +class exception extends \phpbb\exception\runtime_exception  { -	public function __toString() -	{ -		return $this->getMessage(); -	}  } diff --git a/phpBB/phpbb/notification/manager.php b/phpBB/phpbb/notification/manager.php index db92170dd8..38d7a13165 100644 --- a/phpBB/phpbb/notification/manager.php +++ b/phpBB/phpbb/notification/manager.php @@ -943,7 +943,7 @@ class manager  		{  			if (!isset($this->notification_types[$notification_type_name]) && !isset($this->notification_types['notification.type.' . $notification_type_name]))  			{ -				throw new \phpbb\notification\exception($this->user->lang('NOTIFICATION_TYPE_NOT_EXIST', $notification_type_name)); +				throw new \phpbb\notification\exception('NOTIFICATION_TYPE_NOT_EXIST', array($notification_type_name));  			}  			$sql = 'INSERT INTO ' . $this->notification_types_table . ' ' . $this->db->sql_build_array('INSERT', array( diff --git a/phpBB/phpbb/notification/type/admin_activate_user.php b/phpBB/phpbb/notification/type/admin_activate_user.php index dfc0157558..73ed612480 100644 --- a/phpBB/phpbb/notification/type/admin_activate_user.php +++ b/phpBB/phpbb/notification/type/admin_activate_user.php @@ -36,7 +36,7 @@ class admin_activate_user extends \phpbb\notification\type\base  	/**  	* {@inheritdoc}  	*/ -	public static $notification_option = array( +	static public $notification_option = array(  		'lang'	=> 'NOTIFICATION_TYPE_ADMIN_ACTIVATE_USER',  		'group'	=> 'NOTIFICATION_GROUP_ADMINISTRATION',  	); @@ -52,7 +52,7 @@ class admin_activate_user extends \phpbb\notification\type\base  	/**  	* {@inheritdoc}  	*/ -	public static function get_item_id($user) +	static public function get_item_id($user)  	{  		return (int) $user['user_id'];  	} @@ -60,7 +60,7 @@ class admin_activate_user extends \phpbb\notification\type\base  	/**  	* {@inheritdoc}  	*/ -	public static function get_item_parent_id($post) +	static public function get_item_parent_id($post)  	{  		return 0;  	} diff --git a/phpBB/phpbb/notification/type/approve_post.php b/phpBB/phpbb/notification/type/approve_post.php index a9e635b41a..e6c38b2ede 100644 --- a/phpBB/phpbb/notification/type/approve_post.php +++ b/phpBB/phpbb/notification/type/approve_post.php @@ -50,7 +50,7 @@ class approve_post extends \phpbb\notification\type\post  	* @var bool|array False if the service should use it's default data  	* 					Array of data (including keys 'id', 'lang', and 'group')  	*/ -	public static $notification_option = array( +	static public $notification_option = array(  		'id'	=> 'moderation_queue',  		'lang'	=> 'NOTIFICATION_TYPE_MODERATION_QUEUE',  		'group'	=> 'NOTIFICATION_GROUP_POSTING', diff --git a/phpBB/phpbb/notification/type/approve_topic.php b/phpBB/phpbb/notification/type/approve_topic.php index 2f4678359c..5f5b96f335 100644 --- a/phpBB/phpbb/notification/type/approve_topic.php +++ b/phpBB/phpbb/notification/type/approve_topic.php @@ -50,7 +50,7 @@ class approve_topic extends \phpbb\notification\type\topic  	* @var bool|array False if the service should use it's default data  	* 					Array of data (including keys 'id', 'lang', and 'group')  	*/ -	public static $notification_option = array( +	static public $notification_option = array(  		'id'	=> 'moderation_queue',  		'lang'	=> 'NOTIFICATION_TYPE_MODERATION_QUEUE',  		'group'	=> 'NOTIFICATION_GROUP_POSTING', diff --git a/phpBB/phpbb/notification/type/base.php b/phpBB/phpbb/notification/type/base.php index 4ead06071e..1cf0498138 100644 --- a/phpBB/phpbb/notification/type/base.php +++ b/phpBB/phpbb/notification/type/base.php @@ -63,7 +63,7 @@ abstract class base implements \phpbb\notification\type\type_interface  	* @var bool|array False if the service should use its default data  	* 					Array of data (including keys 'id', 'lang', and 'group')  	*/ -	public static $notification_option = false; +	static public $notification_option = false;  	/**  	* The notification_type_id, set upon creation of the class diff --git a/phpBB/phpbb/notification/type/bookmark.php b/phpBB/phpbb/notification/type/bookmark.php index 4f2d34cb60..e6a695e875 100644 --- a/phpBB/phpbb/notification/type/bookmark.php +++ b/phpBB/phpbb/notification/type/bookmark.php @@ -43,7 +43,7 @@ class bookmark extends \phpbb\notification\type\post  	* @var bool|array False if the service should use it's default data  	* 					Array of data (including keys 'id', 'lang', and 'group')  	*/ -	public static $notification_option = array( +	static public $notification_option = array(  		'lang'	=> 'NOTIFICATION_TYPE_BOOKMARK',  		'group'	=> 'NOTIFICATION_GROUP_POSTING',  	); diff --git a/phpBB/phpbb/notification/type/disapprove_post.php b/phpBB/phpbb/notification/type/disapprove_post.php index 6c7bcbcaee..7021cdc837 100644 --- a/phpBB/phpbb/notification/type/disapprove_post.php +++ b/phpBB/phpbb/notification/type/disapprove_post.php @@ -60,7 +60,7 @@ class disapprove_post extends \phpbb\notification\type\approve_post  	* @var bool|array False if the service should use it's default data  	* 					Array of data (including keys 'id', 'lang', and 'group')  	*/ -	public static $notification_option = array( +	static public $notification_option = array(  		'id'	=> 'moderation_queue',  		'lang'	=> 'NOTIFICATION_TYPE_MODERATION_QUEUE',  		'group'	=> 'NOTIFICATION_GROUP_POSTING', diff --git a/phpBB/phpbb/notification/type/disapprove_topic.php b/phpBB/phpbb/notification/type/disapprove_topic.php index efa5eb7ecd..419cc5b2a6 100644 --- a/phpBB/phpbb/notification/type/disapprove_topic.php +++ b/phpBB/phpbb/notification/type/disapprove_topic.php @@ -60,7 +60,7 @@ class disapprove_topic extends \phpbb\notification\type\approve_topic  	* @var bool|array False if the service should use it's default data  	* 					Array of data (including keys 'id', 'lang', and 'group')  	*/ -	public static $notification_option = array( +	static public $notification_option = array(  		'id'	=> 'moderation_queue',  		'lang'	=> 'NOTIFICATION_TYPE_MODERATION_QUEUE',  		'group'	=> 'NOTIFICATION_GROUP_POSTING', diff --git a/phpBB/phpbb/notification/type/group_request.php b/phpBB/phpbb/notification/type/group_request.php index 4baf516fed..19665624df 100644 --- a/phpBB/phpbb/notification/type/group_request.php +++ b/phpBB/phpbb/notification/type/group_request.php @@ -26,7 +26,7 @@ class group_request extends \phpbb\notification\type\base  	/**  	* {@inheritdoc}  	*/ -	public static $notification_option = array( +	static public $notification_option = array(  		'lang'	=> 'NOTIFICATION_TYPE_GROUP_REQUEST',  	); @@ -50,7 +50,7 @@ class group_request extends \phpbb\notification\type\base  	/**  	* {@inheritdoc}  	*/ -	public static function get_item_id($group) +	static public function get_item_id($group)  	{  		return (int) $group['user_id'];  	} @@ -58,7 +58,7 @@ class group_request extends \phpbb\notification\type\base  	/**  	* {@inheritdoc}  	*/ -	public static function get_item_parent_id($group) +	static public function get_item_parent_id($group)  	{  		// Group id is the parent  		return (int) $group['group_id']; diff --git a/phpBB/phpbb/notification/type/group_request_approved.php b/phpBB/phpbb/notification/type/group_request_approved.php index d284046ffa..f282cdd158 100644 --- a/phpBB/phpbb/notification/type/group_request_approved.php +++ b/phpBB/phpbb/notification/type/group_request_approved.php @@ -34,7 +34,7 @@ class group_request_approved extends \phpbb\notification\type\base  	/**  	* {@inheritdoc}  	*/ -	public static function get_item_id($group) +	static public function get_item_id($group)  	{  		return (int) $group['group_id'];  	} @@ -42,7 +42,7 @@ class group_request_approved extends \phpbb\notification\type\base  	/**  	* {@inheritdoc}  	*/ -	public static function get_item_parent_id($group) +	static public function get_item_parent_id($group)  	{  		return 0;  	} diff --git a/phpBB/phpbb/notification/type/pm.php b/phpBB/phpbb/notification/type/pm.php index 330a70c85a..29b4b79216 100644 --- a/phpBB/phpbb/notification/type/pm.php +++ b/phpBB/phpbb/notification/type/pm.php @@ -36,7 +36,7 @@ class pm extends \phpbb\notification\type\base  	* @var bool|array False if the service should use it's default data  	* 					Array of data (including keys 'id', 'lang', and 'group')  	*/ -	public static $notification_option = array( +	static public $notification_option = array(  		'lang'	=> 'NOTIFICATION_TYPE_PM',  	); @@ -53,7 +53,7 @@ class pm extends \phpbb\notification\type\base  	*  	* @param array $pm The data from the private message  	*/ -	public static function get_item_id($pm) +	static public function get_item_id($pm)  	{  		return (int) $pm['msg_id'];  	} @@ -63,7 +63,7 @@ class pm extends \phpbb\notification\type\base  	*  	* @param array $pm The data from the pm  	*/ -	public static function get_item_parent_id($pm) +	static public function get_item_parent_id($pm)  	{  		// No parent  		return 0; diff --git a/phpBB/phpbb/notification/type/post.php b/phpBB/phpbb/notification/type/post.php index 421eff6372..d6aa8a8af9 100644 --- a/phpBB/phpbb/notification/type/post.php +++ b/phpBB/phpbb/notification/type/post.php @@ -50,7 +50,7 @@ class post extends \phpbb\notification\type\base  	* @var bool|array False if the service should use it's default data  	* 					Array of data (including keys 'id', 'lang', and 'group')  	*/ -	public static $notification_option = array( +	static public $notification_option = array(  		'lang'	=> 'NOTIFICATION_TYPE_POST',  		'group'	=> 'NOTIFICATION_GROUP_POSTING',  	); @@ -68,7 +68,7 @@ class post extends \phpbb\notification\type\base  	*  	* @param array $post The data from the post  	*/ -	public static function get_item_id($post) +	static public function get_item_id($post)  	{  		return (int) $post['post_id'];  	} @@ -78,7 +78,7 @@ class post extends \phpbb\notification\type\base  	*  	* @param array $post The data from the post  	*/ -	public static function get_item_parent_id($post) +	static public function get_item_parent_id($post)  	{  		return (int) $post['topic_id'];  	} diff --git a/phpBB/phpbb/notification/type/post_in_queue.php b/phpBB/phpbb/notification/type/post_in_queue.php index 315b8b0243..e500ad33bc 100644 --- a/phpBB/phpbb/notification/type/post_in_queue.php +++ b/phpBB/phpbb/notification/type/post_in_queue.php @@ -43,7 +43,7 @@ class post_in_queue extends \phpbb\notification\type\post  	* @var bool|array False if the service should use it's default data  	* 					Array of data (including keys 'id', 'lang', and 'group')  	*/ -	public static $notification_option = array( +	static public $notification_option = array(  		'id'	=> 'notification.type.needs_approval',  		'lang'	=> 'NOTIFICATION_TYPE_IN_MODERATION_QUEUE',  		'group'	=> 'NOTIFICATION_GROUP_MODERATION', diff --git a/phpBB/phpbb/notification/type/quote.php b/phpBB/phpbb/notification/type/quote.php index 508ca92fa0..141f90c7ae 100644 --- a/phpBB/phpbb/notification/type/quote.php +++ b/phpBB/phpbb/notification/type/quote.php @@ -50,7 +50,7 @@ class quote extends \phpbb\notification\type\post  	* @var bool|array False if the service should use it's default data  	* 					Array of data (including keys 'id', 'lang', and 'group')  	*/ -	public static $notification_option = array( +	static public $notification_option = array(  		'lang'	=> 'NOTIFICATION_TYPE_QUOTE',  		'group'	=> 'NOTIFICATION_GROUP_POSTING',  	); diff --git a/phpBB/phpbb/notification/type/report_pm.php b/phpBB/phpbb/notification/type/report_pm.php index d39143f4b7..1904680d5a 100644 --- a/phpBB/phpbb/notification/type/report_pm.php +++ b/phpBB/phpbb/notification/type/report_pm.php @@ -60,7 +60,7 @@ class report_pm extends \phpbb\notification\type\pm  	* @var bool|array False if the service should use it's default data  	* 					Array of data (including keys 'id', 'lang', and 'group')  	*/ -	public static $notification_option = array( +	static public $notification_option = array(  		'id'	=> 'notification.type.report',  		'lang'	=> 'NOTIFICATION_TYPE_REPORT',  		'group'	=> 'NOTIFICATION_GROUP_MODERATION', @@ -71,7 +71,7 @@ class report_pm extends \phpbb\notification\type\pm  	*  	* @param array $pm The data from the pm  	*/ -	public static function get_item_parent_id($pm) +	static public function get_item_parent_id($pm)  	{  		return (int) $pm['report_id'];  	} diff --git a/phpBB/phpbb/notification/type/report_post.php b/phpBB/phpbb/notification/type/report_post.php index 027cca716b..b64862078a 100644 --- a/phpBB/phpbb/notification/type/report_post.php +++ b/phpBB/phpbb/notification/type/report_post.php @@ -66,7 +66,7 @@ class report_post extends \phpbb\notification\type\post_in_queue  	* @var bool|array False if the service should use it's default data  	* 					Array of data (including keys 'id' and 'lang')  	*/ -	public static $notification_option = array( +	static public $notification_option = array(  		'id'	=> 'notification.type.report',  		'lang'	=> 'NOTIFICATION_TYPE_REPORT',  		'group'	=> 'NOTIFICATION_GROUP_MODERATION', diff --git a/phpBB/phpbb/notification/type/topic.php b/phpBB/phpbb/notification/type/topic.php index 5f57087b73..a1a17535b5 100644 --- a/phpBB/phpbb/notification/type/topic.php +++ b/phpBB/phpbb/notification/type/topic.php @@ -50,7 +50,7 @@ class topic extends \phpbb\notification\type\base  	* @var bool|array False if the service should use it's default data  	* 					Array of data (including keys 'id', 'lang', and 'group')  	*/ -	public static $notification_option = array( +	static public $notification_option = array(  		'lang'	=> 'NOTIFICATION_TYPE_TOPIC',  		'group'	=> 'NOTIFICATION_GROUP_POSTING',  	); @@ -68,7 +68,7 @@ class topic extends \phpbb\notification\type\base  	*  	* @param array $post The data from the post  	*/ -	public static function get_item_id($post) +	static public function get_item_id($post)  	{  		return (int) $post['topic_id'];  	} @@ -78,7 +78,7 @@ class topic extends \phpbb\notification\type\base  	*  	* @param array $post The data from the post  	*/ -	public static function get_item_parent_id($post) +	static public function get_item_parent_id($post)  	{  		return (int) $post['forum_id'];  	} diff --git a/phpBB/phpbb/notification/type/topic_in_queue.php b/phpBB/phpbb/notification/type/topic_in_queue.php index 4c60c6b858..cfdf748d38 100644 --- a/phpBB/phpbb/notification/type/topic_in_queue.php +++ b/phpBB/phpbb/notification/type/topic_in_queue.php @@ -43,7 +43,7 @@ class topic_in_queue extends \phpbb\notification\type\topic  	* @var bool|array False if the service should use it's default data  	* 					Array of data (including keys 'id', 'lang', and 'group')  	*/ -	public static $notification_option = array( +	static public $notification_option = array(  		'id'	=> 'notification.type.needs_approval',  		'lang'	=> 'NOTIFICATION_TYPE_IN_MODERATION_QUEUE',  		'group'	=> 'NOTIFICATION_GROUP_MODERATION', diff --git a/phpBB/phpbb/notification/type/type_interface.php b/phpBB/phpbb/notification/type/type_interface.php index 5c5a110836..8844ce1a38 100644 --- a/phpBB/phpbb/notification/type/type_interface.php +++ b/phpBB/phpbb/notification/type/type_interface.php @@ -37,14 +37,14 @@ interface type_interface  	*  	* @param array $type_data The type specific data  	*/ -	public static function get_item_id($type_data); +	static public function get_item_id($type_data);  	/**  	* Get the id of the parent  	*  	* @param array $type_data The type specific data  	*/ -	public static function get_item_parent_id($type_data); +	static public function get_item_parent_id($type_data);  	/**  	* Is this type available to the current user (defines whether or not it will be shown in the UCP Edit notification options) diff --git a/phpBB/phpbb/path_helper.php b/phpBB/phpbb/path_helper.php index 5400c1c5a6..7b0d6f0fba 100644 --- a/phpBB/phpbb/path_helper.php +++ b/phpBB/phpbb/path_helper.php @@ -21,7 +21,7 @@ class path_helper  	/** @var \phpbb\symfony_request */  	protected $symfony_request; -	/** @var \phpbb\filesystem */ +	/** @var \phpbb\filesystem\filesystem_interface */  	protected $filesystem;  	/** @var \phpbb\request\request_interface */ @@ -43,13 +43,13 @@ class path_helper  	* Constructor  	*  	* @param \phpbb\symfony_request $symfony_request -	* @param \phpbb\filesystem $filesystem +	* @param \phpbb\filesystem\filesystem_interface $filesystem  	* @param \phpbb\request\request_interface $request  	* @param string $phpbb_root_path Relative path to phpBB root  	* @param string $php_ext PHP file extension  	* @param mixed $adm_relative_path Relative path admin path to adm/ root  	*/ -	public function __construct(\phpbb\symfony_request $symfony_request, \phpbb\filesystem $filesystem, \phpbb\request\request_interface $request, $phpbb_root_path, $php_ext, $adm_relative_path = null) +	public function __construct(\phpbb\symfony_request $symfony_request, \phpbb\filesystem\filesystem_interface $filesystem, \phpbb\request\request_interface $request, $phpbb_root_path, $php_ext, $adm_relative_path = null)  	{  		$this->symfony_request = $symfony_request;  		$this->filesystem = $filesystem; diff --git a/phpBB/phpbb/profilefields/type/type_date.php b/phpBB/phpbb/profilefields/type/type_date.php index 90ac9a6703..414484920b 100644 --- a/phpBB/phpbb/profilefields/type/type_date.php +++ b/phpBB/phpbb/profilefields/type/type_date.php @@ -72,7 +72,7 @@ class type_date extends type_base  			'lang_options'			=> $field_data['lang_options'],  		); -		$always_now = request_var('always_now', -1); +		$always_now = $request->variable('always_now', -1);  		if ($always_now == -1)  		{  			$s_checked = ($field_data['field_default_value'] == 'now') ? true : false; diff --git a/phpBB/phpbb/report/controller/report.php b/phpBB/phpbb/report/controller/report.php new file mode 100644 index 0000000000..f703d1cc60 --- /dev/null +++ b/phpBB/phpbb/report/controller/report.php @@ -0,0 +1,319 @@ +<?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\report\controller; + +use phpbb\exception\http_exception; +use Symfony\Component\HttpFoundation\RedirectResponse; + +class report +{ +	/** +	 * @var \phpbb\config\db +	 */ +	protected $config; + +	/** +	 * @var \phpbb\user +	 */ +	protected $user; + +	/** +	 * @var \phpbb\template\template +	 */ +	protected $template; + +	/** +	 * @var \phpbb\controller\helper +	 */ +	protected $helper; + +	/** +	 * @var \phpbb\request\request_interface +	 */ +	protected $request; + +	/** +	 * @var \phpbb\captcha\factory +	 */ +	protected $captcha_factory; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * @var string +	 */ +	protected $php_ext; + +	/** +	 * @var \phpbb\report\report_handler_interface +	 */ +	protected $report_handler; + +	/** +	 * @var \phpbb\report\report_reason_list_provider +	 */ +	protected $report_reason_provider; + +	public function __construct(\phpbb\config\db $config, \phpbb\user $user, \phpbb\template\template $template, \phpbb\controller\helper $helper, \phpbb\request\request_interface $request, \phpbb\captcha\factory $captcha_factory, \phpbb\report\handler_factory $report_factory, \phpbb\report\report_reason_list_provider $ui_provider, $phpbb_root_path, $php_ext) +	{ +		$this->config			= $config; +		$this->user				= $user; +		$this->template			= $template; +		$this->helper			= $helper; +		$this->request			= $request; +		$this->phpbb_root_path	= $phpbb_root_path; +		$this->php_ext			= $php_ext; +		$this->captcha_factory	= $captcha_factory; +		$this->report_handler	= $report_factory; + +		// User interface factory +		$this->report_reason_provider = $ui_provider; +	} + +	/** +	 * Controller for /path_to_entities/{id}/report routes +	 * +	 * Because of how phpBB organizes routes $mode must be set in the route config. +	 * +	 * @param int		$id		ID of the entity to report +	 * @param string	$mode +	 * @return \Symfony\Component\HttpFoundation\Response a Symfony response object +	 * @throws \phpbb\exception\http_exception when $mode or $id is invalid for some reason +	 */ +	public function handle($id, $mode) +	{ +		// Get report handler +		$this->report_handler = $this->report_handler->get_instance($mode); + +		$this->user->add_lang('mcp'); + +		$user_notify	= ($this->user->data['is_registered']) ? $this->request->variable('notify', 0) : false; +		$reason_id		= $this->request->variable('reason_id', 0); +		$report_text	= $this->request->variable('report_text', '', true); + +		$submit = $this->request->variable('submit', ''); +		$cancel = $this->request->variable('cancel', ''); + +		$error = array(); +		$s_hidden_fields = ''; + +		$redirect_url = append_sid( +			$this->phpbb_root_path . ( ($mode === 'pm') ? 'ucp' : 'viewtopic' ) . ".{$this->php_ext}", +			($mode == 'pm') ? "i=pm&mode=view&p=$id" : "p=$id" +		); +		$redirect_url .= ($mode === 'post') ? "#p$id" : ''; + +		// Set up CAPTCHA if necessary +		if ($this->config['enable_post_confirm'] && !$this->user->data['is_registered']) +		{ +			$captcha = $this->captcha_factory->get_instance($this->config['captcha_plugin']); +			$captcha->init(CONFIRM_REPORT); +		} + +		//Has the report been cancelled? +		if (!empty($cancel)) +		{ +			return new RedirectResponse($redirect_url, 302); +		} + +		// Check CAPTCHA, if the form was submited +		if (!empty($submit) && isset($captcha)) +		{ +			$captcha_template_array = $this->check_captcha($captcha); +			$error = $captcha_template_array['error']; +			$s_hidden_fields = $captcha_template_array['hidden_fields']; +		} + +		// Handle request +		try +		{ +			if (!empty($submit) && sizeof($error) === 0) +			{ +				$this->report_handler->add_report( +					(int) $id, +					(int) $reason_id, +					(string) $report_text, +					(int) $user_notify +				); + +				// Send success message +				switch ($mode) +				{ +					case 'pm': +						$lang_return = $this->user->lang['RETURN_PM']; +						$lang_success = $this->user->lang['PM_REPORTED_SUCCESS']; +					break; +					case 'post': +						$lang_return = $this->user->lang['RETURN_TOPIC']; +						$lang_success = $this->user->lang['POST_REPORTED_SUCCESS']; +					break; +				} + +				$this->helper->assign_meta_refresh_var(3, $redirect_url); +				$message = $lang_success . '<br /><br />' . sprintf($lang_return, '<a href="' . $redirect_url . '">', '</a>'); +				return $this->helper->message($message); +			} +			else +			{ +				$this->report_handler->validate_report_request($id); +			} +		} +		catch (\phpbb\report\exception\pm_reporting_disabled_exception $exception) +		{ +			throw new http_exception(404, 'PAGE_NOT_FOUND'); +		} +		catch (\phpbb\report\exception\already_reported_exception $exception) +		{ +			switch ($mode) +			{ +				case 'pm': +					$message = $this->user->lang['ALREADY_REPORTED_PM']; +					$message .= '<br /><br />' . sprintf($this->user->lang['RETURN_PM'], '<a href="' . $redirect_url . '">', '</a>'); +				break; +				case 'post': +					$message = $this->user->lang['ALREADY_REPORTED']; +					$message .= '<br /><br />' . sprintf($this->user->lang['RETURN_TOPIC'], '<a href="' . $redirect_url . '">', '</a>'); +				break; +			} + +			return $this->helper->message($message); +		} +		catch (\phpbb\report\exception\report_permission_denied_exception $exception) +		{ +			$message = $exception->getMessage(); +			if (isset($this->user->lang[$message])) +			{ +				$message = $this->user->lang[$message]; +			} + +			throw new http_exception(403, $message); +		} +		catch (\phpbb\report\exception\entity_not_found_exception $exception) +		{ +			$message = $exception->getMessage(); +			if (isset($this->user->lang[$message])) +			{ +				$message = $this->user->lang[$message]; +			} + +			throw new http_exception(404, $message); +		} +		catch (\phpbb\report\exception\empty_report_exception $exception) +		{ +			$error[] = $this->user->lang['EMPTY_REPORT']; +		} +		catch (\phpbb\report\exception\invalid_report_exception $exception) +		{ +			return $this->helper->message($exception->getMessage()); +		} + +		// Setting up an rendering template +		$page_title = ($mode === 'pm') ? $this->user->lang['REPORT_MESSAGE'] : $this->user->lang['REPORT_POST']; +		$this->assign_template_data( +			$mode, +			$id, +			$reason_id, +			$report_text, +			$user_notify, +			$error, +			$s_hidden_fields, +			( isset($captcha) ? $captcha : false ) +		); + +		return $this->helper->render('report_body.html', $page_title); +	} + +	/** +	 * Assigns template variables +	 * +	 * @param	int		$mode +	 * @param	int		$id +	 * @param	int		$reason_id +	 * @param	string	$report_text +	 * @param	mixed	$user_notify +	 * @param 	array	$error +	 * @param	string	$s_hidden_fields +	 * @param	mixed	$captcha +	 * @return	null +	 */ +	protected function assign_template_data($mode, $id, $reason_id, $report_text, $user_notify, $error = array(), $s_hidden_fields = '', $captcha = false) +	{ +		if ($captcha !== false && $captcha->is_solved() === false) +		{ +			$this->template->assign_vars(array( +				'S_CONFIRM_CODE'	=> true, +				'CAPTCHA_TEMPLATE'	=> $captcha->get_template(), +			)); +		} + +		$this->report_reason_provider->display_reasons($reason_id); + +		switch ($mode) +		{ +			case 'pm': +				$report_route = $this->helper->route('phpbb_report_pm_controller', array('id' => $id)); +			break; +			case 'post': +				$report_route = $this->helper->route('phpbb_report_post_controller', array('id' => $id)); +			break; +		} + +		$this->template->assign_vars(array( +			'ERROR'				=> (sizeof($error) > 0) ? implode('<br />', $error) : '', +			'S_REPORT_POST'		=> ($mode === 'pm') ? false : true, +			'REPORT_TEXT'		=> $report_text, +			'S_HIDDEN_FIELDS'	=> (!empty($s_hidden_fields)) ? $s_hidden_fields : null, +			'S_REPORT_ACTION'	=> $report_route, + +			'S_NOTIFY'			=> $user_notify, +			'S_CAN_NOTIFY'		=> ($this->user->data['is_registered']) ? true : false, +			'S_IN_REPORT'		=> true, +		)); +	} + +	/** +	 * Check CAPTCHA +	 * +	 * @param	object	$captcha	A phpBB CAPTCHA object +	 * @return	array	template variables which ensures that CAPTCHA's work correctly +	 */ +	protected function check_captcha($captcha) +	{ +		$error = array(); +		$captcha_hidden_fields = ''; + +		$visual_confirmation_response = $captcha->validate(); +		if ($visual_confirmation_response) +		{ +			$error[] = $visual_confirmation_response; +		} + +		if (sizeof($error) === 0) +		{ +			$captcha->reset(); +		} +		else if ($captcha->is_solved() !== false) +		{ +			$captcha_hidden_fields = build_hidden_fields($captcha->get_hidden_fields()); +		} + +		return array( +			'error' => $error, +			'hidden_fields' => $captcha_hidden_fields, +		); +	} +} diff --git a/phpBB/phpbb/report/exception/already_reported_exception.php b/phpBB/phpbb/report/exception/already_reported_exception.php new file mode 100644 index 0000000000..54174044fe --- /dev/null +++ b/phpBB/phpbb/report/exception/already_reported_exception.php @@ -0,0 +1,19 @@ +<?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\report\exception; + +class already_reported_exception extends invalid_report_exception +{ + +} diff --git a/phpBB/phpbb/report/exception/empty_report_exception.php b/phpBB/phpbb/report/exception/empty_report_exception.php new file mode 100644 index 0000000000..8c968dca80 --- /dev/null +++ b/phpBB/phpbb/report/exception/empty_report_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\report\exception; + +class empty_report_exception extends invalid_report_exception +{ +	public function __construct() +	{ +		parent::__construct('EMPTY_REPORT'); +	} +} diff --git a/phpBB/phpbb/report/exception/entity_not_found_exception.php b/phpBB/phpbb/report/exception/entity_not_found_exception.php new file mode 100644 index 0000000000..732aa58a13 --- /dev/null +++ b/phpBB/phpbb/report/exception/entity_not_found_exception.php @@ -0,0 +1,19 @@ +<?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\report\exception; + +class entity_not_found_exception extends invalid_report_exception +{ + +} diff --git a/phpBB/phpbb/report/exception/factory_invalid_argument_exception.php b/phpBB/phpbb/report/exception/factory_invalid_argument_exception.php new file mode 100644 index 0000000000..19de91eea3 --- /dev/null +++ b/phpBB/phpbb/report/exception/factory_invalid_argument_exception.php @@ -0,0 +1,21 @@ +<?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\report\exception; + +use \phpbb\exception\runtime_exception; + +class factory_invalid_argument_exception extends runtime_exception +{ + +} diff --git a/phpBB/phpbb/report/exception/invalid_report_exception.php b/phpBB/phpbb/report/exception/invalid_report_exception.php new file mode 100644 index 0000000000..03ff0a872d --- /dev/null +++ b/phpBB/phpbb/report/exception/invalid_report_exception.php @@ -0,0 +1,21 @@ +<?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\report\exception; + +use \phpbb\exception\runtime_exception; + +class invalid_report_exception extends runtime_exception +{ + +} diff --git a/phpBB/phpbb/report/exception/pm_reporting_disabled_exception.php b/phpBB/phpbb/report/exception/pm_reporting_disabled_exception.php new file mode 100644 index 0000000000..2c8ab8cf84 --- /dev/null +++ b/phpBB/phpbb/report/exception/pm_reporting_disabled_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\report\exception; + +class pm_reporting_disabled_exception extends invalid_report_exception +{ +	public function __construct() +	{ + +	} +} diff --git a/phpBB/phpbb/report/exception/report_permission_denied_exception.php b/phpBB/phpbb/report/exception/report_permission_denied_exception.php new file mode 100644 index 0000000000..c7069288b8 --- /dev/null +++ b/phpBB/phpbb/report/exception/report_permission_denied_exception.php @@ -0,0 +1,19 @@ +<?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\report\exception; + +class report_permission_denied_exception extends invalid_report_exception +{ + +} diff --git a/phpBB/phpbb/report/handler_factory.php b/phpBB/phpbb/report/handler_factory.php new file mode 100644 index 0000000000..ec229aac54 --- /dev/null +++ b/phpBB/phpbb/report/handler_factory.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\report; + +use phpbb\report\exception\factory_invalid_argument_exception; + +class handler_factory +{ +	/** +	 * @var \Symfony\Component\DependencyInjection\ContainerInterface +	 */ +	protected $container; + +	/** +	 * Constructor +	 * +	 * @param \Symfony\Component\DependencyInjection\ContainerInterface $container +	 */ +	public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container) +	{ +		$this->container = $container; +	} + +	/** +	 * Return a new instance of an appropriate report handler +	 * +	 * @param string	$type +	 * @return \phpbb\report\report_handler_interface +	 * @throws \phpbb\report\exception\factory_invalid_argument_exception if $type is not valid +	 */ +	public function get_instance($type) +	{ +		switch ($type) +		{ +			case 'pm': +				return $this->container->get('phpbb.report.handlers.report_handler_pm'); +			break; +			case 'post': +				return $this->container->get('phpbb.report.handlers.report_handler_post'); +			break; +		} + +		throw new factory_invalid_argument_exception(); +	} +} diff --git a/phpBB/phpbb/report/report_handler.php b/phpBB/phpbb/report/report_handler.php new file mode 100644 index 0000000000..126a206dbf --- /dev/null +++ b/phpBB/phpbb/report/report_handler.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\report; + +abstract class report_handler implements report_handler_interface +{ +	/** +	 * @var \phpbb\db\driver\driver_interface +	 */ +	protected $db; + +	/** +	 * @var \phpbb\event\dispatcher_interface +	 */ +	protected $dispatcher; + +	/** +	 * @var \phpbb\config\db +	 */ +	protected $config; + +	/** +	 * @var \phpbb\auth\auth +	 */ +	protected $auth; + +	/** +	 * @var \phpbb\user +	 */ +	protected $user; + +	/** +	 * @var \phpbb\notification\manager +	 */ +	protected $notifications; + +	/** +	 * @var array +	 */ +	protected $report_data; + +	/** +	 * Construtor +	 * +	 * @param \phpbb\db\driver\driver_interface	$db +	 * @param \phpbb\event\dispatcher_interface	$dispatcher +	 * @param \phpbb\config\db					$config +	 * @param \phpbb\auth\auth					$auth +	 * @param \phpbb\user						$user +	 * @param \phpbb\notification\manager		$notification +	 */ +	public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\event\dispatcher_interface $dispatcher, \phpbb\config\db $config, \phpbb\auth\auth $auth, \phpbb\user $user, \phpbb\notification\manager $notification) +	{ +		$this->db				= $db; +		$this->dispatcher		= $dispatcher; +		$this->config			= $config; +		$this->auth				= $auth; +		$this->user				= $user; +		$this->notifications	= $notification; +		$this->report_data		= array(); +	} + +	/** +	 * Creates a report entity in the database +	 * +	 * @param	array	$report_data +	 * @return	int	the ID of the created entity +	 */ +	protected function create_report(array $report_data) +	{ +		$sql_ary = array( +			'reason_id'							=> (int) $report_data['reason_id'], +			'post_id'							=> $report_data['post_id'], +			'pm_id'								=> $report_data['pm_id'], +			'user_id'							=> (int) $this->user->data['user_id'], +			'user_notify'						=> (int) $report_data['user_notify'], +			'report_closed'						=> 0, +			'report_time'						=> (int) time(), +			'report_text'						=> (string) $report_data['report_text'], +			'reported_post_text'				=> $report_data['reported_post_text'], +			'reported_post_uid'					=> $report_data['reported_post_uid'], +			'reported_post_bitfield'			=> $report_data['reported_post_bitfield'], +			'reported_post_enable_bbcode'		=> $report_data['reported_post_enable_bbcode'], +			'reported_post_enable_smilies'		=> $report_data['reported_post_enable_smilies'], +			'reported_post_enable_magic_url'	=> $report_data['reported_post_enable_magic_url'], +		); + +		$sql = 'INSERT INTO ' . REPORTS_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); +		$this->db->sql_query($sql); + +		return $this->db->sql_nextid(); +	} +} diff --git a/phpBB/phpbb/report/report_handler_interface.php b/phpBB/phpbb/report/report_handler_interface.php new file mode 100644 index 0000000000..8dafc392d0 --- /dev/null +++ b/phpBB/phpbb/report/report_handler_interface.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\report; + +interface report_handler_interface +{ +	/** +	 * Reports a message +	 * +	 * @param int		$id +	 * @param int		$reason_id +	 * @param string	$report_text +	 * @param int		$user_notify +	 * @return null +	 * @throws \phpbb\report\exception\empty_report_exception		when the given report is empty +	 * @throws \phpbb\report\exception\already_reported_exception	when the entity is already reported +	 * @throws \phpbb\report\exception\entity_not_found_exception	when the entity does not exist or the user does not have viewing permissions for it +	 * @throws \phpbb\report\exception\invalid_report_exception		when the entity cannot be reported for some other reason +	 */ +	public function add_report($id, $reason_id, $report_text, $user_notify); + +	/** +	 * Checks if the message is reportable +	 * +	 * @param int	$id +	 * @return null +	 * @throws \phpbb\report\exception\already_reported_exception	when the entity is already reported +	 * @throws \phpbb\report\exception\entity_not_found_exception	when the entity does not exist or the user does not have viewing permissions for it +	 * @throws \phpbb\report\exception\invalid_report_exception		when the entity cannot be reported for some other reason +	 */ +	public function validate_report_request($id); +} diff --git a/phpBB/phpbb/report/report_handler_pm.php b/phpBB/phpbb/report/report_handler_pm.php new file mode 100644 index 0000000000..2f2a697efc --- /dev/null +++ b/phpBB/phpbb/report/report_handler_pm.php @@ -0,0 +1,137 @@ +<?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\report; + +use phpbb\report\exception\empty_report_exception; +use phpbb\report\exception\already_reported_exception; +use phpbb\report\exception\pm_reporting_disabled_exception; +use phpbb\report\exception\entity_not_found_exception; + +class report_handler_pm extends report_handler +{ +	/** +	 * {@inheritdoc} +	 * @throws \phpbb\report\exception\pm_reporting_disabled_exception when PM reporting is disabled on the board +	 */ +	public function add_report($id, $reason_id, $report_text, $user_notify) +	{ +		// Cast the input variables +		$id				= (int) $id; +		$reason_id		= (int) $reason_id; +		$report_text	= (string) $report_text; +		$user_notify	= (int) $user_notify; + +		$this->validate_report_request($id); + +		$sql = 'SELECT * +			FROM ' . REPORTS_REASONS_TABLE . " +			WHERE reason_id = $reason_id"; +		$result = $this->db->sql_query($sql); +		$row = $this->db->sql_fetchrow($result); +		$this->db->sql_freeresult($result); + +		if (!$row || (empty($report_text) && strtolower($row['reason_title']) === 'other')) +		{ +			throw new empty_report_exception(); +		} + +		$report_data = array( +			'reason_id'							=> $reason_id, +			'post_id'							=> 0, +			'pm_id'								=> $id, +			'user_notify'						=> $user_notify, +			'report_text'						=> $report_text, +			'reported_post_text'				=> $this->report_data['message_text'], +			'reported_post_uid'					=> $this->report_data['bbcode_bitfield'], +			'reported_post_bitfield'			=> $this->report_data['bbcode_uid'], +			'reported_post_enable_bbcode'		=> $this->report_data['enable_bbcode'], +			'reported_post_enable_smilies'		=> $this->report_data['enable_smilies'], +			'reported_post_enable_magic_url'	=> $this->report_data['enable_magic_url'], +		); + +		$report_id = $this->create_report($report_data); + +		$sql = 'UPDATE ' . PRIVMSGS_TABLE . ' +			SET message_reported = 1 +			WHERE msg_id = ' . $id; +		$this->db->sql_query($sql); + +		$sql_ary = array( +			'msg_id'		=> $id, +			'user_id'		=> ANONYMOUS, +			'author_id'		=> (int) $this->report_data['author_id'], +			'pm_deleted'	=> 0, +			'pm_new'		=> 0, +			'pm_unread'		=> 0, +			'pm_replied'	=> 0, +			'pm_marked'		=> 0, +			'pm_forwarded'	=> 0, +			'folder_id'		=> PRIVMSGS_INBOX, +		); + +		$sql = 'INSERT INTO ' . PRIVMSGS_TO_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); +		$this->db->sql_query($sql); + +		$this->notifications->add_notifications('notification.type.report_pm', array_merge($this->report_data, $row, array( +			'report_text'	=> $report_text, +			'from_user_id'	=> $this->report_data['author_id'], +			'report_id'		=> $report_id, +		))); +	} + +	/** +	 * {@inheritdoc} +	 * @throws \phpbb\report\exception\pm_reporting_disabled_exception when PM reporting is disabled on the board +	 */ +	public function validate_report_request($id) +	{ +		$id = (int) $id; + +		// Check if reporting PMs is enabled +		if (!$this->config['allow_pm_report']) +		{ +			throw new pm_reporting_disabled_exception(); +		} +		else if ($id <= 0) +		{ +			throw new entity_not_found_exception('NO_POST_SELECTED'); +		} + +		// Grab all relevant data +		$sql = 'SELECT p.*, pt.* +			FROM ' . PRIVMSGS_TABLE . ' p, ' . PRIVMSGS_TO_TABLE . " pt +			WHERE p.msg_id = $id +				AND p.msg_id = pt.msg_id +				AND (p.author_id = " . $this->user->data['user_id'] . " +					OR pt.user_id = " . $this->user->data['user_id'] . ")"; +		$result = $this->db->sql_query($sql); +		$report_data = $this->db->sql_fetchrow($result); +		$this->db->sql_freeresult($result); + +		// Check if message exists +		if (!$report_data) +		{ +			$this->user->add_lang('ucp'); +			throw new entity_not_found_exception('NO_MESSAGE'); +		} + +		// Check if message is already reported +		if ($report_data['message_reported']) +		{ +			throw new already_reported_exception(); +		} + +		$this->report_data = $report_data; +	} +} diff --git a/phpBB/phpbb/report/report_handler_post.php b/phpBB/phpbb/report/report_handler_post.php new file mode 100644 index 0000000000..ce4ed67d27 --- /dev/null +++ b/phpBB/phpbb/report/report_handler_post.php @@ -0,0 +1,175 @@ +<?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\report; + +use phpbb\report\exception\invalid_report_exception; +use phpbb\report\exception\empty_report_exception; +use phpbb\report\exception\already_reported_exception; +use phpbb\report\exception\entity_not_found_exception; +use phpbb\report\exception\report_permission_denied_exception; + +class report_handler_post extends report_handler +{ +	/** +	 * @var array +	 */ +	protected $forum_data; + +	/** +	 * {@inheritdoc} +	 * @throws \phpbb\report\exception\report_permission_denied_exception when the user does not have permission to report the post +	 */ +	public function add_report($id, $reason_id, $report_text, $user_notify) +	{ +		// Cast the input variables +		$id				= (int) $id; +		$reason_id		= (int) $reason_id; +		$report_text	= (string) $report_text; +		$user_notify	= (int) $user_notify; + +		$this->validate_report_request($id); + +		$sql = 'SELECT * +			FROM ' . REPORTS_REASONS_TABLE . " +			WHERE reason_id = $reason_id"; +		$result = $this->db->sql_query($sql); +		$row = $this->db->sql_fetchrow($result); +		$this->db->sql_freeresult($result); + +		if (!$row || (empty($report_text) && strtolower($row['reason_title']) === 'other')) +		{ +			throw new empty_report_exception(); +		} + +		$report_data = array( +			'reason_id'							=> $reason_id, +			'post_id'							=> $id, +			'pm_id'								=> 0, +			'user_notify'						=> $user_notify, +			'report_text'						=> $report_text, +			'reported_post_text'				=> $this->report_data['post_text'], +			'reported_post_uid'					=> $this->report_data['bbcode_bitfield'], +			'reported_post_bitfield'			=> $this->report_data['bbcode_uid'], +			'reported_post_enable_bbcode'		=> $this->report_data['enable_bbcode'], +			'reported_post_enable_smilies'		=> $this->report_data['enable_smilies'], +			'reported_post_enable_magic_url'	=> $this->report_data['enable_magic_url'], +		); + +		$report_id = $this->create_report($report_data); + +		$sql = 'UPDATE ' . POSTS_TABLE . ' +			SET post_reported = 1 +			WHERE post_id = ' . $id; +		$this->db->sql_query($sql); + +		if (!$this->report_data['topic_reported']) +		{ +			$sql = 'UPDATE ' . TOPICS_TABLE . ' +				SET topic_reported = 1 +				WHERE topic_id = ' . $this->report_data['topic_id'] . ' +					OR topic_moved_id = ' . $this->report_data['topic_id']; +			$this->db->sql_query($sql); +		} + +		$this->notifications->add_notifications('notification.type.report_post', array_merge($this->report_data, $row, $this->forum_data, array( +			'report_text'	=> $report_text, +		))); +	} + +	/** +	 * {@inheritdoc} +	 * @throws \phpbb\report\exception\report_permission_denied_exception when the user does not have permission to report the post +	 */ +	public function validate_report_request($id) +	{ +		$id = (int) $id; + +		// Check if id is valid +		if ($id <= 0) +		{ +			throw new entity_not_found_exception('NO_POST_SELECTED'); +		} + +		// Grab all relevant data +		$sql = 'SELECT t.*, p.* +			FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . " t +			WHERE p.post_id = $id +				AND p.topic_id = t.topic_id"; +		$result = $this->db->sql_query($sql); +		$report_data = $this->db->sql_fetchrow($result); +		$this->db->sql_freeresult($result); + +		if (!$report_data) +		{ +			throw new entity_not_found_exception('POST_NOT_EXIST'); +		} + +		$forum_id = (int) $report_data['forum_id']; + +		$sql = 'SELECT * +			FROM ' . FORUMS_TABLE . ' +			WHERE forum_id = ' . $forum_id; +		$result = $this->db->sql_query($sql); +		$forum_data = $this->db->sql_fetchrow($result); +		$this->db->sql_freeresult($result); + +		if (!$forum_data) +		{ +			throw new invalid_report_exception('FORUM_NOT_EXIST'); +		} + +		$acl_check_ary = array( +			'f_list' => 'POST_NOT_EXIST', +			'f_read' => 'USER_CANNOT_READ', +			'f_report' => 'USER_CANNOT_REPORT' +		); + +		/** +		 * This event allows you to do extra auth checks and verify if the user +		 * has the required permissions +		 * +		 * @event core.report_post_auth +		 * @var	array	forum_data		All data available from the forums table on this post's forum +		 * @var	array	report_data		All data available from the topics and the posts tables on this post (and its topic) +		 * @var	array	acl_check_ary	An array with the ACL to be tested. The evaluation is made in the same order as the array is sorted +		 *								The key is the ACL name and the value is the language key for the error message. +		 * @since 3.1.3-RC1 +		 */ +		$vars = array( +			'forum_data', +			'report_data', +			'acl_check_ary', +		); +		extract($this->dispatcher->trigger_event('core.report_post_auth', compact($vars))); + +		$this->auth->acl($this->user->data); + +		foreach ($acl_check_ary as $acl => $error) +		{ +			if (!$this->auth->acl_get($acl, $forum_id)) +			{ +				throw new report_permission_denied_exception($error); +			} +		} +		unset($acl_check_ary); + +		if ($report_data['post_reported']) +		{ +			throw new already_reported_exception(); +		} + +		$this->report_data	= $report_data; +		$this->forum_data	= $forum_data; +	} +} diff --git a/phpBB/phpbb/report/report_reason_list_provider.php b/phpBB/phpbb/report/report_reason_list_provider.php new file mode 100644 index 0000000000..388a61d577 --- /dev/null +++ b/phpBB/phpbb/report/report_reason_list_provider.php @@ -0,0 +1,78 @@ +<?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\report; + +class report_reason_list_provider +{ +	/** +	 * @var \phpbb\db\driver\driver_interface +	 */ +	protected $db; + +	/** +	 * @var \phpbb\template\template +	 */ +	protected $template; + +	/** +	 * @var \phpbb\user +	 */ +	protected $user; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\db\driver\driver_interface	$db +	 * @param \phpbb\template\template			$template +	 * @param \phpbb\user						$user +	 */ +	public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\template\template $template, \phpbb\user $user) +	{ +		$this->db		= $db; +		$this->template	= $template; +		$this->user		= $user; +	} + +	/** +	 * Sets template variables to render report reasons select HTML input +	 * +	 * @param int	$reason_id +	 * @return null +	 */ +	public function display_reasons($reason_id = 0) +	{ +		$sql = 'SELECT * +			FROM ' . REPORTS_REASONS_TABLE . ' +			ORDER BY reason_order ASC'; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			// If the reason is defined within the language file, we will use the localized version, else just use the database entry... +			if (isset($this->user->lang['report_reasons']['TITLE'][strtoupper($row['reason_title'])]) && isset($this->user->lang['report_reasons']['DESCRIPTION'][strtoupper($row['reason_title'])])) +			{ +				$row['reason_description'] = $this->user->lang['report_reasons']['DESCRIPTION'][strtoupper($row['reason_title'])]; +				$row['reason_title'] = $this->user->lang['report_reasons']['TITLE'][strtoupper($row['reason_title'])]; +			} + +			$this->template->assign_block_vars('reason', array( +				'ID'			=> $row['reason_id'], +				'TITLE'			=> $row['reason_title'], +				'DESCRIPTION'	=> $row['reason_description'], +				'S_SELECTED'	=> ($row['reason_id'] == $reason_id) ? true : false, +			)); +		} +		$this->db->sql_freeresult($result); +	} +} diff --git a/phpBB/phpbb/request/deactivated_super_global.php b/phpBB/phpbb/request/deactivated_super_global.php index b6cad59be4..ab56240b14 100644 --- a/phpBB/phpbb/request/deactivated_super_global.php +++ b/phpBB/phpbb/request/deactivated_super_global.php @@ -56,7 +56,7 @@ class deactivated_super_global implements \ArrayAccess, \Countable, \IteratorAgg  		$file = '';  		$line = 0; -		$message = 'Illegal use of $' . $this->name . '. You must use the request class or request_var() to access input data. Found in %s on line %d. This error message was generated by deactivated_super_global.'; +		$message = 'Illegal use of $' . $this->name . '. You must use the request class to access input data. Found in %s on line %d. This error message was generated by deactivated_super_global.';  		$backtrace = debug_backtrace();  		if (isset($backtrace[1])) diff --git a/phpBB/phpbb/routing/router.php b/phpBB/phpbb/routing/router.php new file mode 100644 index 0000000000..7444f06253 --- /dev/null +++ b/phpBB/phpbb/routing/router.php @@ -0,0 +1,325 @@ +<?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\routing; + +use Symfony\Component\Config\ConfigCache; +use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper; +use Symfony\Component\Routing\Generator\Dumper\PhpGeneratorDumper; +use Symfony\Component\Routing\Matcher\UrlMatcher; +use Symfony\Component\Routing\Generator\UrlGenerator; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RouterInterface; +use Symfony\Component\Routing\Loader\YamlFileLoader; +use Symfony\Component\Config\FileLocator; +use phpbb\extension\manager; + +/** + * Integration of all pieces of the routing system for easier use. + */ +class router implements RouterInterface +{ +	/** +	 * Extension manager +	 * +	 * @var manager +	 */ +	protected $extension_manager; + +	/** +	 * phpBB root path +	 * +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * PHP file extensions +	 * +	 * @var string +	 */ +	protected $php_ext; + +	/** +	 * Name of the current environment +	 * +	 * @var string +	 */ +	protected $environment; + +	/** +	 * YAML file(s) containing route information +	 * +	 * @var array +	 */ +	protected $routing_files; + +	/** +	 * @var \Symfony\Component\Routing\Matcher\UrlMatcherInterface|null +	 */ +	protected $matcher; + +	/** +	 * @var \Symfony\Component\Routing\Generator\UrlGeneratorInterface|null +	 */ +	protected $generator; + +	/** +	 * @var RequestContext +	 */ +	protected $context; + +	/** +	 * @var RouteCollection|null +	 */ +	protected $route_collection; + +	/** +	 * @var \phpbb\filesystem\filesystem_interface +	 */ +	protected $filesystem; + +	/** +	 * Construct method +	 * +	 * @param \phpbb\filesystem\filesystem_interface $filesystem	Filesystem helper +	 * @param manager	$extension_manager	Extension manager +	 * @param string	$phpbb_root_path	phpBB root path +	 * @param string	$php_ext			PHP file extension +	 * @param string	$environment		Name of the current environment +	 * @param array		$routing_files		Array of strings containing paths to YAML files holding route information +	 */ +	public function __construct(\phpbb\filesystem\filesystem_interface $filesystem, manager $extension_manager, $phpbb_root_path, $php_ext, $environment, $routing_files = array()) +	{ +		$this->filesystem			= $filesystem; +		$this->extension_manager	= $extension_manager; +		$this->routing_files		= $routing_files; +		$this->phpbb_root_path		= $phpbb_root_path; +		$this->php_ext				= $php_ext; +		$this->environment			= $environment; +		$this->context				= new RequestContext(); +	} + +	/** +	 * Find the list of routing files +	 * +	 * @param array $paths Array of paths where to look for routing files (they must be relative to the phpBB root path). +	 * @return router +	 */ +	public function find_routing_files(array $paths) +	{ +		$this->routing_files = array('config/' . $this->environment . '/routing/environment.yml'); +		foreach ($paths as $path) +		{ +			if (file_exists($this->phpbb_root_path . $path . 'config/' . $this->environment . '/routing/environment.yml')) +			{ +				$this->routing_files[] = $path . 'config/' . $this->environment . '/routing/environment.yml'; +			} +			else if (!is_dir($this->phpbb_root_path . $path . 'config/' . $this->environment)) +			{ +				if (file_exists($this->phpbb_root_path . $path . 'config/default/routing/environment.yml')) +				{ +					$this->routing_files[] = $path . 'config/default/routing/environment.yml'; +				} +				else if (!is_dir($this->phpbb_root_path . $path . 'config/default/routing') && file_exists($this->phpbb_root_path . $path . 'config/routing.yml')) +				{ +					$this->routing_files[] = $path . 'config/routing.yml'; +				} +			} +		} + +		return $this; +	} + +	/** +	 * Find a list of controllers +	 * +	 * @param string $base_path Base path to prepend to file paths +	 * @return router +	 */ +	public function find($base_path = '') +	{ +		if ($this->route_collection === null || $this->route_collection->count() === 0) +		{ +			$this->route_collection = new RouteCollection; +			foreach ($this->routing_files as $file_path) +			{ +				$loader = new YamlFileLoader(new FileLocator($this->filesystem->realpath($base_path))); +				$this->route_collection->addCollection($loader->load($file_path)); +			} +		} + +		return $this; +	} + +	/** +	 * Get the list of routes +	 * +	 * @return RouteCollection Get the route collection +	 */ +	public function get_routes() +	{ +		if ($this->route_collection == null || empty($this->routing_files)) +		{ +			$this->find_routing_files($this->extension_manager->all_enabled(false)) +				->find($this->phpbb_root_path); +		} + +		return $this->route_collection; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function getRouteCollection() +	{ +		return $this->get_routes(); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function setContext(RequestContext $context) +	{ +		$this->context = $context; + +		if ($this->matcher !== null) +		{ +			$this->get_matcher()->setContext($context); +		} +		if ($this->generator !== null) +		{ +			$this->get_generator()->setContext($context); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function getContext() +	{ +		return $this->context; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH) +	{ +		return $this->get_generator()->generate($name, $parameters, $referenceType); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function match($pathinfo) +	{ +		return $this->get_matcher()->match($pathinfo); +	} + +	/** +	 * Gets the UrlMatcher instance associated with this Router. +	 * +	 * @return \Symfony\Component\Routing\Matcher\UrlMatcherInterface A UrlMatcherInterface instance +	 */ +	public function get_matcher() +	{ +		if ($this->matcher !== null) +		{ +			return $this->matcher; +		} + +		$this->create_dumped_url_matcher(); + +		return $this->matcher; +	} +	/** +	 * Creates a new dumped URL Matcher (dump it if necessary) +	 */ +	protected function create_dumped_url_matcher() +	{ +		$cache = new ConfigCache("{$this->phpbb_root_path}cache/{$this->environment}/url_matcher.{$this->php_ext}", defined('DEBUG')); +		if (!$cache->isFresh()) +		{ +			$dumper = new PhpMatcherDumper($this->get_routes()); + +			$options = array( +				'class'      => 'phpbb_url_matcher', +				'base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', +			); + +			$cache->write($dumper->dump($options), $this->get_routes()->getResources()); +		} + +		require_once($cache->getPath()); + +		$this->matcher = new \phpbb_url_matcher($this->context); +	} + +	/** +	 * Creates a new URL Matcher +	 */ +	protected function create_new_url_matcher() +	{ +		$this->matcher = new UrlMatcher($this->get_routes(), $this->context); +	} + +	/** +	 * Gets the UrlGenerator instance associated with this Router. +	 * +	 * @return \Symfony\Component\Routing\Generator\UrlGeneratorInterface A UrlGeneratorInterface instance +	 */ +	public function get_generator() +	{ +		if ($this->generator !== null) +		{ +			return $this->generator; +		} + +		$this->create_dumped_url_generator(); + +		return $this->generator; +	} + +	/** +	 * Creates a new dumped URL Generator (dump it if necessary) +	 */ +	protected function create_dumped_url_generator() +	{ +		$cache = new ConfigCache("{$this->phpbb_root_path}cache/{$this->environment}/url_generator.{$this->php_ext}", defined('DEBUG')); +		if (!$cache->isFresh()) +		{ +			$dumper = new PhpGeneratorDumper($this->get_routes()); + +			$options = array( +				'class'      => 'phpbb_url_generator', +				'base_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', +			); + +			$cache->write($dumper->dump($options), $this->get_routes()->getResources()); +		} + +		require_once($cache->getPath()); + +		$this->generator = new \phpbb_url_generator($this->context); +	} + +	/** +	 * Creates a new URL Generator +	 */ +	protected function create_new_url_generator() +	{ +		$this->generator = new UrlGenerator($this->get_routes(), $this->context); +	} +} diff --git a/phpBB/phpbb/search/fulltext_mysql.php b/phpBB/phpbb/search/fulltext_mysql.php index 1a0aba096f..da9de56009 100644 --- a/phpBB/phpbb/search/fulltext_mysql.php +++ b/phpBB/phpbb/search/fulltext_mysql.php @@ -188,8 +188,8 @@ class fulltext_mysql extends \phpbb\search\base  		}  		$this->db->sql_freeresult($result); -		set_config('fulltext_mysql_max_word_len', $mysql_info['ft_max_word_len']); -		set_config('fulltext_mysql_min_word_len', $mysql_info['ft_min_word_len']); +		$this->config->set('fulltext_mysql_max_word_len', $mysql_info['ft_max_word_len']); +		$this->config->set('fulltext_mysql_min_word_len', $mysql_info['ft_min_word_len']);  		return false;  	} @@ -745,7 +745,7 @@ class fulltext_mysql extends \phpbb\search\base  		// destroy too old cached search results  		$this->destroy_cache(array()); -		set_config('search_last_gc', time(), true); +		$this->config->set('search_last_gc', time(), false);  	}  	/** diff --git a/phpBB/phpbb/search/fulltext_native.php b/phpBB/phpbb/search/fulltext_native.php index 4d02dd1cbf..42ad97da30 100644 --- a/phpBB/phpbb/search/fulltext_native.php +++ b/phpBB/phpbb/search/fulltext_native.php @@ -18,6 +18,13 @@ namespace phpbb\search;  */  class fulltext_native extends \phpbb\search\base  { +	const UTF8_HANGUL_FIRST = "\xEA\xB0\x80"; +	const UTF8_HANGUL_LAST = "\xED\x9E\xA3"; +	const UTF8_CJK_FIRST = "\xE4\xB8\x80"; +	const UTF8_CJK_LAST = "\xE9\xBE\xBB"; +	const UTF8_CJK_B_FIRST = "\xF0\xA0\x80\x80"; +	const UTF8_CJK_B_LAST = "\xF0\xAA\x9B\x96"; +  	/**  	 * Associative array holding index stats  	 * @var array @@ -93,7 +100,7 @@ class fulltext_native extends \phpbb\search\base  	protected $user;  	/** -	* Initialises the fulltext_native search backend with min/max word length and makes sure the UTF-8 normalizer is loaded +	* Initialises the fulltext_native search backend with min/max word length  	*  	* @param	boolean|string	&$error	is passed by reference and should either be set to false on success or an error message on failure  	*/ @@ -110,10 +117,6 @@ class fulltext_native extends \phpbb\search\base  		/**  		* Load the UTF tools  		*/ -		if (!class_exists('utf_normalizer')) -		{ -			include($this->phpbb_root_path . 'includes/utf/utf_normalizer.' . $this->php_ext); -		}  		if (!function_exists('utf8_decode_ncr'))  		{  			include($this->phpbb_root_path . 'includes/utf/utf_tools.' . $this->php_ext); @@ -1172,9 +1175,9 @@ class fulltext_native extends \phpbb\search\base  				* Note: this could be optimized. If the codepoint is lower than Hangul's range  				* we know that it will also be lower than CJK ranges  				*/ -				if ((strncmp($word, UTF8_HANGUL_FIRST, 3) < 0 || strncmp($word, UTF8_HANGUL_LAST, 3) > 0) -					&& (strncmp($word, UTF8_CJK_FIRST, 3) < 0 || strncmp($word, UTF8_CJK_LAST, 3) > 0) -					&& (strncmp($word, UTF8_CJK_B_FIRST, 4) < 0 || strncmp($word, UTF8_CJK_B_LAST, 4) > 0)) +				if ((strncmp($word, self::UTF8_HANGUL_FIRST, 3) < 0 || strncmp($word, self::UTF8_HANGUL_LAST, 3) > 0) +					&& (strncmp($word, self::UTF8_CJK_FIRST, 3) < 0 || strncmp($word, self::UTF8_CJK_LAST, 3) > 0) +					&& (strncmp($word, self::UTF8_CJK_B_FIRST, 4) < 0 || strncmp($word, self::UTF8_CJK_B_LAST, 4) > 0))  				{  					$word = strtok(' ');  					continue; @@ -1419,7 +1422,7 @@ class fulltext_native extends \phpbb\search\base  		// carry on ... it's okay ... I know when I'm not wanted boo hoo  		if (!$this->config['fulltext_native_load_upd'])  		{ -			set_config('search_last_gc', time(), true); +			$this->config->set('search_last_gc', time(), false);  			return;  		} @@ -1454,7 +1457,7 @@ class fulltext_native extends \phpbb\search\base  				// by setting search_last_gc to the new time here we make sure that if a user reloads because the  				// following query takes too long, he won't run into it again -				set_config('search_last_gc', time(), true); +				$this->config->set('search_last_gc', time(), false);  				// Delete the matches  				$sql = 'DELETE FROM ' . SEARCH_WORDMATCH_TABLE . ' @@ -1470,7 +1473,7 @@ class fulltext_native extends \phpbb\search\base  			$this->destroy_cache(array_unique($destroy_cache_words));  		} -		set_config('search_last_gc', time(), true); +		$this->config->set('search_last_gc', time(), false);  	}  	/** @@ -1541,8 +1544,6 @@ class fulltext_native extends \phpbb\search\base  	* @param	string	$allowed_chars	String of special chars to allow  	* @param	string	$encoding		Text encoding  	* @return	string					Cleaned up text, only alphanumeric chars are left -	* -	* @todo \normalizer::cleanup being able to be used?  	*/  	protected function cleanup($text, $allowed_chars = null, $encoding = 'utf-8')  	{ @@ -1569,12 +1570,9 @@ class fulltext_native extends \phpbb\search\base  		$text = htmlspecialchars_decode(utf8_decode_ncr($text), ENT_QUOTES);  		/** -		* Load the UTF-8 normalizer -		* -		* If we use it more widely, an instance of that class should be held in a -		* a global variable instead +		* Normalize to NFC  		*/ -		\utf_normalizer::nfc($text); +		$text = \Normalizer::normalize($text);  		/**  		* The first thing we do is: @@ -1667,9 +1665,9 @@ class fulltext_native extends \phpbb\search\base  			$utf_char = substr($text, $pos, $utf_len);  			$pos += $utf_len; -			if (($utf_char >= UTF8_HANGUL_FIRST && $utf_char <= UTF8_HANGUL_LAST) -				|| ($utf_char >= UTF8_CJK_FIRST && $utf_char <= UTF8_CJK_LAST) -				|| ($utf_char >= UTF8_CJK_B_FIRST && $utf_char <= UTF8_CJK_B_LAST)) +			if (($utf_char >= self::UTF8_HANGUL_FIRST && $utf_char <= self::UTF8_HANGUL_LAST) +				|| ($utf_char >= self::UTF8_CJK_FIRST && $utf_char <= self::UTF8_CJK_LAST) +				|| ($utf_char >= self::UTF8_CJK_B_FIRST && $utf_char <= self::UTF8_CJK_B_LAST))  			{  				/**  				* All characters within these ranges are valid diff --git a/phpBB/phpbb/search/fulltext_postgres.php b/phpBB/phpbb/search/fulltext_postgres.php index b6af371d13..5a68f0cbfb 100644 --- a/phpBB/phpbb/search/fulltext_postgres.php +++ b/phpBB/phpbb/search/fulltext_postgres.php @@ -746,7 +746,7 @@ class fulltext_postgres extends \phpbb\search\base  		// destroy too old cached search results  		$this->destroy_cache(array()); -		set_config('search_last_gc', time(), true); +		$this->config->set('search_last_gc', time(), false);  	}  	/** diff --git a/phpBB/phpbb/search/fulltext_sphinx.php b/phpBB/phpbb/search/fulltext_sphinx.php index eb53ca6d40..a5ad96b114 100644 --- a/phpBB/phpbb/search/fulltext_sphinx.php +++ b/phpBB/phpbb/search/fulltext_sphinx.php @@ -85,7 +85,7 @@ class fulltext_sphinx  	/**  	 * Database Tools object -	 * @var \phpbb\db\tools +	 * @var \phpbb\db\tools\tools_interface  	 */  	protected $db_tools; @@ -135,12 +135,13 @@ class fulltext_sphinx  		$this->db = $db;  		$this->auth = $auth; -		// Initialize \phpbb\db\tools object -		$this->db_tools = new \phpbb\db\tools($this->db); +		// Initialize \phpbb\db\tools\tools object +		global $phpbb_container; // TODO inject into object +		$this->db_tools = $phpbb_container->get('dbal.tools');  		if(!$this->config['fulltext_sphinx_id'])  		{ -			set_config('fulltext_sphinx_id', unique_id()); +			$this->config->set('fulltext_sphinx_id', unique_id());  		}  		$this->id = $this->config['fulltext_sphinx_id'];  		$this->indexes = 'index_phpbb_' . $this->id . '_delta;index_phpbb_' . $this->id . '_main'; @@ -211,7 +212,7 @@ class fulltext_sphinx  		}  		// Move delta to main index each hour -		set_config('search_gc', 3600); +		$this->config->set('search_gc', 3600);  		return false;  	} @@ -454,6 +455,8 @@ class fulltext_sphinx  	*/  	public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page)  	{ +		global $user, $phpbb_log; +  		// No keywords? No posts.  		if (!strlen($this->search_query) && !sizeof($author_ary))  		{ @@ -601,7 +604,7 @@ class fulltext_sphinx  		if ($this->sphinx->GetLastError())  		{ -			add_log('critical', 'LOG_SPHINX_ERROR', $this->sphinx->GetLastError()); +			$phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_SPHINX_ERROR', false, array($this->sphinx->GetLastError()));  			if ($this->auth->acl_get('a_'))  			{  				trigger_error($this->user->lang('SPHINX_SEARCH_FAILED', $this->sphinx->GetLastError())); @@ -755,7 +758,7 @@ class fulltext_sphinx  	*/  	public function tidy($create = false)  	{ -		set_config('search_last_gc', time(), true); +		$this->config->set('search_last_gc', time(), false);  	}  	/** diff --git a/phpBB/phpbb/session.php b/phpBB/phpbb/session.php index bedd581725..6154f384f3 100644 --- a/phpBB/phpbb/session.php +++ b/phpBB/phpbb/session.php @@ -92,8 +92,8 @@ class session  		}  		// current directory within the phpBB root (for example: adm) -		$root_dirs = explode('/', str_replace('\\', '/', phpbb_realpath($root_path))); -		$page_dirs = explode('/', str_replace('\\', '/', phpbb_realpath('./'))); +		$root_dirs = explode('/', str_replace('\\', '/', $phpbb_filesystem->realpath($root_path))); +		$page_dirs = explode('/', str_replace('\\', '/', $phpbb_filesystem->realpath('./')));  		$intersection = array_intersect_assoc($root_dirs, $page_dirs);  		$root_dirs = array_diff_assoc($root_dirs, $intersection); @@ -219,7 +219,7 @@ class session  	function session_begin($update_session_page = true)  	{  		global $phpEx, $SID, $_SID, $_EXTRA_URL, $db, $config, $phpbb_root_path; -		global $request, $phpbb_container; +		global $request, $phpbb_container, $user, $phpbb_log;  		// Give us some basic information  		$this->time_now				= time(); @@ -257,23 +257,23 @@ class session  		if ($request->is_set($config['cookie_name'] . '_sid', \phpbb\request\request_interface::COOKIE) || $request->is_set($config['cookie_name'] . '_u', \phpbb\request\request_interface::COOKIE))  		{ -			$this->cookie_data['u'] = request_var($config['cookie_name'] . '_u', 0, false, true); -			$this->cookie_data['k'] = request_var($config['cookie_name'] . '_k', '', false, true); -			$this->session_id 		= request_var($config['cookie_name'] . '_sid', '', false, true); +			$this->cookie_data['u'] = $request->variable($config['cookie_name'] . '_u', 0, false, \phpbb\request\request_interface::COOKIE); +			$this->cookie_data['k'] = $request->variable($config['cookie_name'] . '_k', '', false, \phpbb\request\request_interface::COOKIE); +			$this->session_id 		= $request->variable($config['cookie_name'] . '_sid', '', false, \phpbb\request\request_interface::COOKIE);  			$SID = (defined('NEED_SID')) ? '?sid=' . $this->session_id : '?sid=';  			$_SID = (defined('NEED_SID')) ? $this->session_id : '';  			if (empty($this->session_id))  			{ -				$this->session_id = $_SID = request_var('sid', ''); +				$this->session_id = $_SID = $request->variable('sid', '');  				$SID = '?sid=' . $this->session_id;  				$this->cookie_data = array('u' => 0, 'k' => '');  			}  		}  		else  		{ -			$this->session_id = $_SID = request_var('sid', ''); +			$this->session_id = $_SID = $request->variable('sid', '');  			$SID = '?sid=' . $this->session_id;  		} @@ -349,8 +349,8 @@ class session  			}  			else  			{ -				set_config('limit_load', '0'); -				set_config('limit_search_load', '0'); +				$config->set('limit_load', '0'); +				$config->set('limit_search_load', '0');  			}  		} @@ -413,6 +413,7 @@ class session  					$session_expired = false;  					// Check whether the session is still valid if we have one +					/* @var $provider_collection \phpbb\auth\provider_collection */  					$provider_collection = $phpbb_container->get('auth.provider_collection');  					$provider = $provider_collection->get_provider(); @@ -493,11 +494,18 @@ class session  					{  						if ($referer_valid)  						{ -							add_log('critical', 'LOG_IP_BROWSER_FORWARDED_CHECK', $u_ip, $s_ip, $u_browser, $s_browser, htmlspecialchars($u_forwarded_for), htmlspecialchars($s_forwarded_for)); +							$phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_IP_BROWSER_FORWARDED_CHECK', false, array( +								$u_ip, +								$s_ip, +								$u_browser, +								$s_browser, +								htmlspecialchars($u_forwarded_for), +								htmlspecialchars($s_forwarded_for) +							));  						}  						else  						{ -							add_log('critical', 'LOG_REFERER_INVALID', $this->referer); +							$phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_REFERER_INVALID', false, array($this->referer));  						}  					}  				} @@ -582,6 +590,7 @@ class session  			}  		} +		/* @var $provider_collection \phpbb\auth\provider_collection */  		$provider_collection = $phpbb_container->get('auth.provider_collection');  		$provider = $provider_collection->get_provider();  		$this->data = $provider->autologin(); @@ -914,6 +923,7 @@ class session  		$db->sql_query($sql);  		// Allow connecting logout with external auth method logout +		/* @var $provider_collection \phpbb\auth\provider_collection */  		$provider_collection = $phpbb_container->get('auth.provider_collection');  		$provider = $provider_collection->get_provider();  		$provider->logout($this->data, $new_session); @@ -1030,7 +1040,7 @@ class session  		{  			// Less than 10 users, update gc timer ... else we want gc  			// called again to delete other sessions -			set_config('session_last_gc', $this->time_now, true); +			$config->set('session_last_gc', $this->time_now, false);  			if ($config['max_autologin_time'])  			{ @@ -1040,6 +1050,7 @@ class session  			}  			// only called from CRON; should be a safe workaround until the infrastructure gets going +			/* @var $captcha_factory \phpbb\captcha\factory */  			$captcha_factory = $phpbb_container->get('captcha.factory');  			$captcha_factory->garbage_collect($config['captcha_plugin']); diff --git a/phpBB/phpbb/template/asset.php b/phpBB/phpbb/template/asset.php index 67dbd7b357..cb00f16549 100644 --- a/phpBB/phpbb/template/asset.php +++ b/phpBB/phpbb/template/asset.php @@ -20,15 +20,20 @@ class asset  	/** @var \phpbb\path_helper **/  	protected $path_helper; +	/** @var \phpbb\filesystem\filesystem */ +	protected $filesystem; +  	/**  	* Constructor  	*  	* @param string $url URL  	* @param \phpbb\path_helper $path_helper Path helper object +	* @param \phpbb\filesystem\filesystem $filesystem  	*/ -	public function __construct($url, \phpbb\path_helper $path_helper) +	public function __construct($url, \phpbb\path_helper $path_helper, \phpbb\filesystem\filesystem $filesystem)  	{  		$this->path_helper = $path_helper; +		$this->filesystem = $filesystem;  		$this->set_url($url);  	} @@ -152,6 +157,24 @@ class asset  	*/  	public function set_path($path, $urlencode = false)  	{ +		// Since 1.7.0 Twig returns the real path of the file. We need it to be relative. +		$real_root_path = $this->filesystem->realpath($this->path_helper->get_phpbb_root_path()) . DIRECTORY_SEPARATOR; + +		// If the asset is under the phpBB root path we need to remove its path and then prepend $phpbb_root_path +		if ($real_root_path && substr($path . DIRECTORY_SEPARATOR, 0, strlen($real_root_path)) === $real_root_path) +		{ +			$path = $this->path_helper->get_phpbb_root_path() . str_replace('\\', '/', substr($path, strlen($real_root_path))); +		} +		else +		{ +			// Else we make the path relative to the current working directory +			$real_root_path = $this->filesystem->realpath('.') . DIRECTORY_SEPARATOR; +			if ($real_root_path && substr($path . DIRECTORY_SEPARATOR, 0, strlen($real_root_path)) === $real_root_path) +			{ +				$path = str_replace('\\', '/', substr($path, strlen($real_root_path))); +			} +		} +  		if ($urlencode)  		{  			$paths = explode('/', $path); @@ -161,6 +184,7 @@ class asset  			}  			$path = implode('/', $paths);  		} +  		$this->components['path'] = $path;  	} diff --git a/phpBB/phpbb/template/twig/environment.php b/phpBB/phpbb/template/twig/environment.php index 476ffd935e..e7b8aeab89 100644 --- a/phpBB/phpbb/template/twig/environment.php +++ b/phpBB/phpbb/template/twig/environment.php @@ -18,9 +18,15 @@ class environment extends \Twig_Environment  	/** @var \phpbb\config\config */  	protected $phpbb_config; +	/** @var \phpbb\filesystem\filesystem */ +	protected $filesystem; +  	/** @var \phpbb\path_helper */  	protected $phpbb_path_helper; +	/** @var \Symfony\Component\DependencyInjection\ContainerInterface */ +	protected $container; +  	/** @var \phpbb\extension\manager */  	protected $extension_manager; @@ -37,25 +43,52 @@ class environment extends \Twig_Environment  	* Constructor  	*  	* @param \phpbb\config\config $phpbb_config The phpBB configuration +	* @param \phpbb\filesystem\filesystem $filesystem  	* @param \phpbb\path_helper $path_helper phpBB path helper +	* @param \Symfony\Component\DependencyInjection\ContainerInterface $container The dependency injection container +	* @param string $cache_path The path to the cache directory  	* @param \phpbb\extension\manager $extension_manager phpBB extension manager  	* @param \Twig_LoaderInterface $loader Twig loader interface  	* @param array $options Array of options to pass to Twig  	*/ -	public function __construct($phpbb_config, \phpbb\path_helper $path_helper, \phpbb\extension\manager $extension_manager = null, \Twig_LoaderInterface $loader = null, $options = array()) +	public function __construct(\phpbb\config\config $phpbb_config, \phpbb\filesystem\filesystem $filesystem, \phpbb\path_helper $path_helper, \Symfony\Component\DependencyInjection\ContainerInterface $container, $cache_path, \phpbb\extension\manager $extension_manager = null, \Twig_LoaderInterface $loader = null, $options = array())  	{  		$this->phpbb_config = $phpbb_config; +		$this->filesystem = $filesystem;  		$this->phpbb_path_helper = $path_helper;  		$this->extension_manager = $extension_manager; +		$this->container = $container;  		$this->phpbb_root_path = $this->phpbb_path_helper->get_phpbb_root_path();  		$this->web_root_path = $this->phpbb_path_helper->get_web_root_path(); +		$options = array_merge(array( +			'cache'			=> (defined('IN_INSTALL')) ? false : $cache_path, +			'debug'			=> defined('DEBUG'), +			'auto_reload'	=> (bool) $this->phpbb_config['load_tplcompile'], +			'autoescape'	=> false, +		), $options); +  		return parent::__construct($loader, $options);  	}  	/** +	* {@inheritdoc} +	*/ +	public function getLexer() +	{ +		if (null === $this->lexer) +		{ +			$this->lexer = $this->container->get('template.twig.lexer'); +			$this->lexer->set_environment($this); +		} + +		return $this->lexer; +	} + + +	/**  	* Get the list of enabled phpBB extensions  	*  	* Used in EVENT node @@ -78,16 +111,26 @@ class environment extends \Twig_Environment  	}  	/** -	* Get the phpBB root path -	* -	* @return string -	*/ +	 * Get the phpBB root path +	 * +	 * @return string +	 */  	public function get_phpbb_root_path()  	{  		return $this->phpbb_root_path;  	}  	/** +	* Get the filesystem object +	* +	* @return \phpbb\filesystem\filesystem +	*/ +	public function get_filesystem() +	{ +		return $this->filesystem; +	} + +	/**  	* Get the web root path  	*  	* @return string diff --git a/phpBB/phpbb/template/twig/extension.php b/phpBB/phpbb/template/twig/extension.php index 3a983491b9..92f87a0331 100644 --- a/phpBB/phpbb/template/twig/extension.php +++ b/phpBB/phpbb/template/twig/extension.php @@ -18,20 +18,20 @@ class extension extends \Twig_Extension  	/** @var \phpbb\template\context */  	protected $context; -	/** @var \phpbb\user */ -	protected $user; +	/** @var \phpbb\language\language */ +	protected $language;  	/**  	* Constructor  	*  	* @param \phpbb\template\context $context -	* @param \phpbb\user $user +	* @param \phpbb\language\language $language  	* @return \phpbb\template\twig\extension  	*/ -	public function __construct(\phpbb\template\context $context, $user) +	public function __construct(\phpbb\template\context $context, $language)  	{  		$this->context = $context; -		$this->user = $user; +		$this->language = $language;  	}  	/** @@ -71,6 +71,7 @@ class extension extends \Twig_Extension  	{  		return array(  			new \Twig_SimpleFilter('subset', array($this, 'loop_subset'), array('needs_environment' => true)), +			// @deprecated 3.2.0 Uses twig's JS escape method instead of addslashes  			new \Twig_SimpleFilter('addslashes', 'addslashes'),  		);  	} @@ -177,9 +178,9 @@ class extension extends \Twig_Extension  			return $context_vars['L_' . $key];  		} -		// LA_ is transformed into lang(\'$1\')|addslashes, so we should not +		// LA_ is transformed into lang(\'$1\')|escape('js'), so we should not  		// need to check for it -		return call_user_func_array(array($this->user, 'lang'), $args); +		return call_user_func_array(array($this->language, 'lang'), $args);  	}  } diff --git a/phpBB/phpbb/template/twig/lexer.php b/phpBB/phpbb/template/twig/lexer.php index c5dc7273ba..f1542109a4 100644 --- a/phpBB/phpbb/template/twig/lexer.php +++ b/phpBB/phpbb/template/twig/lexer.php @@ -15,6 +15,11 @@ namespace phpbb\template\twig;  class lexer extends \Twig_Lexer  { +	public function set_environment(\Twig_Environment $env) +	{ +		$this->env = $env; +	} +  	public function tokenize($code, $filename = null)  	{  		// Our phpBB tags @@ -112,9 +117,9 @@ class lexer extends \Twig_Lexer  		// Appends any filters after lang()  		$code = preg_replace('#{L_([a-zA-Z0-9_\.]+)(\|[^}]+?)?}#', '{{ lang(\'$1\')$2 }}', $code); -		// Replace all of our escaped language variables, {LA_VARNAME}, with Twig style, {{ lang('NAME')|addslashes }} -		// Appends any filters after lang(), but before addslashes -		$code = preg_replace('#{LA_([a-zA-Z0-9_\.]+)(\|[^}]+?)?}#', '{{ lang(\'$1\')$2|addslashes }}', $code); +		// Replace all of our escaped language variables, {LA_VARNAME}, with Twig style, {{ lang('NAME')|escape('js') }} +		// Appends any filters after lang(), but before escape('js') +		$code = preg_replace('#{LA_([a-zA-Z0-9_\.]+)(\|[^}]+?)?}#', '{{ lang(\'$1\')$2|escape(\'js\') }}', $code);  		// Replace all of our variables, {VARNAME}, with Twig style, {{ VARNAME }}  		// Appends any filters diff --git a/phpBB/phpbb/template/twig/loader.php b/phpBB/phpbb/template/twig/loader.php index 2f8ffaa776..df8183c019 100644 --- a/phpBB/phpbb/template/twig/loader.php +++ b/phpBB/phpbb/template/twig/loader.php @@ -21,6 +21,24 @@ class loader extends \Twig_Loader_Filesystem  	protected $safe_directories = array();  	/** +	 * @var \phpbb\filesystem\filesystem_interface +	 */ +	protected $filesystem; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\filesystem\filesystem_interface $filesystem +	 * @param string|array	$paths +	 */ +	public function __construct(\phpbb\filesystem\filesystem_interface $filesystem, $paths = array()) +	{ +		$this->filesystem = $filesystem; + +		parent::__construct($paths); +	} + +	/**  	* Set safe directories  	*  	* @param array $directories Array of directories that are safe (empty to clear) @@ -49,7 +67,7 @@ class loader extends \Twig_Loader_Filesystem  	*/  	public function addSafeDirectory($directory)  	{ -		$directory = phpbb_realpath($directory); +		$directory = $this->filesystem->realpath($directory);  		if ($directory !== false)  		{ @@ -118,7 +136,7 @@ class loader extends \Twig_Loader_Filesystem  				//	can now check if we're within a "safe" directory  				// Find the real path of the directory the file is in -				$directory = phpbb_realpath(dirname($file)); +				$directory = $this->filesystem->realpath(dirname($file));  				if ($directory === false)  				{ diff --git a/phpBB/phpbb/template/twig/node/includeasset.php b/phpBB/phpbb/template/twig/node/includeasset.php index 15195a226b..324823b8d7 100644 --- a/phpBB/phpbb/template/twig/node/includeasset.php +++ b/phpBB/phpbb/template/twig/node/includeasset.php @@ -39,7 +39,7 @@ abstract class includeasset extends \Twig_Node  			->write("\$asset_file = ")  			->subcompile($this->getNode('expr'))  			->raw(";\n") -			->write("\$asset = new \phpbb\\template\\asset(\$asset_file, \$this->getEnvironment()->get_path_helper());\n") +			->write("\$asset = new \phpbb\\template\\asset(\$asset_file, \$this->getEnvironment()->get_path_helper(), \$this->getEnvironment()->get_filesystem());\n")  			->write("if (substr(\$asset_file, 0, 2) !== './' && \$asset->is_relative()) {\n")  			->indent()  				->write("\$asset_path = \$asset->get_path();") diff --git a/phpBB/phpbb/template/twig/twig.php b/phpBB/phpbb/template/twig/twig.php index bd754d9bbd..0e4c619029 100644 --- a/phpBB/phpbb/template/twig/twig.php +++ b/phpBB/phpbb/template/twig/twig.php @@ -78,9 +78,12 @@ class twig extends \phpbb\template\base  	* @param \phpbb\config\config $config  	* @param \phpbb\user $user  	* @param \phpbb\template\context $context template context +	* @param \phpbb\template\twig\environment $twig_environment +	* @param string $cache_path +	* @param array|\ArrayAccess $extensions  	* @param \phpbb\extension\manager $extension_manager extension manager, if null then template events will not be invoked  	*/ -	public function __construct(\phpbb\path_helper $path_helper, $config, $user, \phpbb\template\context $context, \phpbb\extension\manager $extension_manager = null) +	public function __construct(\phpbb\path_helper $path_helper, $config, $user, \phpbb\template\context $context, \phpbb\template\twig\environment $twig_environment, $cache_path, $extensions = array(), \phpbb\extension\manager $extension_manager = null)  	{  		$this->path_helper = $path_helper;  		$this->phpbb_root_path = $path_helper->get_phpbb_root_path(); @@ -89,41 +92,14 @@ class twig extends \phpbb\template\base  		$this->user = $user;  		$this->context = $context;  		$this->extension_manager = $extension_manager; +		$this->cachepath = $cache_path; +		$this->twig = $twig_environment; -		$this->cachepath = $this->phpbb_root_path . 'cache/twig/'; - -		// Initiate the loader, __main__ namespace paths will be setup later in set_style_names() -		$loader = new \phpbb\template\twig\loader(''); - -		$this->twig = new \phpbb\template\twig\environment( -			$this->config, -			$this->path_helper, -			$this->extension_manager, -			$loader, -			array( -				'cache'			=> (defined('IN_INSTALL')) ? false : $this->cachepath, -				'debug'			=> defined('DEBUG'), -				'auto_reload'	=> (bool) $this->config['load_tplcompile'], -				'autoescape'	=> false, -			) -		); - -		$this->twig->addExtension( -			new \phpbb\template\twig\extension( -				$this->context, -				$this->user -			) -		); - -		if (defined('DEBUG')) +		foreach ($extensions as $extension)  		{ -			$this->twig->addExtension(new \Twig_Extension_Debug()); +			$this->twig->addExtension($extension);  		} -		$lexer = new \phpbb\template\twig\lexer($this->twig); - -		$this->twig->setLexer($lexer); -  		// Add admin namespace  		if ($this->path_helper->get_adm_relative_path() !== null && is_dir($this->phpbb_root_path . $this->path_helper->get_adm_relative_path() . 'style/'))  		{ @@ -376,6 +352,12 @@ class twig extends \phpbb\template\base  		// cleanup  		unset($vars['loops']['.']); +		// Inject in the main context the value added by assign_block_vars() to be able to use directly the Twig loops. +		foreach ($vars['loops'] as $key => &$value) +		{ +			$vars[$key] = $value; +		} +  		return $vars;  	} diff --git a/phpBB/phpbb/textformatter/cache_interface.php b/phpBB/phpbb/textformatter/cache_interface.php new file mode 100644 index 0000000000..f6b5f195c7 --- /dev/null +++ b/phpBB/phpbb/textformatter/cache_interface.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\textformatter; + +/** +* Currently only used to signal that something that could effect the rendering has changed. +* BBCodes, smilies, censored words, templates, etc... +*/ +interface cache_interface +{ +	/** +	* Invalidate and/or regenerate this text formatter's cache(s) +	*/ +	public function invalidate(); + +	/** +	* Tidy/prune this text formatter's cache(s) +	*/ +	public function tidy(); +} diff --git a/phpBB/phpbb/textformatter/data_access.php b/phpBB/phpbb/textformatter/data_access.php new file mode 100644 index 0000000000..2103bf8e60 --- /dev/null +++ b/phpBB/phpbb/textformatter/data_access.php @@ -0,0 +1,228 @@ +<?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\textformatter; + +/** +* Data access layer that fetchs BBCodes, smilies and censored words from the database. +* To be extended to include insert/update/delete operations. +* +* Also used to get templates. +*/ +class data_access +{ +	/** +	* @var string Name of the BBCodes table +	*/ +	protected $bbcodes_table; + +	/** +	* @var \phpbb\db\driver\driver_interface +	*/ +	protected $db; + +	/** +	* @var string Name of the smilies table +	*/ +	protected $smilies_table; + +	/** +	* @var string Name of the styles table +	*/ +	protected $styles_table; + +	/** +	* @var string Path to the styles dir +	*/ +	protected $styles_path; + +	/** +	* @var string Name of the words table +	*/ +	protected $words_table; + +	/** +	* Constructor +	* +	* @param \phpbb\db\driver\driver_interface $db Database connection +	* @param string $bbcodes_table Name of the BBCodes table +	* @param string $smilies_table Name of the smilies table +	* @param string $styles_table  Name of the styles table +	* @param string $words_table   Name of the words table +	* @param string $styles_path   Path to the styles dir +	*/ +	public function __construct(\phpbb\db\driver\driver_interface $db, $bbcodes_table, $smilies_table, $styles_table, $words_table, $styles_path) +	{ +		$this->db = $db; + +		$this->bbcodes_table = $bbcodes_table; +		$this->smilies_table = $smilies_table; +		$this->styles_table  = $styles_table; +		$this->words_table   = $words_table; + +		$this->styles_path = $styles_path; +	} + +	/** +	* Return the list of custom BBCodes +	* +	* @return array +	*/ +	public function get_bbcodes() +	{ +		$sql = 'SELECT bbcode_match, bbcode_tpl FROM ' . $this->bbcodes_table; +		$result = $this->db->sql_query($sql); +		$rows = $this->db->sql_fetchrowset($result); +		$this->db->sql_freeresult($result); + +		return $rows; +	} + +	/** +	* Return the list of smilies +	* +	* @return array +	*/ +	public function get_smilies() +	{ +		// NOTE: smilies that are displayed on the posting page are processed first because they're +		//       typically the most used smilies and it ends up producing a slightly more efficient +		//       renderer +		$sql = 'SELECT code, emotion, smiley_url, smiley_width, smiley_height +			FROM ' . $this->smilies_table . ' +			ORDER BY display_on_posting DESC'; +		$result = $this->db->sql_query($sql); +		$rows = $this->db->sql_fetchrowset($result); +		$this->db->sql_freeresult($result); + +		return $rows; +	} + +	/** +	* Return the list of installed styles +	* +	* @return array +	*/ +	protected function get_styles() +	{ +		$sql = 'SELECT style_id, style_path, style_parent_id, bbcode_bitfield FROM ' . $this->styles_table; +		$result = $this->db->sql_query($sql); +		$rows = $this->db->sql_fetchrowset($result); +		$this->db->sql_freeresult($result); + +		return $rows; +	} + +	/** +	* Return the bbcode.html template for every installed style +	* +	* @return array 2D array. style_id as keys, each element is an array with a "template" element that contains the style's bbcode.html and a "bbcodes" element that contains the name of each BBCode that is to be stylised +	*/ +	public function get_styles_templates() +	{ +		$templates = array(); + +		$bbcode_ids = array( +			'quote' => 0, +			'b'     => 1, +			'i'     => 2, +			'url'   => 3, +			'img'   => 4, +			'size'  => 5, +			'color' => 6, +			'u'     => 7, +			'code'  => 8, +			'list'  => 9, +			'*'     => 9, +			'email' => 10, +			'flash' => 11, +			'attachment' => 12, +		); + +		$styles = array(); +		foreach ($this->get_styles() as $row) +		{ +			$styles[$row['style_id']] = $row; +		} + +		foreach ($styles as $style_id => $style) +		{ +			$bbcodes = array(); + +			// Collect the name of the BBCodes whose bit is set in the style's bbcode_bitfield +			$template_bitfield = new \bitfield($style['bbcode_bitfield']); +			foreach ($bbcode_ids as $bbcode_name => $bit) +			{ +				if ($template_bitfield->get($bit)) +				{ +					$bbcodes[] = $bbcode_name; +				} +			} + +			$filename = $this->resolve_style_filename($styles, $style); +			if ($filename === false) +			{ +				// Ignore this style, it will use the default templates +				continue; +			} + +			$templates[$style_id] = array( +				'bbcodes'  => $bbcodes, +				'template' => file_get_contents($filename), +			); +		} + +		return $templates; +	} + +	/** +	* Resolve inheritance for given style and return the path to their bbcode.html file +	* +	* @param  array       $styles Associative array of [style_id => style] containing all styles +	* @param  array       $style  Style for which we resolve +	* @return string|bool         Path to this style's bbcode.html, or FALSE +	*/ +	protected function resolve_style_filename(array $styles, array $style) +	{ +		// Look for a bbcode.html in this style's dir +		$filename = $this->styles_path . $style['style_path'] . '/template/bbcode.html'; +		if (file_exists($filename)) +		{ +			return $filename; +		} + +		// Resolve using this style's parent +		$parent_id = $style['style_parent_id']; +		if ($parent_id && !empty($styles[$parent_id])) +		{ +			return $this->resolve_style_filename($styles, $styles[$parent_id]); +		} + +		return false; +	} + +	/** +	* Return the list of censored words +	* +	* @return array +	*/ +	public function get_censored_words() +	{ +		$sql = 'SELECT word, replacement FROM ' . $this->words_table; +		$result = $this->db->sql_query($sql); +		$rows = $this->db->sql_fetchrowset($result); +		$this->db->sql_freeresult($result); + +		return $rows; +	} +} diff --git a/phpBB/phpbb/textformatter/parser_interface.php b/phpBB/phpbb/textformatter/parser_interface.php new file mode 100644 index 0000000000..3cb9f8e977 --- /dev/null +++ b/phpBB/phpbb/textformatter/parser_interface.php @@ -0,0 +1,111 @@ +<?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\textformatter; + +interface parser_interface +{ +	/** +	* Parse given text +	* +	* @param  string $text +	* @return string +	*/ +	public function parse($text); + +	/** +	* Disable a specific BBCode +	* +	* @param  string $name BBCode name +	* @return null +	*/ +	public function disable_bbcode($name); + +	/** +	* Disable BBCodes in general +	*/ +	public function disable_bbcodes(); + +	/** +	* Disable the censor +	*/ +	public function disable_censor(); + +	/** +	* Disable magic URLs +	*/ +	public function disable_magic_url(); + +	/** +	* Disable smilies +	*/ +	public function disable_smilies(); + +	/** +	* Enable a specific BBCode +	* +	* @param  string $name BBCode name +	* @return null +	*/ +	public function enable_bbcode($name); + +	/** +	* Enable BBCodes in general +	*/ +	public function enable_bbcodes(); + +	/** +	* Enable the censor +	*/ +	public function enable_censor(); + +	/** +	* Enable magic URLs +	*/ +	public function enable_magic_url(); + +	/** +	* Enable smilies +	*/ +	public function enable_smilies(); + +	/** +	* Get the list of errors that were generated during last parsing +	* +	* @return array +	*/ +	public function get_errors(); + +	/** +	* Set a variable to be used by the parser +	* +	*  - max_font_size +	*  - max_img_height +	*  - max_img_width +	*  - max_smilies +	*  - max_urls +	* +	* @param  string $name +	* @param  mixed  $value +	* @return null +	*/ +	public function set_var($name, $value); + +	/** +	* Set multiple variables to be used by the parser +	* +	* @param  array $vars Associative array of [name => value] +	* @return null +	*/ +	public function set_vars(array $vars); +} diff --git a/phpBB/phpbb/textformatter/renderer_interface.php b/phpBB/phpbb/textformatter/renderer_interface.php new file mode 100644 index 0000000000..609b0bb642 --- /dev/null +++ b/phpBB/phpbb/textformatter/renderer_interface.php @@ -0,0 +1,92 @@ +<?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\textformatter; + +interface renderer_interface +{ +	/** +	* Render given text +	* +	* @param  string $text Text, as parsed by something that implements \phpbb\textformatter\parser +	* @return string +	*/ +	public function render($text); + +	/** +	* Set the smilies' path +	* +	* @return null +	*/ +	public function set_smilies_path($path); + +	/** +	* Return the value of the "viewcensors" option +	* +	* @return bool Option's value +	*/ +	public function get_viewcensors(); + +	/** +	* Return the value of the "viewflash" option +	* +	* @return bool Option's value +	*/ +	public function get_viewflash(); + +	/** +	* Return the value of the "viewimg" option +	* +	* @return bool Option's value +	*/ +	public function get_viewimg(); + +	/** +	* Return the value of the "viewsmilies" option +	* +	* @return bool Option's value +	*/ +	public function get_viewsmilies(); + +	/** +	* Set the "viewcensors" option +	* +	* @param  bool $value Option's value +	* @return null +	*/ +	public function set_viewcensors($value); + +	/** +	* Set the "viewflash" option +	* +	* @param  bool $value Option's value +	* @return null +	*/ +	public function set_viewflash($value); + +	/** +	* Set the "viewimg" option +	* +	* @param  bool $value Option's value +	* @return null +	*/ +	public function set_viewimg($value); + +	/** +	* Set the "viewsmilies" option +	* +	* @param  bool $value Option's value +	* @return null +	*/ +	public function set_viewsmilies($value); +} diff --git a/phpBB/phpbb/textformatter/s9e/factory.php b/phpBB/phpbb/textformatter/s9e/factory.php new file mode 100644 index 0000000000..9576abe1f0 --- /dev/null +++ b/phpBB/phpbb/textformatter/s9e/factory.php @@ -0,0 +1,545 @@ +<?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\textformatter\s9e; + +use s9e\TextFormatter\Configurator; +use s9e\TextFormatter\Configurator\Items\AttributeFilters\RegexpFilter; +use s9e\TextFormatter\Configurator\Items\UnsafeTemplate; + +/** +* Creates s9e\TextFormatter objects +*/ +class factory implements \phpbb\textformatter\cache_interface +{ +	/** +	* @var \phpbb\cache\driver\driver_interface +	*/ +	protected $cache; + +	/** +	* @var string Path to the cache dir +	*/ +	protected $cache_dir; + +	/** +	* @var string Cache key used for the parser +	*/ +	protected $cache_key_parser; + +	/** +	* @var string Cache key used for the renderer +	*/ +	protected $cache_key_renderer; + +	/** +	* @var array Custom tokens used in bbcode.html and their corresponding token from the definition +	*/ +	protected $custom_tokens = array( +		'email' => array('{DESCRIPTION}' => '{TEXT}'), +		'flash' => array('{WIDTH}' => '{NUMBER1}', '{HEIGHT}' => '{NUMBER2}'), +		'img'   => array('{URL}' => '{IMAGEURL}'), +		'list'  => array('{LIST_TYPE}' => '{HASHMAP}'), +		'quote' => array('{USERNAME}' => '{TEXT1}'), +		'size'  => array('{SIZE}' => '{FONTSIZE}'), +		'url'   => array('{DESCRIPTION}' => '{TEXT}'), +	); + +	/** +	* @var \phpbb\textformatter\data_access +	*/ +	protected $data_access; + +	/** +	* @var array Default BBCode definitions +	*/ +	protected $default_definitions = array( +		'attachment' => '[ATTACHMENT index={NUMBER} filename={TEXT;useContent}]', +		'b'     => '[B]{TEXT}[/B]', +		'code'  => '[CODE]{TEXT}[/CODE]', +		'color' => '[COLOR={COLOR}]{TEXT}[/COLOR]', +		'email' => '[EMAIL={EMAIL;useContent}]{TEXT}[/EMAIL]', +		'flash' => '[FLASH={NUMBER1},{NUMBER2} width={NUMBER1;postFilter=#flashwidth} height={NUMBER2;postFilter=#flashheight} url={URL;useContent} /]', +		'i'     => '[I]{TEXT}[/I]', +		'img'   => '[IMG src={IMAGEURL;useContent}]', +		'list'  => '[LIST type={HASHMAP=1:decimal,a:lower-alpha,A:upper-alpha,i:lower-roman,I:upper-roman;optional;postFilter=#simpletext}]{TEXT}[/LIST]', +		'li'    => '[* $tagName=LI]{TEXT}[/*]', +		'quote' => +			"[QUOTE +				author={TEXT1;optional} +				url={URL;optional} +				author={PARSE=/^\\[url=(?'url'.*?)](?'author'.*)\\[\\/url]$/i} +				author={PARSE=/^\\[url](?'author'(?'url'.*?))\\[\\/url]$/i} +				author={PARSE=/(?'url'https?:\\/\\/[^[\\]]+)/i} +			]{TEXT2}[/QUOTE]", +		'size'  => '[SIZE={FONTSIZE}]{TEXT}[/SIZE]', +		'u'     => '[U]{TEXT}[/U]', +		'url'   => '[URL={URL;useContent}]{TEXT}[/URL]', +	); + +	/** +	* @var array Default templates, taken from bbcode::bbcode_tpl() +	*/ +	protected $default_templates = array( +		'b'     => '<span style="font-weight: bold"><xsl:apply-templates/></span>', +		'i'     => '<span style="font-style: italic"><xsl:apply-templates/></span>', +		'u'     => '<span style="text-decoration: underline"><xsl:apply-templates/></span>', +		'img'   => '<img src="{IMAGEURL}" alt="{L_IMAGE}"/>', +		'size'  => '<span style="font-size: {FONTSIZE}%; line-height: normal"><xsl:apply-templates/></span>', +		'color' => '<span style="color: {COLOR}"><xsl:apply-templates/></span>', +		'email' => '<a href="mailto:{EMAIL}"><xsl:apply-templates/></a>', +	); + +	/** +	* @var \phpbb\event\dispatcher_interface +	*/ +	protected $dispatcher; + +	/** +	* Constructor +	* +	* @param \phpbb\textformatter\data_access $data_access +	* @param \phpbb\cache\driver\driver_interface $cache +	* @param \phpbb\event\dispatcher_interface $dispatcher +	* @param string $cache_dir          Path to the cache dir +	* @param string $cache_key_parser   Cache key used for the parser +	* @param string $cache_key_renderer Cache key used for the renderer +	*/ +	public function __construct(\phpbb\textformatter\data_access $data_access, \phpbb\cache\driver\driver_interface $cache, \phpbb\event\dispatcher_interface $dispatcher, $cache_dir, $cache_key_parser, $cache_key_renderer) +	{ +		$this->cache = $cache; +		$this->cache_dir = $cache_dir; +		$this->cache_key_parser = $cache_key_parser; +		$this->cache_key_renderer = $cache_key_renderer; +		$this->data_access = $data_access; +		$this->dispatcher = $dispatcher; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function invalidate() +	{ +		$this->regenerate(); +	} + +	/** +	* {@inheritdoc} +	* +	* Will remove old renderers from the cache dir but won't touch the current renderer +	*/ +	public function tidy() +	{ +		// Get the name of current renderer +		$renderer_data = $this->cache->get($this->cache_key_renderer); +		$renderer_file = ($renderer_data) ? $renderer_data['class'] . '.php' : null; + +		foreach (glob($this->cache_dir . 's9e_*') as $filename) +		{ +			// Only remove the file if it's not the current renderer +			if (!$renderer_file || substr($filename, -strlen($renderer_file)) !== $renderer_file) +			{ +				unlink($filename); +			} +		} +	} + +	/** +	* Generate and return a new configured instance of s9e\TextFormatter\Configurator +	* +	* @return Configurator +	*/ +	public function get_configurator() +	{ +		// Create a new Configurator +		$configurator = new Configurator; + +		/** +		* Modify the s9e\TextFormatter configurator before the default settings are set +		* +		* @event core.text_formatter_s9e_configure_before +		* @var \s9e\TextFormatter\Configurator configurator Configurator instance +		* @since 3.2.0-a1 +		*/ +		$vars = array('configurator'); +		extract($this->dispatcher->trigger_event('core.text_formatter_s9e_configure_before', compact($vars))); + +		// Convert newlines to br elements by default +		$configurator->rootRules->enableAutoLineBreaks(); + +		// Don't automatically ignore text in places where text is not allowed +		$configurator->rulesGenerator->remove('IgnoreTextIfDisallowed'); + +		// Don't remove comments and instead convert them to xsl:comment elements +		$configurator->templateNormalizer->remove('RemoveComments'); +		$configurator->templateNormalizer->add('TransposeComments'); + +		// Set the rendering engine and configure it to save to the cache dir +		$configurator->rendering->engine = 'PHP'; +		$configurator->rendering->engine->cacheDir = $this->cache_dir; +		$configurator->rendering->engine->defaultClassPrefix = 's9e_renderer_'; +		$configurator->rendering->engine->enableQuickRenderer = true; + +		// Create custom filters for BBCode tokens that are supported in phpBB but not in +		// s9e\TextFormatter +		$filter = new RegexpFilter('#^' . get_preg_expression('relative_url') . '$#Du'); +		$configurator->attributeFilters->add('#local_url', $filter); +		$configurator->attributeFilters->add('#relative_url', $filter); + +		// INTTEXT regexp from acp_bbcodes +		$filter = new RegexpFilter('!^([\p{L}\p{N}\-+,_. ]+)$!Du'); +		$configurator->attributeFilters->add('#inttext', $filter); + +		// Create custom filters for Flash restrictions, which use the same values as the image +		// restrictions but have their own error message +		$configurator->attributeFilters +			->add('#flashheight', __NAMESPACE__ . '\\parser::filter_flash_height') +			->addParameterByName('max_img_height') +			->addParameterByName('logger'); + +		$configurator->attributeFilters +			->add('#flashwidth', __NAMESPACE__ . '\\parser::filter_flash_width') +			->addParameterByName('max_img_width') +			->addParameterByName('logger'); + +		// Create a custom filter for phpBB's per-mode font size limits +		$configurator->attributeFilters +			->add('#fontsize', __NAMESPACE__ . '\\parser::filter_font_size') +			->addParameterByName('max_font_size') +			->addParameterByName('logger') +			->markAsSafeInCSS(); + +		// Create a custom filter for image URLs +		$configurator->attributeFilters +			->add('#imageurl', __NAMESPACE__ . '\\parser::filter_img_url') +			->addParameterByName('urlConfig') +			->addParameterByName('logger') +			->addParameterByName('max_img_height') +			->addParameterByName('max_img_width') +			->markAsSafeAsURL(); + +		// Add default BBCodes +		foreach ($this->get_default_bbcodes($configurator) as $bbcode) +		{ +			$configurator->BBCodes->addCustom($bbcode['usage'], $bbcode['template']); +		} + +		// Modify the template to disable images/flash depending on user's settings +		foreach (array('FLASH', 'IMG') as $name) +		{ +			$tag = $configurator->tags[$name]; +			$tag->template = '<xsl:choose><xsl:when test="$S_VIEW' . $name . '">' . $tag->template . '</xsl:when><xsl:otherwise><xsl:apply-templates/></xsl:otherwise></xsl:choose>'; +		} + +		// Load custom BBCodes +		foreach ($this->data_access->get_bbcodes() as $row) +		{ +			// Insert the board's URL before {LOCAL_URL} tokens +			$tpl = preg_replace_callback( +				'#\\{LOCAL_URL\\d*\\}#', +				function ($m) +				{ +					return generate_board_url() . '/' . $m[0]; +				}, +				$row['bbcode_tpl'] +			); + +			try +			{ +				$configurator->BBCodes->addCustom($row['bbcode_match'], new UnsafeTemplate($tpl)); +			} +			catch (\Exception $e) +			{ +				/** +				* @todo log an error? +				*/ +			} +		} + +		// Load smilies +		foreach ($this->data_access->get_smilies() as $row) +		{ +			$configurator->Emoticons->add( +				$row['code'], +				'<img class="smilies" src="{$T_SMILIES_PATH}/' . htmlspecialchars($row['smiley_url']) . '" alt="{.}" title="' . htmlspecialchars($row['emotion']) . '"/>' +			); +		} + +		if (isset($configurator->Emoticons)) +		{ +			// Force emoticons to be rendered as text if $S_VIEWSMILIES is not set +			$configurator->Emoticons->notIfCondition = 'not($S_VIEWSMILIES)'; + +			// Only parse emoticons at the beginning of the text or if they're preceded by any +			// one of: a new line, a space, a dot, or a right square bracket +			$configurator->Emoticons->notAfter = '[^\\n .\\]]'; +		} + +		// Load the censored words +		$censor = $this->data_access->get_censored_words(); +		if (!empty($censor)) +		{ +			// Use a namespaced tag to avoid collisions +			$configurator->plugins->load('Censor', array('tagName' => 'censor:tag')); +			foreach ($censor as $row) +			{ +				// NOTE: words are stored as HTML, we need to decode them to plain text +				$configurator->Censor->add(htmlspecialchars_decode($row['word']),  htmlspecialchars_decode($row['replacement'])); +			} +		} + +		// Load the magic links plugins. We do that after BBCodes so that they use the same tags +		$configurator->plugins->load('Autoemail'); +		$configurator->plugins->load('Autolink', array('matchWww' => true)); + +		// Register some vars with a default value. Those should be set at runtime by whatever calls +		// the parser +		$configurator->registeredVars['max_font_size'] = 0; +		$configurator->registeredVars['max_img_height'] = 0; +		$configurator->registeredVars['max_img_width'] = 0; + +		/** +		* Modify the s9e\TextFormatter configurator after the default settings are set +		* +		* @event core.text_formatter_s9e_configure_after +		* @var \s9e\TextFormatter\Configurator configurator Configurator instance +		* @since 3.2.0-a1 +		*/ +		$vars = array('configurator'); +		extract($this->dispatcher->trigger_event('core.text_formatter_s9e_configure_after', compact($vars))); + +		return $configurator; +	} + +	/** +	* Regenerate and cache a new parser and renderer +	* +	* @return array Associative array with at least two elements: "parser" and "renderer" +	*/ +	public function regenerate() +	{ +		$configurator = $this->get_configurator(); + +		// Get the censor helper and remove the Censor plugin if applicable +		if (isset($configurator->Censor)) +		{ +			$censor = $configurator->Censor->getHelper(); +			unset($configurator->Censor); +			unset($configurator->tags['censor:tag']); +		} + +		$objects  = $configurator->finalize(); +		$parser   = $objects['parser']; +		$renderer = $objects['renderer']; + +		// Cache the parser as-is +		$this->cache->put($this->cache_key_parser, $parser); + +		// We need to cache the name of the renderer's generated class +		$renderer_data = array('class' => get_class($renderer)); +		if (isset($censor)) +		{ +			$renderer_data['censor'] = $censor; +		} +		$this->cache->put($this->cache_key_renderer, $renderer_data); + +		return array('parser' => $parser, 'renderer' => $renderer); +	} + +	/** +	* Return the default BBCodes configuration +	* +	* @return array 2D array. Each element has a 'usage' key, a 'template' key, and an optional 'options' key +	*/ +	protected function get_default_bbcodes($configurator) +	{ +		// For each BBCode, build an associative array matching style_ids to their template +		$templates = array(); +		foreach ($this->data_access->get_styles_templates() as $style_id => $data) +		{ +			foreach ($this->extract_templates($data['template']) as $bbcode_name => $template) +			{ +				$templates[$bbcode_name][$style_id] = $template; +			} + +			// Add default templates wherever missing, or for BBCodes that were not specified in +			// this template's bitfield. For instance, prosilver has a custom template for b but its +			// bitfield does not enable it so the default template is used instead +			foreach ($this->default_templates as $bbcode_name => $template) +			{ +				if (!isset($templates[$bbcode_name][$style_id]) || !in_array($bbcode_name, $data['bbcodes'], true)) +				{ +					$templates[$bbcode_name][$style_id] = $template; +				} +			} +		} + +		// Replace custom tokens and normalize templates +		foreach ($templates as $bbcode_name => $style_templates) +		{ +			foreach ($style_templates as $i => $template) +			{ +				if (isset($this->custom_tokens[$bbcode_name])) +				{ +					$template = strtr($template, $this->custom_tokens[$bbcode_name]); +				} + +				$templates[$bbcode_name][$i] = $configurator->templateNormalizer->normalizeTemplate($template); +			} +		} + +		$bbcodes = array(); +		foreach ($this->default_definitions as $bbcode_name => $usage) +		{ +			$bbcodes[$bbcode_name] = array( +				'usage'    => $usage, +				'template' => $this->merge_templates($templates[$bbcode_name]), +			); +		} + +		return $bbcodes; +	} + +	/** +	* Extract and recompose individual BBCode templates from a style's template file +	* +	* @param  string $template Style template (bbcode.html) +	* @return array Associative array matching BBCode names to their template +	*/ +	protected function extract_templates($template) +	{ +		// Capture the template fragments +		preg_match_all('#<!-- BEGIN (.*?) -->(.*?)<!-- END .*? -->#s', $template, $matches, PREG_SET_ORDER); + +		$fragments = array(); +		foreach ($matches as $match) +		{ +			// Normalize the whitespace +			$fragment = preg_replace('#>\\n\\t*<#', '><', trim($match[2])); + +			$fragments[$match[1]] = $fragment; +		} + +		// Automatically recompose templates split between *_open and *_close +		foreach ($fragments as $fragment_name => $fragment) +		{ +			if (preg_match('#^(\\w+)_close$#', $fragment_name, $match)) +			{ +				$bbcode_name = $match[1]; + +				if (isset($fragments[$bbcode_name . '_open'])) +				{ +					$templates[$bbcode_name] = $fragments[$bbcode_name . '_open'] . '<xsl:apply-templates/>' . $fragment; +				} +			} +		} + +		// Manually recompose and overwrite irregular templates +		$templates['list'] = +			'<xsl:choose> +				<xsl:when test="not(@type)"> +					' . $fragments['ulist_open_default'] . '<xsl:apply-templates/>' . $fragments['ulist_close'] . ' +				</xsl:when> +				<xsl:when test="contains(\'upperlowerdecim\',substring(@type,1,5))"> +					' . $fragments['olist_open'] . '<xsl:apply-templates/>' . $fragments['olist_close'] . ' +				</xsl:when> +				<xsl:otherwise> +					' . $fragments['ulist_open'] . '<xsl:apply-templates/>' . $fragments['ulist_close'] . ' +				</xsl:otherwise> +			</xsl:choose>'; + +		$templates['li'] = $fragments['listitem'] . '<xsl:apply-templates/>' . $fragments['listitem_close']; + +		$fragments['quote_username_open'] = str_replace( +			'{USERNAME}', +			'<xsl:choose> +				<xsl:when test="@url">' . str_replace('{DESCRIPTION}', '{USERNAME}', $fragments['url']) . '</xsl:when> +				<xsl:otherwise>{USERNAME}</xsl:otherwise> +			</xsl:choose>', +			$fragments['quote_username_open'] +		); + +		$templates['quote'] = +			'<xsl:choose> +				<xsl:when test="@author"> +					' . $fragments['quote_username_open'] . '<xsl:apply-templates/>' . $fragments['quote_close'] . ' +				</xsl:when> +				<xsl:otherwise> +					' . $fragments['quote_open'] . '<xsl:apply-templates/>' . $fragments['quote_close'] . ' +				</xsl:otherwise> +			</xsl:choose>'; + +		// The [attachment] BBCode uses the inline_attachment template to output a comment that +		// is post-processed by parse_attachments() +		$templates['attachment'] = $fragments['inline_attachment_open'] . '<xsl:comment> ia<xsl:value-of select="@index"/> </xsl:comment><xsl:value-of select="@filename"/><xsl:comment> ia<xsl:value-of select="@index"/> </xsl:comment>' . $fragments['inline_attachment_close']; + +		// Add fragments as templates +		foreach ($fragments as $fragment_name => $fragment) +		{ +			if (preg_match('#^\\w+$#', $fragment_name)) +			{ +				$templates[$fragment_name] = $fragment; +			} +		} + +		// Keep only templates that are named after an existing BBCode +		$templates = array_intersect_key($templates, $this->default_definitions); + +		return $templates; +	} + +	/** +	* Merge the templates from any number of styles into one BBCode template +	* +	* When multiple templates are available for the same BBCode (because of multiple styles) we +	* merge them into a single template that uses an xsl:choose construct that determines which +	* style to use at rendering time. +	* +	* @param  array  $style_templates Associative array matching style_ids to their template +	* @return string +	*/ +	protected function merge_templates(array $style_templates) +	{ +		// Return the template as-is if there's only one style or all styles share the same template +		if (count(array_unique($style_templates)) === 1) +		{ +			return end($style_templates); +		} + +		// Group identical templates together +		$grouped_templates = array(); +		foreach ($style_templates as $style_id => $style_template) +		{ +			$grouped_templates[$style_template][] = '$STYLE_ID=' . $style_id; +		} + +		// Sort templates by frequency descending +		$templates_cnt = array_map('sizeof', $grouped_templates); +		array_multisort($grouped_templates, $templates_cnt); + +		// Remove the most frequent template from the list; It becomes the default +		reset($grouped_templates); +		$default_template = key($grouped_templates); +		unset($grouped_templates[$default_template]); + +		// Build an xsl:choose switch +		$template = '<xsl:choose>'; +		foreach ($grouped_templates as $style_template => $exprs) +		{ +			$template .= '<xsl:when test="' . implode(' or ', $exprs) . '">' . $style_template . '</xsl:when>'; +		} +		$template .= '<xsl:otherwise>' . $default_template . '</xsl:otherwise></xsl:choose>'; + +		return $template; +	} +} diff --git a/phpBB/phpbb/textformatter/s9e/parser.php b/phpBB/phpbb/textformatter/s9e/parser.php new file mode 100644 index 0000000000..e46a0578d2 --- /dev/null +++ b/phpBB/phpbb/textformatter/s9e/parser.php @@ -0,0 +1,400 @@ +<?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\textformatter\s9e; + +use s9e\TextFormatter\Parser\BuiltInFilters; +use s9e\TextFormatter\Parser\Logger; + +/** +* s9e\TextFormatter\Parser adapter +*/ +class parser implements \phpbb\textformatter\parser_interface +{ +	/** +	* @var \phpbb\event\dispatcher_interface +	*/ +	protected $dispatcher; + +	/** +	* @var \s9e\TextFormatter\Parser +	*/ +	protected $parser; + +	/** +	* @var \phpbb\user User object, used for translating errors +	*/ +	protected $user; + +	/** +	* Constructor +	* +	* @param \phpbb\cache\driver_interface $cache +	* @param string $key Cache key +	* @param \phpbb\user $user +	* @param factory $factory +	* @param \phpbb\event\dispatcher_interface $dispatcher +	*/ +	public function __construct(\phpbb\cache\driver\driver_interface $cache, $key, \phpbb\user $user, factory $factory, \phpbb\event\dispatcher_interface $dispatcher) +	{ +		$parser = $cache->get($key); +		if (!$parser) +		{ +			$objects = $factory->regenerate(); +			$parser  = $objects['parser']; +		} + +		$this->dispatcher = $dispatcher; +		$this->parser = $parser; +		$this->user = $user; +		$parser = $this; + +		/** +		* Configure the parser service +		* +		* Can be used to: +		*  - toggle features according to the user's preferences, +		*  - toggle BBCodes according to the user's permissions, +		*  - register variables or custom parsers in the s9e\TextFormatter +		*  - configure the s9e\TextFormatter parser +		* +		* @event core.text_formatter_s9e_parser_setup +		* @var \phpbb\textformatter\s9e\parser parser This parser service +		* @var \phpbb\user user Current user +		* @since 3.2.0-a1 +		*/ +		$vars = array('parser', 'user'); +		extract($dispatcher->trigger_event('core.text_formatter_s9e_parser_setup', compact($vars))); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function parse($text) +	{ +		$parser = $this; + +		/** +		* Modify a text before it is parsed +		* +		* @event core.text_formatter_s9e_parse_before +		* @var \phpbb\textformatter\s9e\parser parser This parser service +		* @var string text The original text +		* @since 3.2.0-a1 +		*/ +		$vars = array('parser', 'text'); +		extract($this->dispatcher->trigger_event('core.text_formatter_s9e_parse_before', compact($vars))); + +		$xml = $this->parser->parse($text); + +		/** +		* Modify a parsed text in its XML form +		* +		* @event core.text_formatter_s9e_parse_after +		* @var \phpbb\textformatter\s9e\parser parser This parser service +		* @var string xml The parsed text, in XML +		* @since 3.2.0-a1 +		*/ +		$vars = array('parser', 'xml'); +		extract($this->dispatcher->trigger_event('core.text_formatter_s9e_parse_after', compact($vars))); + +		return $xml; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function disable_bbcode($name) +	{ +		$this->parser->disableTag(strtoupper($name)); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function disable_bbcodes() +	{ +		$this->parser->disablePlugin('BBCodes'); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function disable_censor() +	{ +		$this->parser->disablePlugin('Censor'); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function disable_magic_url() +	{ +		$this->parser->disablePlugin('Autoemail'); +		$this->parser->disablePlugin('Autolink'); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function disable_smilies() +	{ +		$this->parser->disablePlugin('Emoticons'); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function enable_bbcode($name) +	{ +		$this->parser->enableTag(strtoupper($name)); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function enable_bbcodes() +	{ +		$this->parser->enablePlugin('BBCodes'); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function enable_censor() +	{ +		$this->parser->enablePlugin('Censor'); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function enable_magic_url() +	{ +		$this->parser->enablePlugin('Autoemail'); +		$this->parser->enablePlugin('Autolink'); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function enable_smilies() +	{ +		$this->parser->enablePlugin('Emoticons'); +	} + +	/** +	* {@inheritdoc} +	* +	* This will translate the log entries found in s9e\TextFormatter's logger into phpBB error +	* messages +	*/ +	public function get_errors() +	{ +		$errors = array(); + +		foreach ($this->parser->getLogger()->get() as $entry) +		{ +			list($type, $msg, $context) = $entry; + +			if ($msg === 'Tag limit exceeded') +			{ +				if ($context['tagName'] === 'E') +				{ +					$errors[] = $this->user->lang('TOO_MANY_SMILIES', $context['tagLimit']); +				} +				else if ($context['tagName'] === 'URL') +				{ +					$errors[] = $this->user->lang('TOO_MANY_URLS', $context['tagLimit']); +				} +			} +			else if ($msg === 'MAX_FONT_SIZE_EXCEEDED') +			{ +				$errors[] = $this->user->lang($msg, $context['max_size']); +			} +			else if (preg_match('/^MAX_(?:FLASH|IMG)_(HEIGHT|WIDTH)_EXCEEDED$/D', $msg, $m)) +			{ +				$errors[] = $this->user->lang($msg, $context['max_' . strtolower($m[1])]); +			} +			else if ($msg === 'Tag is disabled') +			{ +				$name = strtolower($context['tag']->getName()); +				$errors[] = $this->user->lang('UNAUTHORISED_BBCODE', '[' . $name . ']'); +			} +			else if ($msg === 'UNABLE_GET_IMAGE_SIZE') +			{ +				$errors[] = $this->user->lang($msg); +			} +		} + +		return array_unique($errors); +	} + +	/** +	* Return the instance of s9e\TextFormatter\Parser used by this object +	* +	* @return \s9e\TextFormatter\Parser +	*/ +	public function get_parser() +	{ +		return $this->parser; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function set_var($name, $value) +	{ +		if ($name === 'max_smilies') +		{ +			$this->parser->setTagLimit('E', $value ?: PHP_INT_MAX); +		} +		else if ($name === 'max_urls') +		{ +			$this->parser->setTagLimit('URL', $value ?: PHP_INT_MAX); +		} +		else +		{ +			$this->parser->registeredVars[$name] = $value; +		} +	} + +	/** +	* {@inheritdoc} +	*/ +	public function set_vars(array $vars) +	{ +		foreach ($vars as $name => $value) +		{ +			$this->set_var($name, $value); +		} +	} + +	/** +	* Filter a flash object's height +	* +	* @see bbcode_firstpass::bbcode_flash() +	* +	* @param  string  $height +	* @param  integer $max_height +	* @param  Logger  $logger +	* @return mixed              Original value if valid, FALSE otherwise +	*/ +	static public function filter_flash_height($height, $max_height, Logger $logger) +	{ +		if ($max_height && $height > $max_height) +		{ +			$logger->err('MAX_FLASH_HEIGHT_EXCEEDED', array('max_height' => $max_height)); + +			return false; +		} + +		return $height; +	} + +	/** +	* Filter a flash object's width +	* +	* @see bbcode_firstpass::bbcode_flash() +	* +	* @param  string  $width +	* @param  integer $max_width +	* @param  Logger  $logger +	* @return mixed              Original value if valid, FALSE otherwise +	*/ +	static public function filter_flash_width($width, $max_width, Logger $logger) +	{ +		if ($max_width && $width > $max_width) +		{ +			$logger->err('MAX_FLASH_WIDTH_EXCEEDED', array('max_width' => $max_width)); + +			return false; +		} + +		return $width; +	} + +	/** +	* Filter the value used in a [size] BBCode +	* +	* @see bbcode_firstpass::bbcode_size() +	* +	* @param  string  $size     Original size +	* @param  integer $max_size Maximum allowed size +	* @param  Logger  $logger +	* @return mixed             Original value if valid, FALSE otherwise +	*/ +	static public function filter_font_size($size, $max_size, Logger $logger) +	{ +		if ($max_size && $size > $max_size) +		{ +			$logger->err('MAX_FONT_SIZE_EXCEEDED', array('max_size' => $max_size)); + +			return false; +		} + +		if ($size < 1) +		{ +			return false; +		} + +		return $size; +	} + +	/** +	* Filter an image's URL to enforce restrictions on its dimensions +	* +	* @see bbcode_firstpass::bbcode_img() +	* +	* @param  string  $url        Original URL +	* @param  array   $url_config Config used by the URL filter +	* @param  Logger  $logger +	* @param  integer $max_height Maximum height allowed +	* @param  integer $max_width  Maximum width allowed +	* @return string|bool         Original value if valid, FALSE otherwise +	*/ +	static public function filter_img_url($url, array $url_config, Logger $logger, $max_height, $max_width) +	{ +		// Validate the URL +		$url = BuiltInFilters::filterUrl($url, $url_config, $logger); +		if ($url === false) +		{ +			return false; +		} + +		if ($max_height || $max_width) +		{ +			$imagesize = new \fastImageSize\fastImageSize(); +			$size_info = $imagesize->getImageSize($url); +			if ($size_info === false) +			{ +				$logger->err('UNABLE_GET_IMAGE_SIZE'); +				return false; +			} + +			if ($max_height && $max_height < $size_info['height']) +			{ +				$logger->err('MAX_IMG_HEIGHT_EXCEEDED', array('max_height' => $max_height)); +				return false; +			} + +			if ($max_width && $max_width < $size_info['width']) +			{ +				$logger->err('MAX_IMG_WIDTH_EXCEEDED', array('max_width' => $max_width)); +				return false; +			} +		} + +		return $url; +	} +} diff --git a/phpBB/phpbb/textformatter/s9e/renderer.php b/phpBB/phpbb/textformatter/s9e/renderer.php new file mode 100644 index 0000000000..8999f1d25f --- /dev/null +++ b/phpBB/phpbb/textformatter/s9e/renderer.php @@ -0,0 +1,338 @@ +<?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\textformatter\s9e; + +/** +* s9e\TextFormatter\Renderer adapter +*/ +class renderer implements \phpbb\textformatter\renderer_interface +{ +	/** +	* @var \s9e\TextFormatter\Plugins\Censor\Helper +	*/ +	protected $censor; + +	/** +	* @var \phpbb\event\dispatcher_interface +	*/ +	protected $dispatcher; + +	/** +	* @var \s9e\TextFormatter\Renderer +	*/ +	protected $renderer; + +	/** +	* @var bool Status of the viewcensors option +	*/ +	protected $viewcensors = false; + +	/** +	* @var bool Status of the viewflash option +	*/ +	protected $viewflash = false; + +	/** +	* @var bool Status of the viewimg option +	*/ +	protected $viewimg = false; + +	/** +	* @var bool Status of the viewsmilies option +	*/ +	protected $viewsmilies = false; + +	/** +	* Constructor +	* +	* @param \phpbb\cache\driver\driver_interface $cache +	* @param string $cache_dir Path to the cache dir +	* @param string $key Cache key +	* @param factory $factory +	* @param \phpbb\event\dispatcher_interface $dispatcher +	*/ +	public function __construct(\phpbb\cache\driver\driver_interface $cache, $cache_dir, $key, factory $factory, \phpbb\event\dispatcher_interface $dispatcher) +	{ +		$renderer_data = $cache->get($key); +		if ($renderer_data) +		{ +			$class = $renderer_data['class']; +			if (!class_exists($class, false)) +			{ +				// Try to load the renderer class from its cache file +				$cache_file = $cache_dir . $class . '.php'; + +				if (file_exists($cache_file)) +				{ +					include($cache_file); +				} +			} +			if (class_exists($class, false)) +			{ +				$renderer = new $class; +			} +			if (isset($renderer_data['censor'])) +			{ +				$censor = $renderer_data['censor']; +			} +		} +		if (!isset($renderer)) +		{ +			$objects  = $factory->regenerate(); +			$renderer = $objects['renderer']; +		} + +		if (isset($censor)) +		{ +			$this->censor = $censor; +		} +		$this->dispatcher = $dispatcher; +		$this->renderer = $renderer; +		$renderer = $this; + +		/** +		* Configure the renderer service +		* +		* @event core.text_formatter_s9e_renderer_setup +		* @var \phpbb\textformatter\s9e\renderer renderer This renderer service +		* @since 3.2.0-a1 +		*/ +		$vars = array('renderer'); +		extract($dispatcher->trigger_event('core.text_formatter_s9e_renderer_setup', compact($vars))); +	} + +	/** +	* Automatically set the smilies path based on config +	* +	* @param  \phpbb\config\config $config +	* @param  \phpbb\path_helper   $path_helper +	* @return null +	*/ +	public function configure_smilies_path(\phpbb\config\config $config, \phpbb\path_helper $path_helper) +	{ +		/** +		* @see smiley_text() +		*/ +		$root_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? generate_board_url() . '/' : $path_helper->get_web_root_path(); + +		$this->set_smilies_path($root_path . $config['smilies_path']); +	} + +	/** +	* Configure this renderer as per the user's settings +	* +	* Should set the locale as well as the viewcensor/viewflash/viewimg/viewsmilies options. +	* +	* @param  \phpbb\user          $user +	* @param  \phpbb\config\config $config +	* @param  \phpbb\auth\auth     $auth +	* @return null +	*/ +	public function configure_user(\phpbb\user $user, \phpbb\config\config $config, \phpbb\auth\auth $auth) +	{ +		$censor = $user->optionget('viewcensors') || !$config['allow_nocensors'] || !$auth->acl_get('u_chgcensors'); + +		$this->set_viewcensors($censor); +		$this->set_viewflash($user->optionget('viewflash')); +		$this->set_viewimg($user->optionget('viewimg')); +		$this->set_viewsmilies($user->optionget('viewsmilies')); + +		// Set the stylesheet parameters +		foreach (array_keys($this->renderer->getParameters()) as $param_name) +		{ +			if (strpos($param_name, 'L_') === 0) +			{ +				// L_FOO is set to $user->lang('FOO') +				$this->renderer->setParameter($param_name, $user->lang(substr($param_name, 2))); +			} +		} + +		// Set this user's style id and other parameters +		$this->renderer->setParameters(array( +			'S_IS_BOT'          => $user->data['is_bot'], +			'S_REGISTERED_USER' => $user->data['is_registered'], +			'S_USER_LOGGED_IN'  => ($user->data['user_id'] != ANONYMOUS), +			'STYLE_ID'          => $user->style['style_id'], +		)); +	} + +	/** +	* Return the instance of s9e\TextFormatter\Renderer used by this object +	* +	* @return \s9e\TextFormatter\Renderer +	*/ +	public function get_renderer() +	{ +		return $this->renderer; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function get_viewcensors() +	{ +		return $this->viewcensors; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function get_viewflash() +	{ +		return $this->viewflash; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function get_viewimg() +	{ +		return $this->viewimg; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function get_viewsmilies() +	{ +		return $this->viewsmilies; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function render($xml) +	{ +		$renderer = $this; + +		/** +		* Modify a parsed text before it is rendered +		* +		* @event core.text_formatter_s9e_render_before +		* @var \phpbb\textformatter\s9e\renderer renderer This renderer service +		* @var string xml The parsed text, in its XML form +		* @since 3.2.0-a1 +		*/ +		$vars = array('renderer', 'xml'); +		extract($this->dispatcher->trigger_event('core.text_formatter_s9e_render_before', compact($vars))); + +		if (isset($this->censor) && $this->viewcensors) +		{ +			// NOTE: censorHtml() is XML-safe +			$xml = $this->censor->censorHtml($xml, true); +		} + +		$html = $this->renderer->render($xml); +		if (stripos($html, '<code') !== false) +		{ +			$html = $this->replace_tabs_in_code($html); +		} + +		/** +		* Modify a rendered text +		* +		* @event core.text_formatter_s9e_render_after +		* @var string html The rendered text's HTML +		* @var \phpbb\textformatter\s9e\renderer renderer This renderer service +		* @since 3.2.0-a1 +		*/ +		$vars = array('html', 'renderer'); +		extract($this->dispatcher->trigger_event('core.text_formatter_s9e_render_after', compact($vars))); + +		return $html; +	} + +	/** +	* Replace tabs in code elements +	* +	* @see bbcode::bbcode_second_pass_code() +	* +	* @param  string $html Original HTML +	* @return string       Modified HTML +	*/ +	protected function replace_tabs_in_code($html) +	{ +		return preg_replace_callback( +			'((<code[^>]*>)(.*?)(</code>))is', +			function ($captures) +			{ +				$code = $captures[2]; + +				$code = str_replace("\t", '   ', $code); +				$code = str_replace('  ', '  ', $code); +				$code = str_replace('  ', '  ', $code); +				$code = str_replace("\n ", "\n ", $code); + +				// keep space at the beginning +				if (!empty($code) && $code[0] == ' ') +				{ +					$code = ' ' . substr($code, 1); +				} + +				// remove newline at the beginning +				if (!empty($code) && $code[0] == "\n") +				{ +					$code = substr($code, 1); +				} + +				return $captures[1] . $code . $captures[3]; +			}, +			$html +		); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function set_smilies_path($path) +	{ +		$this->renderer->setParameter('T_SMILIES_PATH', $path); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function set_viewcensors($value) +	{ +		$this->viewcensors = $value; +		$this->renderer->setParameter('S_VIEWCENSORS', $value); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function set_viewflash($value) +	{ +		$this->viewflash = $value; +		$this->renderer->setParameter('S_VIEWFLASH', $value); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function set_viewimg($value) +	{ +		$this->viewimg = $value; +		$this->renderer->setParameter('S_VIEWIMG', $value); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function set_viewsmilies($value) +	{ +		$this->viewsmilies = $value; +		$this->renderer->setParameter('S_VIEWSMILIES', $value); +	} +} diff --git a/phpBB/phpbb/textformatter/s9e/utils.php b/phpBB/phpbb/textformatter/s9e/utils.php new file mode 100644 index 0000000000..2018bbf519 --- /dev/null +++ b/phpBB/phpbb/textformatter/s9e/utils.php @@ -0,0 +1,60 @@ +<?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\textformatter\s9e; + +/** +* Text manipulation utilities +*/ +class utils implements \phpbb\textformatter\utils_interface +{ +	/** +	* Replace BBCodes and other formatting elements with whitespace +	* +	* NOTE: preserves smilies as text +	* +	* @param  string $xml Parsed text +	* @return string      Plain text +	*/ +	public function clean_formatting($xml) +	{ +		// Insert a space before <s> and <e> then remove formatting +		$xml = preg_replace('#<[es]>#', ' $0', $xml); + +		return \s9e\TextFormatter\Utils::removeFormatting($xml); +	} + +	/** +	* Remove given BBCode and its content, at given nesting depth +	* +	* @param  string  $xml         Parsed text +	* @param  string  $bbcode_name BBCode's name +	* @param  integer $depth       Minimum nesting depth (number of parents of the same name) +	* @return string               Parsed text +	*/ +	public function remove_bbcode($xml, $bbcode_name, $depth = 0) +	{ +		return \s9e\TextFormatter\Utils::removeTag($xml, strtoupper($bbcode_name), $depth); +	} + +	/** +	* Return a parsed text to its original form +	* +	* @param  string $xml Parsed text +	* @return string      Original plain text +	*/ +	public function unparse($xml) +	{ +		return \s9e\TextFormatter\Unparser::unparse($xml); +	} +} diff --git a/phpBB/phpbb/textformatter/utils_interface.php b/phpBB/phpbb/textformatter/utils_interface.php new file mode 100644 index 0000000000..132dc8ece4 --- /dev/null +++ b/phpBB/phpbb/textformatter/utils_interface.php @@ -0,0 +1,48 @@ +<?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\textformatter; + +/** +* Used to manipulate a parsed text +*/ +interface utils_interface +{ +	/** +	* Replace BBCodes and other formatting elements with whitespace +	* +	* NOTE: preserves smilies as text +	* +	* @param  string $text Parsed text +	* @return string       Plain text +	*/ +	public function clean_formatting($text); + +	/** +	* Remove given BBCode and its content, at given nesting depth +	* +	* @param  string  $text        Parsed text +	* @param  string  $bbcode_name BBCode's name +	* @param  integer $depth       Minimum nesting depth (number of parents of the same name) +	* @return string               Parsed text +	*/ +	public function remove_bbcode($text, $bbcode_name, $depth = 0); + +	/** +	* Return a parsed text to its original form +	* +	* @param  string $text Parsed text +	* @return string       Original plain text +	*/ +	public function unparse($text); +} diff --git a/phpBB/phpbb/user.php b/phpBB/phpbb/user.php index 882e9cef26..c33070d6f4 100644 --- a/phpBB/phpbb/user.php +++ b/phpBB/phpbb/user.php @@ -21,8 +21,11 @@ namespace phpbb;  */  class user extends \phpbb\session  { -	var $lang = array(); -	var $help = array(); +	/** +	 * @var \phpbb\language\language +	 */ +	protected $language; +  	var $style = array();  	var $date_format; @@ -42,35 +45,63 @@ class user extends \phpbb\session  	var $img_lang;  	var $img_array = array(); +	/** @var bool */ +	protected $is_setup_flag; +  	// Able to add new options (up to id 31)  	var $keyoptions = array('viewimg' => 0, 'viewflash' => 1, 'viewsmilies' => 2, 'viewsigs' => 3, 'viewavatars' => 4, 'viewcensors' => 5, 'attachsig' => 6, 'bbcode' => 8, 'smilies' => 9, 'sig_bbcode' => 15, 'sig_smilies' => 16, 'sig_links' => 17);  	/**  	* Constructor to set the lang path +	*  	* @param string $datetime_class Class name of datetime class +	* @param \phpbb\language\language	$lang	phpBB's Language loader  	*/ -	function __construct($datetime_class) +	function __construct(\phpbb\language\language $lang, $datetime_class)  	{  		global $phpbb_root_path;  		$this->lang_path = $phpbb_root_path . 'language/'; +		$this->language = $lang;  		$this->datetime = $datetime_class; + +		$this->is_setup_flag = false;  	}  	/** -	* Function to set custom language path (able to use directory outside of phpBB) -	* -	* @param string $lang_path New language path used. -	* @access public -	*/ -	function set_custom_lang_path($lang_path) +	 * Returns whether user::setup was called +	 * +	 * @return bool +	 */ +	public function is_setup()  	{ -		$this->lang_path = $lang_path; +		return $this->is_setup_flag; +	} -		if (substr($this->lang_path, -1) != '/') +	/** +	 * Magic getter for BC compatibility +	 * +	 * Implement array access for user::lang. +	 * +	 * @param string	$param_name	Name of the BC component the user want to access +	 * +	 * @return array	The appropriate array +	 * +	 * @deprecated 3.2.0-dev (To be removed: 4.0.0) +	 */ +	public function __get($param_name) +	{ +		if ($param_name === 'lang') +		{ +			return $this->language->get_lang_array(); +		} +		else if ($param_name === 'help')  		{ -			$this->lang_path .= '/'; +			$help_array = $this->language->get_lang_array(); +			return $help_array['__help'];  		} + +		return array();  	}  	/** @@ -81,6 +112,8 @@ class user extends \phpbb\session  		global $db, $request, $template, $config, $auth, $phpEx, $phpbb_root_path, $cache;  		global $phpbb_dispatcher; +		$this->language->set_default_language($config['default_lang']); +  		if ($this->data['user_id'] != ANONYMOUS)  		{  			$user_lang_name = (file_exists($this->lang_path . $this->data['user_lang'] . "/common.$phpEx")) ? $this->data['user_lang'] : basename($config['default_lang']); @@ -98,6 +131,7 @@ class user extends \phpbb\session  			{  				$lang_override = $request->variable($config['cookie_name'] . '_lang', '', true, \phpbb\request\request_interface::COOKIE);  			} +  			if ($lang_override)  			{  				$use_lang = basename($lang_override); @@ -108,6 +142,7 @@ class user extends \phpbb\session  			{  				$user_lang_name = basename($config['default_lang']);  			} +  			$user_date_format = $config['default_dateformat'];  			$user_timezone = $config['board_timezone']; @@ -187,6 +222,8 @@ class user extends \phpbb\session  		$this->lang_name = $user_lang_name;  		$this->date_format = $user_date_format; +		$this->language->set_user_language($user_lang_name); +  		try  		{  			$this->timezone = new \DateTimeZone($user_timezone); @@ -197,17 +234,6 @@ class user extends \phpbb\session  			$this->timezone = new \DateTimeZone('UTC');  		} -		// We include common language file here to not load it every time a custom language file is included -		$lang = &$this->lang; - -		// Do not suppress error if in DEBUG mode -		$include_result = (defined('DEBUG')) ? (include $this->lang_path . $this->lang_name . "/common.$phpEx") : (@include $this->lang_path . $this->lang_name . "/common.$phpEx"); - -		if ($include_result === false) -		{ -			die('Language file ' . $this->lang_path . $this->lang_name . "/common.$phpEx" . " couldn't be opened."); -		} -  		$this->add_lang($lang_set);  		unset($lang_set); @@ -393,6 +419,8 @@ class user extends \phpbb\session  			}  		} +		$this->is_setup_flag = true; +  		return;  	} @@ -406,103 +434,13 @@ class user extends \phpbb\session  	*  	* If the first parameter is an array, the elements are used as keys and subkeys to get the language entry:  	* Example: <samp>$user->lang(array('datetime', 'AGO'), 1)</samp> uses $user->lang['datetime']['AGO'] as language entry. +	* +	* @deprecated 3.2.0-dev (To be removed 4.0.0)  	*/  	function lang()  	{  		$args = func_get_args(); -		$key = $args[0]; - -		if (is_array($key)) -		{ -			$lang = &$this->lang[array_shift($key)]; - -			foreach ($key as $_key) -			{ -				$lang = &$lang[$_key]; -			} -		} -		else -		{ -			$lang = &$this->lang[$key]; -		} - -		// Return if language string does not exist -		if (!isset($lang) || (!is_string($lang) && !is_array($lang))) -		{ -			return $key; -		} - -		// If the language entry is a string, we simply mimic sprintf() behaviour -		if (is_string($lang)) -		{ -			if (sizeof($args) == 1) -			{ -				return $lang; -			} - -			// Replace key with language entry and simply pass along... -			$args[0] = $lang; -			return call_user_func_array('sprintf', $args); -		} -		else if (sizeof($lang) == 0) -		{ -			// If the language entry is an empty array, we just return the language key -			return $args[0]; -		} - -		// It is an array... now handle different nullar/singular/plural forms -		$key_found = false; - -		// We now get the first number passed and will select the key based upon this number -		for ($i = 1, $num_args = sizeof($args); $i < $num_args; $i++) -		{ -			if (is_int($args[$i]) || is_float($args[$i])) -			{ -				if ($args[$i] == 0 && isset($lang[0])) -				{ -					// We allow each translation using plural forms to specify a version for the case of 0 things, -					// so that "0 users" may be displayed as "No users". -					$key_found = 0; -					break; -				} -				else -				{ -					$use_plural_form = $this->get_plural_form($args[$i]); -					if (isset($lang[$use_plural_form])) -					{ -						// The key we should use exists, so we use it. -						$key_found = $use_plural_form; -					} -					else -					{ -						// If the key we need to use does not exist, we fall back to the previous one. -						$numbers = array_keys($lang); - -						foreach ($numbers as $num) -						{ -							if ($num > $use_plural_form) -							{ -								break; -							} - -							$key_found = $num; -						} -					} -					break; -				} -			} -		} - -		// Ok, let's check if the key was found, else use the last entry (because it is mostly the plural form) -		if ($key_found === false) -		{ -			$numbers = array_keys($lang); -			$key_found = end($numbers); -		} - -		// Use the language string we determined and pass it to sprintf() -		$args[0] = $lang[$key_found]; -		return call_user_func_array('sprintf', $args); +		return call_user_func_array(array($this->language, 'lang'), $args);  	}  	/** @@ -512,24 +450,22 @@ class user extends \phpbb\session  	* @param $number        int|float   The number we want to get the plural case for. Float numbers are floored.  	* @param $force_rule    mixed   False to use the plural rule of the language package  	*                               or an integer to force a certain plural rule -	* @return   int     The plural-case we need to use for the number plural-rule combination +	* @return int|bool     The plural-case we need to use for the number plural-rule combination, false if $force_rule +	* 					   was invalid. +	* +	* @deprecated: 3.2.0-dev (To be removed: 3.3.0)  	*/  	function get_plural_form($number, $force_rule = false)  	{ -		$number = (int) $number; - -		// Default to English system -		$plural_rule = ($force_rule !== false) ? $force_rule : ((isset($this->lang['PLURAL_RULE'])) ? $this->lang['PLURAL_RULE'] : 1); - -		return phpbb_get_plural_form($plural_rule, $number); +		return $this->language->get_plural_form($number, $force_rule);  	}  	/**  	* Add Language Items - use_db and use_help are assigned where needed (only use them to force inclusion)  	*  	* @param mixed $lang_set specifies the language entries to include -	* @param bool $use_db internal variable for recursion, do not use -	* @param bool $use_help internal variable for recursion, do not use +	* @param bool $use_db internal variable for recursion, do not use	@deprecated 3.2.0-dev (To be removed: 3.3.0) +	* @param bool $use_help internal variable for recursion, do not use	@deprecated 3.2.0-dev (To be removed: 3.3.0)  	* @param string $ext_name The extension to load language from, or empty for core files  	*  	* Examples: @@ -540,11 +476,14 @@ class user extends \phpbb\session  	* $lang_set = 'posting'  	* $lang_set = array('help' => 'faq', 'db' => array('help:faq', 'posting'))  	* </code> +	* +	* Note: $use_db and $use_help should be removed. The old function was kept for BC purposes, +	* 		so the BC logic is handled here. +	* +	* @deprecated: 3.2.0-dev (To be removed: 3.3.0)  	*/  	function add_lang($lang_set, $use_db = false, $use_help = false, $ext_name = '')  	{ -		global $phpEx; -  		if (is_array($lang_set))  		{  			foreach ($lang_set as $key => $lang_file) @@ -555,6 +494,7 @@ class user extends \phpbb\session  				if ($key == 'db')  				{ +					// This is never used  					$this->add_lang($lang_file, true, $use_help, $ext_name);  				}  				else if ($key == 'help') @@ -563,7 +503,7 @@ class user extends \phpbb\session  				}  				else if (!is_array($lang_file))  				{ -					$this->set_lang($this->lang, $this->help, $lang_file, $use_db, $use_help, $ext_name); +					$this->set_lang($lang_file, $use_help, $ext_name);  				}  				else  				{ @@ -574,8 +514,37 @@ class user extends \phpbb\session  		}  		else if ($lang_set)  		{ -			$this->set_lang($this->lang, $this->help, $lang_set, $use_db, $use_help, $ext_name); +			$this->set_lang($lang_set, $use_help, $ext_name); +		} +	} + +	/** +	 * BC function for loading language files +	 * +	 * @deprecated 3.2.0-dev (To be removed: 3.3.0) +	 */ +	private function set_lang($lang_set, $use_help, $ext_name) +	{ +		if (empty($ext_name)) +		{ +			$ext_name = null; +		} + +		if ($use_help && strpos($lang_set, '/') !== false) +		{ +			$component = dirname($lang_set) . '/help_' . basename($lang_set); + +			if ($component[0] === '/') +			{ +				$component = substr($component, 1); +			} +		} +		else +		{ +			$component = (($use_help) ? 'help_' : '') . $lang_set;  		} + +		$this->language->add_lang($component, $ext_name);  	}  	/** @@ -585,6 +554,10 @@ class user extends \phpbb\session  	* @param mixed $lang_set specifies the language entries to include  	* @param bool $use_db internal variable for recursion, do not use  	* @param bool $use_help internal variable for recursion, do not use +	* +	* Note: $use_db and $use_help should be removed. Kept for BC purposes. +	* +	* @deprecated: 3.2.0-dev (To be removed: 3.3.0)  	*/  	function add_lang_ext($ext_name, $lang_set, $use_db = false, $use_help = false)  	{ @@ -597,109 +570,6 @@ class user extends \phpbb\session  	}  	/** -	* Set language entry (called by add_lang) -	* @access private -	*/ -	function set_lang(&$lang, &$help, $lang_file, $use_db = false, $use_help = false, $ext_name = '') -	{ -		global $phpbb_root_path, $phpEx; - -		// Make sure the language name is set (if the user setup did not happen it is not set) -		if (!$this->lang_name) -		{ -			global $config; -			$this->lang_name = basename($config['default_lang']); -		} - -		// $lang == $this->lang -		// $help == $this->help -		// - add appropriate variables here, name them as they are used within the language file... -		if (!$use_db) -		{ -			if ($use_help && strpos($lang_file, '/') !== false) -			{ -				$filename = dirname($lang_file) . '/help_' . basename($lang_file); -			} -			else -			{ -				$filename = (($use_help) ? 'help_' : '') . $lang_file; -			} - -			if ($ext_name) -			{ -				global $phpbb_extension_manager; -				$ext_path = $phpbb_extension_manager->get_extension_path($ext_name, true); - -				$lang_path = $ext_path . 'language/'; -			} -			else -			{ -				$lang_path = $this->lang_path; -			} - -			if (strpos($phpbb_root_path . $filename, $lang_path . $this->lang_name . '/') === 0) -			{ -				$language_filename = $phpbb_root_path . $filename; -			} -			else -			{ -				$language_filename = $lang_path . $this->lang_name . '/' . $filename . '.' . $phpEx; -			} - -			// If we are in install, try to use the updated version, when available -			$install_language_filename = str_replace('language/', 'install/update/new/language/', $language_filename); -			if (defined('IN_INSTALL') && file_exists($install_language_filename)) -			{ -				$language_filename = $install_language_filename; -			} - -			if (!file_exists($language_filename)) -			{ -				global $config; - -				if ($this->lang_name == 'en') -				{ -					// The user's selected language is missing the file, the board default's language is missing the file, and the file doesn't exist in /en. -					$language_filename = str_replace($lang_path . 'en', $lang_path . $this->data['user_lang'], $language_filename); -					trigger_error('Language file ' . $language_filename . ' couldn\'t be opened.', E_USER_ERROR); -				} -				else if ($this->lang_name == basename($config['default_lang'])) -				{ -					// Fall back to the English Language -					$reset_lang_name = $this->lang_name; -					$this->lang_name = 'en'; -					$this->set_lang($lang, $help, $lang_file, $use_db, $use_help, $ext_name); -					$this->lang_name = $reset_lang_name; -				} -				else if ($this->lang_name == $this->data['user_lang']) -				{ -					// Fall back to the board default language -					$reset_lang_name = $this->lang_name; -					$this->lang_name = basename($config['default_lang']); -					$this->set_lang($lang, $help, $lang_file, $use_db, $use_help, $ext_name); -					$this->lang_name = $reset_lang_name; -				} - -				return; -			} - -			// Do not suppress error if in DEBUG mode -			$include_result = (defined('DEBUG')) ? (include $language_filename) : (@include $language_filename); - -			if ($include_result === false) -			{ -				trigger_error('Language file ' . $language_filename . ' couldn\'t be opened.', E_USER_ERROR); -			} -		} -		else if ($use_db) -		{ -			// Get Database Language Strings -			// Put them into $lang if nothing is prefixed, put them into $help if help: is prefixed -			// For example: help:faq, posting -		} -	} - -	/**  	* Format user date  	*  	* @param int $gmepoch unix timestamp @@ -808,7 +678,7 @@ class user extends \phpbb\session  		if ($alt)  		{ -			$alt = $this->lang($alt); +			$alt = $this->language->lang($alt);  			$title = ' title="' . $alt . '"';  		}  		return '<span class="imageset ' . $img . '"' . $title . '>' . $alt . '</span>'; diff --git a/phpBB/phpbb/user_loader.php b/phpBB/phpbb/user_loader.php index 24e663b150..0b192e4452 100644 --- a/phpBB/phpbb/user_loader.php +++ b/phpBB/phpbb/user_loader.php @@ -175,7 +175,7 @@ class user_loader  	/**  	* Get avatar  	* -	* @param int $user_id User ID of the user you want to retreive the avatar for +	* @param int $user_id User ID of the user you want to retrieve the avatar for  	* @param bool $query Should we query the database if this user has not yet been loaded?  	* 						Typically this should be left as false and you should make sure  	* 						you load users ahead of time with load_users() @@ -188,12 +188,14 @@ class user_loader  			return '';  		} -		if (!function_exists('get_user_avatar')) -		{ -			include($this->phpbb_root_path . 'includes/functions_display.' . $this->php_ext); -		} +		$row = array( +			'avatar'		=> $user['user_avatar'], +			'avatar_type'	=> $user['user_avatar_type'], +			'avatar_width'	=> $user['user_avatar_width'], +			'avatar_height'	=> $user['user_avatar_height'], +		); -		return get_user_avatar($user['user_avatar'], $user['user_avatar_type'], $user['user_avatar_width'], $user['user_avatar_height']); +		return phpbb_get_avatar($row, 'USER_AVATAR');  	}  	/** diff --git a/phpBB/phpbb/viewonline_helper.php b/phpBB/phpbb/viewonline_helper.php index b722f9d911..89915f2228 100644 --- a/phpBB/phpbb/viewonline_helper.php +++ b/phpBB/phpbb/viewonline_helper.php @@ -18,13 +18,13 @@ namespace phpbb;  */  class viewonline_helper  { -	/** @var \phpbb\filesystem */ +	/** @var \phpbb\filesystem\filesystem_interface */  	protected $filesystem;  	/** -	* @param \phpbb\filesystem $filesystem +	* @param \phpbb\filesystem\filesystem_interface $filesystem	phpBB's filesystem service  	*/ -	public function __construct(\phpbb\filesystem $filesystem) +	public function __construct(\phpbb\filesystem\filesystem_interface $filesystem)  	{  		$this->filesystem = $filesystem;  	}  | 
