diff options
Diffstat (limited to 'phpBB/phpbb')
385 files changed, 37660 insertions, 3893 deletions
diff --git a/phpBB/phpbb/attachment/delete.php b/phpBB/phpbb/attachment/delete.php new file mode 100644 index 0000000000..922f24b5dc --- /dev/null +++ b/phpBB/phpbb/attachment/delete.php @@ -0,0 +1,432 @@ +<?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\attachment; + +use \phpbb\config\config; +use \phpbb\db\driver\driver_interface; +use \phpbb\event\dispatcher; +use \phpbb\filesystem\filesystem; + +/** + * Attachment delete class + */ +class delete +{ +	/** @var config */ +	protected $config; + +	/** @var driver_interface */ +	protected $db; + +	/** @var dispatcher */ +	protected $dispatcher; + +	/** @var filesystem  */ +	protected $filesystem; + +	/** @var resync */ +	protected $resync; + +	/** @var string phpBB root path */ +	protected $phpbb_root_path; + +	/** @var array Attachement IDs */ +	protected $ids; + +	/** @var string SQL ID string */ +	private $sql_id; + +	/** @var string SQL where string */ +	private $sql_where = ''; + +	/** @var int Number of deleted items */ +	private $num_deleted; + +	/** @var array Post IDs */ +	private $post_ids = array(); + +	/** @var array Message IDs */ +	private $message_ids = array(); + +	/** @var array Topic IDs */ +	private $topic_ids = array(); + +	/** @var array Info of physical file */ +	private $physical = array(); + +	/** +	 * Attachment delete class constructor +	 * +	 * @param config $config +	 * @param driver_interface $db +	 * @param dispatcher $dispatcher +	 * @param filesystem $filesystem +	 * @param resync $resync +	 * @param string $phpbb_root_path +	 */ +	public function __construct(config $config, driver_interface $db, dispatcher $dispatcher, filesystem $filesystem, resync $resync, $phpbb_root_path) +	{ +		$this->config = $config; +		$this->db = $db; +		$this->dispatcher = $dispatcher; +		$this->filesystem = $filesystem; +		$this->resync = $resync; +		$this->phpbb_root_path = $phpbb_root_path; +	} + +	/** +	 * Delete Attachments +	 * +	 * @param string $mode can be: post|message|topic|attach|user +	 * @param mixed $ids can be: post_ids, message_ids, topic_ids, attach_ids, user_ids +	 * @param bool $resync set this to false if you are deleting posts or topics +	 * +	 * @return int|bool Number of deleted attachments or false if something +	 *			went wrong during attachment deletion +	 */ +	public function delete($mode, $ids, $resync = true) +	{ +		if (!$this->set_attachment_ids($ids)) +		{ +			return false; +		} + +		$this->set_sql_constraints($mode); + +		/** +		 * Perform additional actions before collecting data for attachment(s) deletion +		 * +		 * @event core.delete_attachments_collect_data_before +		 * @var	string	mode			Variable containing attachments deletion mode, can be: post|message|topic|attach|user +		 * @var	mixed	ids				Array or comma separated list of ids corresponding to the mode +		 * @var	bool	resync			Flag indicating if posts/messages/topics should be synchronized +		 * @var	string	sql_id			The field name to collect/delete data for depending on the mode +		 * @since 3.1.7-RC1 +		 */ +		$vars = array( +			'mode', +			'ids', +			'resync', +			'sql_id', +		); +		extract($this->dispatcher->trigger_event('core.delete_attachments_collect_data_before', compact($vars))); + +		// Collect post and topic ids for later use if we need to touch remaining entries (if resync is enabled) +		$this->collect_attachment_info($resync); + +		// Delete attachments from database +		$this->delete_attachments_from_db(); + +		/** +		 * Perform additional actions after attachment(s) deletion from the database +		 * +		 * @event core.delete_attachments_from_database_after +		 * @var	string	mode			Variable containing attachments deletion mode, can be: post|message|topic|attach|user +		 * @var	mixed	ids				Array or comma separated list of ids corresponding to the mode +		 * @var	bool	resync			Flag indicating if posts/messages/topics should be synchronized +		 * @var	string	sql_id			The field name to collect/delete data for depending on the mode +		 * @var	array	post_ids		Array with post ids for deleted attachment(s) +		 * @var	array	topic_ids		Array with topic ids for deleted attachment(s) +		 * @var	array	message_ids		Array with private message ids for deleted attachment(s) +		 * @var	array	physical		Array with deleted attachment(s) physical file(s) data +		 * @var	int		num_deleted		The number of deleted attachment(s) from the database +		 * @since 3.1.7-RC1 +		 */ +		$vars = array( +			'mode', +			'ids', +			'resync', +			'sql_id', +			'post_ids', +			'topic_ids', +			'message_ids', +			'physical', +			'num_deleted', +		); +		extract($this->dispatcher->trigger_event('core.delete_attachments_from_database_after', compact($vars))); + +		if (!$this->num_deleted) +		{ +			return 0; +		} + +		// Delete attachments from filesystem +		$this->remove_from_filesystem(); + +		// If we do not resync, we do not need to adjust any message, post, topic or user entries +		if (!$resync) +		{ +			return $this->num_deleted; +		} + +		// No more use for the original ids +		unset($ids); + +		// Update post indicators for posts now no longer having attachments +		$this->resync->resync('post', $this->post_ids); + +		// Update message table if messages are affected +		$this->resync->resync('message', $this->message_ids); + +		// Now update the topics. This is a bit trickier, because there could be posts still having attachments within the topic +		$this->resync->resync('topic', $this->topic_ids); + +		return $this->num_deleted; +	} + +	/** +	 * Set attachment IDs +	 * +	 * @param mixed $ids ID or array of IDs +	 * +	 * @return bool True if attachment IDs were set, false if not +	 */ +	protected function set_attachment_ids($ids) +	{ +		// 0 is as bad as an empty array +		if (empty($ids)) +		{ +			return false; +		} + +		if (is_array($ids)) +		{ +			$ids = array_unique($ids); +			$this->ids = array_map('intval', $ids); +		} +		else +		{ +			$this->ids = array((int) $ids); +		} + +		return true; +	} + +	/** +	 * Set SQL constraints based on mode +	 * +	 * @param string $mode Delete mode; can be: post|message|topic|attach|user +	 */ +	private function set_sql_constraints($mode) +	{ +		switch ($mode) +		{ +			case 'post': +			case 'message': +				$this->sql_id = 'post_msg_id'; +				$this->sql_where = ' AND in_message = ' . ($mode == 'message' ? 1 : 0); +			break; + +			case 'topic': +				$this->sql_id = 'topic_id'; +			break; + +			case 'user': +				$this->sql_id = 'poster_id'; +			break; + +			case 'attach': +			default: +				$this->sql_id = 'attach_id'; +			break; +		} +	} + +	/** +	 * Collect info about attachment IDs +	 * +	 * @param bool $resync Whether topics/posts should be resynced after delete +	 */ +	protected function collect_attachment_info($resync) +	{ +		// Collect post and topic ids for later use if we need to touch remaining entries (if resync is enabled) +		$sql = 'SELECT post_msg_id, topic_id, in_message, physical_filename, thumbnail, filesize, is_orphan +			FROM ' . ATTACHMENTS_TABLE . ' +			WHERE ' . $this->db->sql_in_set($this->sql_id, $this->ids); + +		$sql .= $this->sql_where; + +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			// We only need to store post/message/topic ids if resync is enabled and the file is not orphaned +			if ($resync && !$row['is_orphan']) +			{ +				if (!$row['in_message']) +				{ +					$this->post_ids[] = $row['post_msg_id']; +					$this->topic_ids[] = $row['topic_id']; +				} +				else +				{ +					$this->message_ids[] = $row['post_msg_id']; +				} +			} + +			$this->physical[] = array('filename' => $row['physical_filename'], 'thumbnail' => $row['thumbnail'], 'filesize' => $row['filesize'], 'is_orphan' => $row['is_orphan']); +		} +		$this->db->sql_freeresult($result); + +		// IDs should be unique +		$this->post_ids = array_unique($this->post_ids); +		$this->message_ids = array_unique($this->message_ids); +		$this->topic_ids = array_unique($this->topic_ids); +	} + +	/** +	 * Delete attachments from database table +	 */ +	protected function delete_attachments_from_db() +	{ +		/** +		 * Perform additional actions before attachment(s) deletion +		 * +		 * @event core.delete_attachments_before +		 * @var	string	mode			Variable containing attachments deletion mode, can be: post|message|topic|attach|user +		 * @var	mixed	ids				Array or comma separated list of ids corresponding to the mode +		 * @var	bool	resync			Flag indicating if posts/messages/topics should be synchronized +		 * @var	string	sql_id			The field name to collect/delete data for depending on the mode +		 * @var	array	post_ids		Array with post ids for deleted attachment(s) +		 * @var	array	topic_ids		Array with topic ids for deleted attachment(s) +		 * @var	array	message_ids		Array with private message ids for deleted attachment(s) +		 * @var	array	physical		Array with deleted attachment(s) physical file(s) data +		 * @since 3.1.7-RC1 +		 */ +		$vars = array( +			'mode', +			'ids', +			'resync', +			'sql_id', +			'post_ids', +			'topic_ids', +			'message_ids', +			'physical', +		); +		extract($this->dispatcher->trigger_event('core.delete_attachments_before', compact($vars))); + +		// Delete attachments +		$sql = 'DELETE FROM ' . ATTACHMENTS_TABLE . ' +			WHERE ' . $this->db->sql_in_set($this->sql_id, $this->ids); + +		$sql .= $this->sql_where; + +		$this->db->sql_query($sql); +		$this->num_deleted = $this->db->sql_affectedrows(); +	} + +	/** +	 * Delete attachments from filesystem +	 */ +	protected function remove_from_filesystem() +	{ +		$space_removed = $files_removed = 0; + +		foreach ($this->physical as $file_ary) +		{ +			if ($this->unlink_attachment($file_ary['filename'], 'file', true) && !$file_ary['is_orphan']) +			{ +				// Only non-orphaned files count to the file size +				$space_removed += $file_ary['filesize']; +				$files_removed++; +			} + +			if ($file_ary['thumbnail']) +			{ +				$this->unlink_attachment($file_ary['filename'], 'thumbnail', true); +			} +		} + +		/** +		 * Perform additional actions after attachment(s) deletion from the filesystem +		 * +		 * @event core.delete_attachments_from_filesystem_after +		 * @var	string	mode			Variable containing attachments deletion mode, can be: post|message|topic|attach|user +		 * @var	mixed	ids				Array or comma separated list of ids corresponding to the mode +		 * @var	bool	resync			Flag indicating if posts/messages/topics should be synchronized +		 * @var	string	sql_id			The field name to collect/delete data for depending on the mode +		 * @var	array	post_ids		Array with post ids for deleted attachment(s) +		 * @var	array	topic_ids		Array with topic ids for deleted attachment(s) +		 * @var	array	message_ids		Array with private message ids for deleted attachment(s) +		 * @var	array	physical		Array with deleted attachment(s) physical file(s) data +		 * @var	int		num_deleted		The number of deleted attachment(s) from the database +		 * @var	int		space_removed	The size of deleted files(s) from the filesystem +		 * @var	int		files_removed	The number of deleted file(s) from the filesystem +		 * @since 3.1.7-RC1 +		 */ +		$vars = array( +			'mode', +			'ids', +			'resync', +			'sql_id', +			'post_ids', +			'topic_ids', +			'message_ids', +			'physical', +			'num_deleted', +			'space_removed', +			'files_removed', +		); +		extract($this->dispatcher->trigger_event('core.delete_attachments_from_filesystem_after', compact($vars))); + +		if ($space_removed || $files_removed) +		{ +			$this->config->increment('upload_dir_size', $space_removed * (-1), false); +			$this->config->increment('num_files', $files_removed * (-1), false); +		} +	} + +	/** +	 * Delete attachment from filesystem +	 * +	 * @param string $filename Filename of attachment +	 * @param string $mode Delete mode +	 * @param bool $entry_removed Whether entry was removed. Defaults to false +	 * @return bool True if file was removed, false if not +	 */ +	public function unlink_attachment($filename, $mode = 'file', $entry_removed = false) +	{ +		// Because of copying topics or modifications a physical filename could be assigned more than once. If so, do not remove the file itself. +		$sql = 'SELECT COUNT(attach_id) AS num_entries +		FROM ' . ATTACHMENTS_TABLE . " +		WHERE physical_filename = '" . $this->db->sql_escape(utf8_basename($filename)) . "'"; +		$result = $this->db->sql_query($sql); +		$num_entries = (int) $this->db->sql_fetchfield('num_entries'); +		$this->db->sql_freeresult($result); + +		// Do not remove file if at least one additional entry with the same name exist. +		if (($entry_removed && $num_entries > 0) || (!$entry_removed && $num_entries > 1)) +		{ +			return false; +		} + +		$filename = ($mode == 'thumbnail') ? 'thumb_' . utf8_basename($filename) : utf8_basename($filename); +		$filepath = $this->phpbb_root_path . $this->config['upload_path'] . '/' . $filename; + +		try +		{ +			if ($this->filesystem->exists($filepath)) +			{ +				$this->filesystem->remove($this->phpbb_root_path . $this->config['upload_path'] . '/' . $filename); +				return true; +			} +		} +		catch (\phpbb\filesystem\exception\filesystem_exception $exception) +		{ +			// Fail is covered by return statement below +		} + +		return false; +	} +} diff --git a/phpBB/phpbb/attachment/manager.php b/phpBB/phpbb/attachment/manager.php new file mode 100644 index 0000000000..3c47171b2f --- /dev/null +++ b/phpBB/phpbb/attachment/manager.php @@ -0,0 +1,99 @@ +<?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\attachment; + +/** + * Attachment manager + */ +class manager +{ +	/** @var delete Attachment delete class */ +	protected $delete; + +	/** @var resync Attachment resync class */ +	protected $resync; + +	/** @var upload Attachment upload class */ +	protected $upload; + +	/** +	 * Constructor for attachment manager +	 * +	 * @param delete $delete Attachment delete class +	 * @param resync $resync Attachment resync class +	 * @param upload $upload Attachment upload class +	 */ +	public function __construct(delete $delete, resync $resync, upload $upload) +	{ +		$this->delete = $delete; +		$this->resync = $resync; +		$this->upload = $upload; +	} + +	/** +	 * Wrapper method for deleting attachments +	 * +	 * @param string $mode can be: post|message|topic|attach|user +	 * @param mixed $ids can be: post_ids, message_ids, topic_ids, attach_ids, user_ids +	 * @param bool $resync set this to false if you are deleting posts or topics +	 * +	 * @return int|bool Number of deleted attachments or false if something +	 *			went wrong during attachment deletion +	 */ +	public function delete($mode, $ids, $resync = true) +	{ +		return $this->delete->delete($mode, $ids, $resync); +	} + +	/** +	 * Wrapper method for deleting attachments from filesystem +	 * +	 * @param string $filename Filename of attachment +	 * @param string $mode Delete mode +	 * @param bool $entry_removed Whether entry was removed. Defaults to false +	 * @return bool True if file was removed, false if not +	 */ +	public function unlink($filename, $mode = 'file', $entry_removed = false) +	{ +		return $this->delete->unlink_attachment($filename, $mode, $entry_removed); +	} + +	/** +	 * Wrapper method for resyncing specified type +	 * +	 * @param string $type Type of resync +	 * @param array $ids IDs to resync +	 */ +	public function resync($type, $ids) +	{ +		$this->resync->resync($type, $ids); +	} + +	/** +	 * Wrapper method for uploading attachment +	 * +	 * @param string			$form_name		The form name of the file upload input +	 * @param int			$forum_id		The id of the forum +	 * @param bool			$local			Whether the file is local or not +	 * @param string			$local_storage	The path to the local file +	 * @param bool			$is_message		Whether it is a PM or not +	 * @param array		$local_filedata	An file data object created for the local file +	 * +	 * @return array File data array +	 */ +	public function upload($form_name, $forum_id, $local = false, $local_storage = '', $is_message = false, $local_filedata = []) +	{ +		return $this->upload->upload($form_name, $forum_id, $local, $local_storage, $is_message, $local_filedata); +	} +} diff --git a/phpBB/phpbb/attachment/resync.php b/phpBB/phpbb/attachment/resync.php new file mode 100644 index 0000000000..6c2e0a8b0d --- /dev/null +++ b/phpBB/phpbb/attachment/resync.php @@ -0,0 +1,124 @@ +<?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\attachment; + +use \phpbb\db\driver\driver_interface; + +/** + * Attachment resync class + */ +class resync +{ +	/** @var driver_interface */ +	protected $db; + +	/** @var string Attachment table SQL ID */ +	private $attach_sql_id; + +	/** @var string Resync table SQL ID  */ +	private $resync_sql_id; + +	/** @var string Resync SQL table */ +	private $resync_table; + +	/** @var string SQL where statement */ +	private $sql_where; + +	/** +	 * Constructor for attachment resync class +	 * +	 * @param driver_interface $db Database driver +	 */ +	public function __construct(driver_interface $db) +	{ +		$this->db = $db; +	} + +	/** +	 * Set type constraints for attachment resync +	 * +	 * @param string $type Type of resync; can be: message|post|topic +	 */ +	protected function set_type_constraints($type) +	{ +		switch ($type) +		{ +			case 'message': +				$this->attach_sql_id = 'post_msg_id'; +				$this->sql_where = ' AND in_message = 1 +					AND is_orphan = 0'; +				$this->resync_table = PRIVMSGS_TABLE; +				$this->resync_sql_id = 'msg_id'; +			break; + +			case 'post': +				$this->attach_sql_id = 'post_msg_id'; +				$this->sql_where = ' AND in_message = 0 +					AND is_orphan = 0'; +				$this->resync_table = POSTS_TABLE; +				$this->resync_sql_id = 'post_id'; +			break; + +			case 'topic': +				$this->attach_sql_id = 'topic_id'; +				$this->sql_where = ' AND is_orphan = 0'; +				$this->resync_table = TOPICS_TABLE; +				$this->resync_sql_id = 'topic_id'; +			break; +		} +	} + +	/** +	 * Resync specified type +	 * +	 * @param string $type Type of resync +	 * @param array $ids IDs to resync +	 */ +	public function resync($type, $ids) +	{ +		if (empty($type) || !is_array($ids) || !sizeof($ids) || !in_array($type, array('post', 'topic', 'message'))) +		{ +			return; +		} + +		$this->set_type_constraints($type); + +		// Just check which elements are still having an assigned attachment +		// not orphaned by querying the attachments table +		$sql = 'SELECT ' . $this->attach_sql_id . ' +			FROM ' . ATTACHMENTS_TABLE . ' +			WHERE ' . $this->db->sql_in_set($this->attach_sql_id, $ids) +				. $this->sql_where; +		$result = $this->db->sql_query($sql); + +		$remaining_ids = array(); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$remaining_ids[] = $row[$this->attach_sql_id]; +		} +		$this->db->sql_freeresult($result); + +		// Now only unset those ids remaining +		$ids = array_diff($ids, $remaining_ids); + +		if (sizeof($ids)) +		{ +			$sql = 'UPDATE ' . $this->resync_table . ' +				SET ' . $type . '_attachment = 0 +				WHERE ' . $this->db->sql_in_set($this->resync_sql_id, $ids); +			$this->db->sql_query($sql); +		} +	} + +} diff --git a/phpBB/phpbb/attachment/upload.php b/phpBB/phpbb/attachment/upload.php new file mode 100644 index 0000000000..f9863b372c --- /dev/null +++ b/phpBB/phpbb/attachment/upload.php @@ -0,0 +1,334 @@ +<?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\attachment; + +use phpbb\auth\auth; +use \phpbb\cache\service; +use \phpbb\config\config; +use \phpbb\event\dispatcher; +use \phpbb\language\language; +use \phpbb\mimetype\guesser; +use \phpbb\plupload\plupload; +use \phpbb\user; + +/** + * Attachment upload class + */ +class upload +{ +	/** @var auth */ +	protected $auth; + +	/** @var service */ +	protected $cache; + +	/** @var config */ +	protected $config; + +	/** @var \phpbb\files\upload Upload class */ +	protected $files_upload; + +	/** @var language */ +	protected $language; + +	/** @var guesser Mimetype guesser */ +	protected $mimetype_guesser; + +	/** @var dispatcher */ +	protected $phpbb_dispatcher; + +	/** @var plupload Plupload */ +	protected $plupload; + +	/** @var user */ +	protected $user; + +	/** @var \phpbb\files\filespec Current filespec instance */ +	private $file; + +	/** @var array File data */ +	private $file_data = array( +		'error'	=> array() +	); + +	/** @var array Extensions array */ +	private $extensions; + +	/** +	 * Constructor for attachments upload class +	 * +	 * @param auth $auth +	 * @param service $cache +	 * @param config $config +	 * @param \phpbb\files\upload $files_upload +	 * @param language $language +	 * @param guesser $mimetype_guesser +	 * @param dispatcher $phpbb_dispatcher +	 * @param plupload $plupload +	 * @param user $user +	 * @param $phpbb_root_path +	 */ +	public function __construct(auth $auth, service $cache, config $config, \phpbb\files\upload $files_upload, language $language, guesser $mimetype_guesser, dispatcher $phpbb_dispatcher, plupload $plupload, user $user, $phpbb_root_path) +	{ +		$this->auth = $auth; +		$this->cache = $cache; +		$this->config = $config; +		$this->files_upload = $files_upload; +		$this->language = $language; +		$this->mimetype_guesser = $mimetype_guesser; +		$this->phpbb_dispatcher = $phpbb_dispatcher; +		$this->plupload = $plupload; +		$this->user = $user; +		$this->phpbb_root_path = $phpbb_root_path; +	} + +	/** +	 * Upload Attachment - filedata is generated here +	 * Uses upload class +	 * +	 * @param string			$form_name		The form name of the file upload input +	 * @param int			$forum_id		The id of the forum +	 * @param bool			$local			Whether the file is local or not +	 * @param string			$local_storage	The path to the local file +	 * @param bool			$is_message		Whether it is a PM or not +	 * @param array		$local_filedata	An file data object created for the local file +	 * +	 * @return array File data array +	 */ +	public function upload($form_name, $forum_id, $local = false, $local_storage = '', $is_message = false, $local_filedata = array()) +	{ +		$this->init_files_upload($forum_id, $is_message); + +		$this->file_data['post_attach'] = $local || $this->files_upload->is_valid($form_name); + +		if (!$this->file_data['post_attach']) +		{ +			$this->file_data['error'][] = $this->language->lang('NO_UPLOAD_FORM_FOUND'); +			return $this->file_data; +		} + +		$this->file = ($local) ? $this->files_upload->handle_upload('files.types.local', $local_storage, $local_filedata) : $this->files_upload->handle_upload('files.types.form', $form_name); + +		if ($this->file->init_error()) +		{ +			$this->file_data['post_attach'] = false; +			return $this->file_data; +		} + +		// Whether the uploaded file is in the image category +		$is_image = (isset($this->extensions[$this->file->get('extension')]['display_cat'])) ? $this->extensions[$this->file->get('extension')]['display_cat'] == ATTACHMENT_CATEGORY_IMAGE : false; + +		if (!$this->auth->acl_get('a_') && !$this->auth->acl_get('m_', $forum_id)) +		{ +			// Check Image Size, if it is an image +			if ($is_image) +			{ +				$this->file->upload->set_allowed_dimensions(0, 0, $this->config['img_max_width'], $this->config['img_max_height']); +			} + +			// Admins and mods are allowed to exceed the allowed filesize +			if (!empty($this->extensions[$this->file->get('extension')]['max_filesize'])) +			{ +				$allowed_filesize = $this->extensions[$this->file->get('extension')]['max_filesize']; +			} +			else +			{ +				$allowed_filesize = ($is_message) ? $this->config['max_filesize_pm'] : $this->config['max_filesize']; +			} + +			$this->file->upload->set_max_filesize($allowed_filesize); +		} + +		$this->file->clean_filename('unique', $this->user->data['user_id'] . '_'); + +		// Are we uploading an image *and* this image being within the image category? +		// Only then perform additional image checks. +		$this->file->move_file($this->config['upload_path'], false, !$is_image); + +		// Do we have to create a thumbnail? +		$this->file_data['thumbnail'] = ($is_image && $this->config['img_create_thumbnail']) ? 1 : 0; + +		// Make sure the image category only holds valid images... +		$this->check_image($is_image); + +		if (sizeof($this->file->error)) +		{ +			$this->file->remove(); +			$this->file_data['error'] = array_merge($this->file_data['error'], $this->file->error); +			$this->file_data['post_attach'] = false; + +			return $this->file_data; +		} + +		$this->fill_file_data(); + +		$filedata = $this->file_data; + +		/** +		 * Event to modify uploaded file before submit to the post +		 * +		 * @event core.modify_uploaded_file +		 * @var	array	filedata	Array containing uploaded file data +		 * @var	bool	is_image	Flag indicating if the file is an image +		 * @since 3.1.0-RC3 +		 */ +		$vars = array( +			'filedata', +			'is_image', +		); +		extract($this->phpbb_dispatcher->trigger_event('core.modify_uploaded_file', compact($vars))); +		$this->file_data = $filedata; +		unset($filedata); + +		// Check for attachment quota and free space +		if (!$this->check_attach_quota() || !$this->check_disk_space()) +		{ +			return $this->file_data; +		} + +		// Create Thumbnail +		$this->create_thumbnail(); + +		return $this->file_data; +	} + +	/** +	 * Create thumbnail for file if necessary +	 * +	 * @return array Updated $filedata +	 */ +	protected function create_thumbnail() +	{ +		if ($this->file_data['thumbnail']) +		{ +			$source = $this->file->get('destination_file'); +			$destination = $this->file->get('destination_path') . '/thumb_' . $this->file->get('realname'); + +			if (!create_thumbnail($source, $destination, $this->file->get('mimetype'))) +			{ +				$this->file_data['thumbnail'] = 0; +			} +		} +	} + +	/** +	 * Init files upload class +	 * +	 * @param int $forum_id Forum ID +	 * @param bool $is_message Whether attachment is inside PM or not +	 */ +	protected function init_files_upload($forum_id, $is_message) +	{ +		if ($this->config['check_attachment_content'] && isset($this->config['mime_triggers'])) +		{ +			$this->files_upload->set_disallowed_content(explode('|', $this->config['mime_triggers'])); +		} +		else if (!$this->config['check_attachment_content']) +		{ +			$this->files_upload->set_disallowed_content(array()); +		} + +		$this->extensions = $this->cache->obtain_attach_extensions((($is_message) ? false : (int) $forum_id)); +		$this->files_upload->set_allowed_extensions(array_keys($this->extensions['_allowed_'])); +	} + +	/** +	 * Check if uploaded file is really an image +	 * +	 * @param bool $is_image Whether file is image +	 */ +	protected function check_image($is_image) +	{ +		// Make sure the image category only holds valid images... +		if ($is_image && !$this->file->is_image()) +		{ +			$this->file->remove(); + +			if ($this->plupload && $this->plupload->is_active()) +			{ +				$this->plupload->emit_error(104, 'ATTACHED_IMAGE_NOT_IMAGE'); +			} + +			// If this error occurs a user tried to exploit an IE Bug by renaming extensions +			// Since the image category is displaying content inline we need to catch this. +			$this->file->set_error($this->language->lang('ATTACHED_IMAGE_NOT_IMAGE')); +		} +	} + +	/** +	 * Check if attachment quota was reached +	 * +	 * @return bool False if attachment quota was reached, true if not +	 */ +	protected function check_attach_quota() +	{ +		if ($this->config['attachment_quota']) +		{ +			if (intval($this->config['upload_dir_size']) + $this->file->get('filesize') > $this->config['attachment_quota']) +			{ +				$this->file_data['error'][] = $this->language->lang('ATTACH_QUOTA_REACHED'); +				$this->file_data['post_attach'] = false; + +				$this->file->remove(); + +				return false; +			} +		} + +		return true; +	} + +	/** +	 * Check if there is enough free space available on disk +	 * +	 * @return bool True if disk space is available, false if not +	 */ +	protected function check_disk_space() +	{ +		if ($free_space = @disk_free_space($this->phpbb_root_path . $this->config['upload_path'])) +		{ +			if ($free_space <= $this->file->get('filesize')) +			{ +				if ($this->auth->acl_get('a_')) +				{ +					$this->file_data['error'][] = $this->language->lang('ATTACH_DISK_FULL'); +				} +				else +				{ +					$this->file_data['error'][] = $this->language->lang('ATTACH_QUOTA_REACHED'); +				} +				$this->file_data['post_attach'] = false; + +				$this->file->remove(); + +				return false; +			} +		} + +		return true; +	} + +	/** +	 * Fills file data with file information and current time as filetime +	 */ +	protected function fill_file_data() +	{ +		$this->file_data['filesize'] = $this->file->get('filesize'); +		$this->file_data['mimetype'] = $this->file->get('mimetype'); +		$this->file_data['extension'] = $this->file->get('extension'); +		$this->file_data['physical_filename'] = $this->file->get('realname'); +		$this->file_data['real_filename'] = $this->file->get('uploadname'); +		$this->file_data['filetime'] = time(); +	} +} diff --git a/phpBB/phpbb/auth/auth.php b/phpBB/phpbb/auth/auth.php index b7634e04ce..fc7cc1a0b1 100644 --- a/phpBB/phpbb/auth/auth.php +++ b/phpBB/phpbb/auth/auth.php @@ -930,6 +930,7 @@ class auth  		global $db, $user, $phpbb_root_path, $phpEx, $phpbb_container;  		global $phpbb_dispatcher; +		/* @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/auth/provider/oauth/oauth.php b/phpBB/phpbb/auth/provider/oauth/oauth.php index 9f6345fbba..04729d8453 100644 --- a/phpBB/phpbb/auth/provider/oauth/oauth.php +++ b/phpBB/phpbb/auth/provider/oauth/oauth.php @@ -63,6 +63,13 @@ class oauth extends \phpbb\auth\provider\base  	protected $auth_provider_oauth_token_storage_table;  	/** +	* OAuth state table +	* +	* @var string +	*/ +	protected $auth_provider_oauth_state_table; + +	/**  	* OAuth account association table  	*  	* @var string @@ -120,6 +127,7 @@ class oauth extends \phpbb\auth\provider\base  	* @param	\phpbb\request\request_interface	$request  	* @param	\phpbb\user		$user  	* @param	string			$auth_provider_oauth_token_storage_table +	* @param	string			$auth_provider_oauth_state_table  	* @param	string			$auth_provider_oauth_token_account_assoc  	* @param	\phpbb\di\service_collection	$service_providers Contains \phpbb\auth\provider\oauth\service_interface  	* @param	string			$users_table @@ -127,7 +135,7 @@ class oauth extends \phpbb\auth\provider\base  	* @param	string			$phpbb_root_path  	* @param	string			$php_ext  	*/ -	public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\config\config $config, \phpbb\passwords\manager $passwords_manager, \phpbb\request\request_interface $request, \phpbb\user $user, $auth_provider_oauth_token_storage_table, $auth_provider_oauth_token_account_assoc, \phpbb\di\service_collection $service_providers, $users_table, \Symfony\Component\DependencyInjection\ContainerInterface $phpbb_container, $phpbb_root_path, $php_ext) +	public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\config\config $config, \phpbb\passwords\manager $passwords_manager, \phpbb\request\request_interface $request, \phpbb\user $user, $auth_provider_oauth_token_storage_table, $auth_provider_oauth_state_table, $auth_provider_oauth_token_account_assoc, \phpbb\di\service_collection $service_providers, $users_table, \Symfony\Component\DependencyInjection\ContainerInterface $phpbb_container, $phpbb_root_path, $php_ext)  	{  		$this->db = $db;  		$this->config = $config; @@ -135,6 +143,7 @@ class oauth extends \phpbb\auth\provider\base  		$this->request = $request;  		$this->user = $user;  		$this->auth_provider_oauth_token_storage_table = $auth_provider_oauth_token_storage_table; +		$this->auth_provider_oauth_state_table = $auth_provider_oauth_state_table;  		$this->auth_provider_oauth_token_account_assoc = $auth_provider_oauth_token_account_assoc;  		$this->service_providers = $service_providers;  		$this->users_table = $users_table; @@ -188,11 +197,12 @@ class oauth extends \phpbb\auth\provider\base  		// Get the service credentials for the given service  		$service_credentials = $this->service_providers[$service_name]->get_service_credentials(); -		$storage = new \phpbb\auth\provider\oauth\token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table); +		$storage = new \phpbb\auth\provider\oauth\token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table, $this->auth_provider_oauth_state_table);  		$query = 'mode=login&login=external&oauth_service=' . $service_name_original;  		$service = $this->get_service($service_name_original, $storage, $service_credentials, $query, $this->service_providers[$service_name]->get_auth_scope()); -		if ($this->request->is_set('code', \phpbb\request\request_interface::GET)) +		if (($service::OAUTH_VERSION === 2 && $this->request->is_set('code', \phpbb\request\request_interface::GET)) +			|| ($service::OAUTH_VERSION === 1 && $this->request->is_set('oauth_token', \phpbb\request\request_interface::GET)))  		{  			$this->service_providers[$service_name]->set_external_service_provider($service);  			$unique_id = $this->service_providers[$service_name]->perform_auth_login(); @@ -247,7 +257,15 @@ class oauth extends \phpbb\auth\provider\base  		}  		else  		{ -			$url = $service->getAuthorizationUri(); +			if ($service::OAUTH_VERSION === 1) +			{ +				$token = $service->requestRequestToken(); +				$url = $service->getAuthorizationUri(array('oauth_token' => $token->getRequestToken())); +			} +			else +			{ +				$url = $service->getAuthorizationUri(); +			}  			header('Location: ' . $url);  		}  	} @@ -462,7 +480,7 @@ class oauth extends \phpbb\auth\provider\base  	*/  	protected function link_account_login_link(array $link_data, $service_name)  	{ -		$storage = new \phpbb\auth\provider\oauth\token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table); +		$storage = new \phpbb\auth\provider\oauth\token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table, $this->auth_provider_oauth_state_table);  		// Check for an access token, they should have one  		if (!$storage->has_access_token_by_session($service_name)) @@ -505,13 +523,14 @@ class oauth extends \phpbb\auth\provider\base  	*/  	protected function link_account_auth_link(array $link_data, $service_name)  	{ -		$storage = new \phpbb\auth\provider\oauth\token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table); +		$storage = new \phpbb\auth\provider\oauth\token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table, $this->auth_provider_oauth_state_table);  		$query = 'i=ucp_auth_link&mode=auth_link&link=1&oauth_service=' . strtolower($link_data['oauth_service']);  		$service_credentials = $this->service_providers[$service_name]->get_service_credentials();  		$scopes = $this->service_providers[$service_name]->get_auth_scope();  		$service = $this->get_service(strtolower($link_data['oauth_service']), $storage, $service_credentials, $query, $scopes); -		if ($this->request->is_set('code', \phpbb\request\request_interface::GET)) +		if (($service::OAUTH_VERSION === 2 && $this->request->is_set('code', \phpbb\request\request_interface::GET)) +			|| ($service::OAUTH_VERSION === 1 && $this->request->is_set('oauth_token', \phpbb\request\request_interface::GET)))  		{  			$this->service_providers[$service_name]->set_external_service_provider($service);  			$unique_id = $this->service_providers[$service_name]->perform_auth_login(); @@ -527,7 +546,15 @@ class oauth extends \phpbb\auth\provider\base  		}  		else  		{ -			$url = $service->getAuthorizationUri(); +			if ($service::OAUTH_VERSION === 1) +			{ +				$token = $service->requestRequestToken(); +				$url = $service->getAuthorizationUri(array('oauth_token' => $token->getRequestToken())); +			} +			else +			{ +				$url = $service->getAuthorizationUri(); +			}  			header('Location: ' . $url);  		}  	} @@ -550,7 +577,7 @@ class oauth extends \phpbb\auth\provider\base  	public function logout($data, $new_session)  	{  		// Clear all tokens belonging to the user -		$storage = new \phpbb\auth\provider\oauth\token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table); +		$storage = new \phpbb\auth\provider\oauth\token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table, $this->auth_provider_oauth_state_table);  		$storage->clearAllTokens();  		return; @@ -633,7 +660,7 @@ class oauth extends \phpbb\auth\provider\base  		// Clear all tokens belonging to the user on this servce  		$service_name = 'auth.provider.oauth.service.' . strtolower($link_data['oauth_service']); -		$storage = new \phpbb\auth\provider\oauth\token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table); +		$storage = new \phpbb\auth\provider\oauth\token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table, $this->auth_provider_oauth_state_table);  		$storage->clearToken($service_name);  	}  } diff --git a/phpBB/phpbb/auth/provider/oauth/service/twitter.php b/phpBB/phpbb/auth/provider/oauth/service/twitter.php new file mode 100644 index 0000000000..06beac51e2 --- /dev/null +++ b/phpBB/phpbb/auth/provider/oauth/service/twitter.php @@ -0,0 +1,102 @@ +<?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\auth\provider\oauth\service; + +/** +* Twitter OAuth service +*/ +class twitter extends \phpbb\auth\provider\oauth\service\base +{ +	/** +	* phpBB config +	* +	* @var \phpbb\config\config +	*/ +	protected $config; + +	/** +	* phpBB request +	* +	* @var \phpbb\request\request_interface +	*/ +	protected $request; + +	/** +	* Constructor +	* +	* @param	\phpbb\config\config				$config +	* @param	\phpbb\request\request_interface	$request +	*/ +	public function __construct(\phpbb\config\config $config, \phpbb\request\request_interface $request) +	{ +		$this->config = $config; +		$this->request = $request; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function get_service_credentials() +	{ +		return array( +			'key'		=> $this->config['auth_oauth_twitter_key'], +			'secret'	=> $this->config['auth_oauth_twitter_secret'], +		); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function perform_auth_login() +	{ +		if (!($this->service_provider instanceof \OAuth\OAuth1\Service\Twitter)) +		{ +			throw new \phpbb\auth\provider\oauth\service\exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE'); +		} + +		$storage = $this->service_provider->getStorage(); +		$token = $storage->retrieveAccessToken('Twitter'); +		$tokensecret = $token->getRequestTokenSecret(); + +		// This was a callback request from twitter, get the token +		$this->service_provider->requestAccessToken( +			$this->request->variable('oauth_token', ''), +			$this->request->variable('oauth_verifier', ''), +			$tokensecret +		); + +		// Send a request with it +		$result = json_decode($this->service_provider->request('account/verify_credentials.json'), true); + +		// Return the unique identifier returned from twitter +		return $result['id']; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function perform_token_auth() +	{ +		if (!($this->service_provider instanceof \OAuth\OAuth1\Service\Twitter)) +		{ +			throw new \phpbb\auth\provider\oauth\service\exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE'); +		} + +		// Send a request with it +		$result = json_decode($this->service_provider->request('account/verify_credentials.json'), true); + +		// Return the unique identifier returned from twitter +		return $result['id']; +	} +} diff --git a/phpBB/phpbb/auth/provider/oauth/token_storage.php b/phpBB/phpbb/auth/provider/oauth/token_storage.php index 9b6afae255..b0c2fd0d62 100644 --- a/phpBB/phpbb/auth/provider/oauth/token_storage.php +++ b/phpBB/phpbb/auth/provider/oauth/token_storage.php @@ -17,6 +17,7 @@ use OAuth\OAuth1\Token\StdOAuth1Token;  use OAuth\Common\Token\TokenInterface;  use OAuth\Common\Storage\TokenStorageInterface;  use OAuth\Common\Storage\Exception\TokenNotFoundException; +use OAuth\Common\Storage\Exception\AuthorizationStateNotFoundException;  /**  * OAuth storage wrapper for phpbb's cache @@ -42,7 +43,14 @@ class token_storage implements TokenStorageInterface  	*  	* @var string  	*/ -	protected $auth_provider_oauth_table; +	protected $oauth_token_table; + +	/** +	* OAuth state table +	* +	* @var string +	*/ +	protected $oauth_state_table;  	/**  	* @var object|TokenInterface @@ -50,17 +58,24 @@ class token_storage implements TokenStorageInterface  	protected $cachedToken;  	/** +	* @var string +	*/ +	protected $cachedState; + +	/**  	* Creates token storage for phpBB.  	*  	* @param	\phpbb\db\driver\driver_interface	$db  	* @param	\phpbb\user		$user -	* @param	string			$auth_provider_oauth_table +	* @param	string			$oauth_token_table +	* @param	string			$oauth_state_table  	*/ -	public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\user $user, $auth_provider_oauth_table) +	public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\user $user, $oauth_token_table, $oauth_state_table)  	{  		$this->db = $db;  		$this->user = $user; -		$this->auth_provider_oauth_table = $auth_provider_oauth_table; +		$this->oauth_token_table = $oauth_token_table; +		$this->oauth_state_table = $oauth_state_table;  	}  	/** @@ -98,15 +113,31 @@ class token_storage implements TokenStorageInterface  		$this->cachedToken = $token;  		$data = array( -			'user_id'		=> (int) $this->user->data['user_id'], -			'provider'		=> $service,  			'oauth_token'	=> $this->json_encode_token($token), -			'session_id'	=> $this->user->data['session_id'],  		); -		$sql = 'INSERT INTO ' . $this->auth_provider_oauth_table . ' -			' . $this->db->sql_build_array('INSERT', $data); +		$sql = 'UPDATE ' . $this->oauth_token_table . ' +				SET ' . $this->db->sql_build_array('UPDATE', $data) . ' +				WHERE user_id = ' . (int) $this->user->data['user_id'] . ' +					' . ((int) $this->user->data['user_id'] === ANONYMOUS ? "AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'" : '') . " +					AND provider = '" . $this->db->sql_escape($service) . "'";  		$this->db->sql_query($sql); + +		if (!$this->db->sql_affectedrows()) +		{ +			$data = array( +				'user_id'		=> (int) $this->user->data['user_id'], +				'provider'		=> $service, +				'oauth_token'	=> $this->json_encode_token($token), +				'session_id'	=> $this->user->data['session_id'], +			); + +			$sql = 'INSERT INTO ' . $this->oauth_token_table . $this->db->sql_build_array('INSERT', $data); + +			$this->db->sql_query($sql); +		} + +		return $this;  	}  	/** @@ -143,7 +174,7 @@ class token_storage implements TokenStorageInterface  		$this->cachedToken = null; -		$sql = 'DELETE FROM ' . $this->auth_provider_oauth_table . ' +		$sql = 'DELETE FROM ' . $this->oauth_token_table . '  			WHERE user_id = ' . (int) $this->user->data['user_id'] . "  				AND provider = '" . $this->db->sql_escape($service) . "'"; @@ -153,6 +184,8 @@ class token_storage implements TokenStorageInterface  		}  		$this->db->sql_query($sql); + +		return $this;  	}  	/** @@ -162,7 +195,7 @@ class token_storage implements TokenStorageInterface  	{  		$this->cachedToken = null; -		$sql = 'DELETE FROM ' . $this->auth_provider_oauth_table . ' +		$sql = 'DELETE FROM ' . $this->oauth_token_table . '  			WHERE user_id = ' . (int) $this->user->data['user_id'];  		if ((int) $this->user->data['user_id'] === ANONYMOUS) @@ -171,6 +204,124 @@ class token_storage implements TokenStorageInterface  		}  		$this->db->sql_query($sql); + +		return $this; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function storeAuthorizationState($service, $state) +	{ +		$service = $this->get_service_name_for_db($service); + +		$this->cachedState = $state; + +		$data = array( +			'user_id'		=> (int) $this->user->data['user_id'], +			'provider'		=> $service, +			'oauth_state'	=> $state, +			'session_id'	=> $this->user->data['session_id'], +		); + +		$sql = 'INSERT INTO ' . $this->oauth_state_table . ' +			' . $this->db->sql_build_array('INSERT', $data); +		$this->db->sql_query($sql); + +		return $this; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function hasAuthorizationState($service) +	{ +		$service = $this->get_service_name_for_db($service); + +		if ($this->cachedState) +		{ +			return true; +		} + +		$data = array( +			'user_id'	=> (int) $this->user->data['user_id'], +			'provider'	=> $service, +		); + +		if ((int) $this->user->data['user_id'] === ANONYMOUS) +		{ +			$data['session_id']	= $this->user->data['session_id']; +		} + +		return (bool) $this->get_state_row($data); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function retrieveAuthorizationState($service) +	{ +		$service = $this->get_service_name_for_db($service); + +		if ($this->cachedState) +		{ +			return $this->cachedState; +		} + +		$data = array( +			'user_id'	=> (int) $this->user->data['user_id'], +			'provider'	=> $service, +		); + +		if ((int) $this->user->data['user_id'] === ANONYMOUS) +		{ +			$data['session_id']	= $this->user->data['session_id']; +		} + +		return $this->get_state_row($data); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function clearAuthorizationState($service) +	{ +		$service = $this->get_service_name_for_db($service); + +		$this->cachedState = null; + +		$sql = 'DELETE FROM ' . $this->oauth_state_table . ' +			WHERE user_id = ' . (int) $this->user->data['user_id'] . " +				AND provider = '" . $this->db->sql_escape($service) . "'"; + +		if ((int) $this->user->data['user_id'] === ANONYMOUS) +		{ +			$sql .= " AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'"; +		} + +		$this->db->sql_query($sql); + +		return $this; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function clearAllAuthorizationStates() +	{ +		$this->cachedState = null; + +		$sql = 'DELETE FROM ' . $this->oauth_state_table . ' +			WHERE user_id = ' . (int) $this->user->data['user_id']; + +		if ((int) $this->user->data['user_id'] === ANONYMOUS) +		{ +			$sql .= " AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'"; +		} + +		$this->db->sql_query($sql); + +		return $this;  	}  	/** @@ -185,7 +336,7 @@ class token_storage implements TokenStorageInterface  			return;  		} -		$sql = 'UPDATE ' . $this->auth_provider_oauth_table . ' +		$sql = 'UPDATE ' . $this->oauth_token_table . '  			SET ' . $this->db->sql_build_array('UPDATE', array(  					'user_id' => (int) $user_id  				)) . ' @@ -218,6 +369,29 @@ class token_storage implements TokenStorageInterface  	}  	/** +	* Checks to see if a state exists solely by the session_id of the user +	* +	* @param	string	$service	The name of the OAuth service +	* @return	bool	true if they have state, false if they don't +	*/ +	public function has_state_by_session($service) +	{ +		$service = $this->get_service_name_for_db($service); + +		if ($this->cachedState) +		{ +			return true; +		} + +		$data = array( +			'session_id'	=> $this->user->data['session_id'], +			'provider'		=> $service, +		); + +		return (bool) $this->get_state_row($data); +	} + +	/**  	* A helper function that performs the query for has access token functions  	*  	* @param	array	$data @@ -245,6 +419,23 @@ class token_storage implements TokenStorageInterface  		return $this->_retrieve_access_token($data);  	} +	public function retrieve_state_by_session($service) +	{ +		$service = $this->get_service_name_for_db($service); + +		if ($this->cachedState) +		{ +			return $this->cachedState; +		} + +		$data = array( +			'session_id'	=> $this->user->data['session_id'], +			'provider'	=> $service, +		); + +		return $this->_retrieve_state($data); +	} +  	/**  	* A helper function that performs the query for retrieve access token functions  	* Also checks if the token is a valid token @@ -276,6 +467,26 @@ class token_storage implements TokenStorageInterface  	}  	/** +	 * A helper function that performs the query for retrieve state functions +	 * +	 * @param	array	$data +	 * @return	mixed +	 * @throws \OAuth\Common\Storage\Exception\AuthorizationStateNotFoundException +	 */ +	protected function _retrieve_state($data) +	{ +		$row = $this->get_state_row($data); + +		if (!$row) +		{ +			throw new AuthorizationStateNotFoundException(); +		} + +		$this->cachedState = $row['oauth_state']; +		return $this->cachedState; +	} + +	/**  	* A helper function that performs the query for retrieving an access token  	*  	* @param	array	$data @@ -283,7 +494,24 @@ class token_storage implements TokenStorageInterface  	*/  	protected function get_access_token_row($data)  	{ -		$sql = 'SELECT oauth_token FROM ' . $this->auth_provider_oauth_table . ' +		$sql = 'SELECT oauth_token FROM ' . $this->oauth_token_table . ' +			WHERE ' . $this->db->sql_build_array('SELECT', $data); +		$result = $this->db->sql_query($sql); +		$row = $this->db->sql_fetchrow($result); +		$this->db->sql_freeresult($result); + +		return $row; +	} + +	/** +	 * A helper function that performs the query for retrieving a state +	 * +	 * @param	array	$data +	 * @return	mixed +	 */ +	protected function get_state_row($data) +	{ +		$sql = 'SELECT oauth_state FROM ' . $this->oauth_state_table . '  			WHERE ' . $this->db->sql_build_array('SELECT', $data);  		$result = $this->db->sql_query($sql);  		$row = $this->db->sql_fetchrow($result); diff --git a/phpBB/phpbb/avatar/driver/driver.php b/phpBB/phpbb/avatar/driver/driver.php index ad186635f2..45681f3e59 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 7a43b55852..3e4e7ff98b 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 75c384f31e..f5547c4bc6 100644 --- a/phpBB/phpbb/avatar/driver/local.php +++ b/phpBB/phpbb/avatar/driver/local.php @@ -174,13 +174,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 bec54897b2..3a88a432d1 100644 --- a/phpBB/phpbb/avatar/driver/remote.php +++ b/phpBB/phpbb/avatar/driver/remote.php @@ -92,38 +92,30 @@ 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; -			} - -			$width = ($width && $height) ? $width : $image_data[0]; -			$height = ($width && $height) ? $height : $image_data[1]; +			$error[] = 'UNABLE_GET_IMAGE_SIZE'; +			return false;  		} -		if ($width <= 0 || $height <= 0) +		if (!empty($image_data) && ($image_data['width'] <= 0 || $image_data['height'] <= 0))  		{  			$error[] = 'AVATAR_NO_SIZE';  			return false;  		} -		if (!class_exists('fileupload')) +		$width = ($width && $height) ? $width : $image_data['width']; +		$height = ($width && $height) ? $height : $image_data['height']; + +		if ($width <= 0 || $height <= 0)  		{ -			include($this->phpbb_root_path . 'includes/functions_upload.' . $this->php_ext); +			$error[] = 'AVATAR_NO_SIZE'; +			return false;  		} -		$types = \fileupload::image_types(); -		$extension = strtolower(\filespec::get_extension($url)); +		$types = \phpbb\files\upload::image_types(); +		$extension = strtolower(\phpbb\files\filespec::get_extension($url));  		// Check if this is actually an image  		if ($file_stream = @fopen($url, 'r')) @@ -172,15 +164,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 cb8dfcad4f..2640e1ad1e 100644 --- a/phpBB/phpbb/avatar/driver/upload.php +++ b/phpBB/phpbb/avatar/driver/upload.php @@ -19,9 +19,9 @@ namespace phpbb\avatar\driver;  class upload extends \phpbb\avatar\driver\driver  {  	/** -	* @var \phpbb\mimetype\guesser -	*/ -	protected $mimetype_guesser; +	 * @var \phpbb\filesystem\filesystem_interface +	 */ +	protected $filesystem;  	/**  	* @var \phpbb\event\dispatcher_interface @@ -29,24 +29,31 @@ class upload extends \phpbb\avatar\driver\driver  	protected $dispatcher;  	/** +	 * @var \phpbb\files\factory +	 */ +	protected $files_factory; + +	/**  	* Construct a driver object  	*  	* @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\mimetype\guesser $mimetype_guesser Mimetype guesser +	* @param \phpbb\filesystem\filesystem_interface $filesystem phpBB filesystem helper +	* @param \phpbb\path_helper $path_helper phpBB path helper  	* @param \phpbb\event\dispatcher_interface $dispatcher phpBB Event dispatcher object +	* @param \phpbb\files\factory $files_factory File classes factory  	* @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\event\dispatcher_interface $dispatcher, \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\event\dispatcher_interface $dispatcher, \phpbb\files\factory $files_factory, \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->dispatcher = $dispatcher; +		$this->files_factory = $files_factory;  		$this->cache = $cache;  	} @@ -92,19 +99,24 @@ class upload extends \phpbb\avatar\driver\driver  			return false;  		} -		if (!class_exists('fileupload')) -		{ -			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)); +		/** @var \phpbb\files\upload $upload */ +		$upload = $this->files_factory->get('upload') +			->set_error_prefix('AVATAR_') +			->set_allowed_extensions($this->allowed_extensions) +			->set_max_filesize($this->config['avatar_filesize']) +			->set_allowed_dimensions( +				$this->config['avatar_min_width'], +				$this->config['avatar_min_height'], +				$this->config['avatar_max_width'], +				$this->config['avatar_max_height']) +			->set_disallowed_content((isset($this->config['mime_triggers']) ? explode('|', $this->config['mime_triggers']) : false));  		$url = $request->variable('avatar_upload_url', '');  		$upload_file = $request->file('avatar_upload_file');  		if (!empty($upload_file['name']))  		{ -			$file = $upload->form_upload('avatar_upload_file', $this->mimetype_guesser); +			$file = $upload->handle_upload('files.types.form', 'avatar_upload_file');  		}  		else if (!empty($this->config['allow_avatar_remote_upload']) && !empty($url))  		{ @@ -134,7 +146,7 @@ class upload extends \phpbb\avatar\driver\driver  				return false;  			} -			$file = $upload->remote_upload($url, $this->mimetype_guesser); +			$file = $upload->handle_upload('files.types.remote', $url);  		}  		else  		{ @@ -292,6 +304,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 53c50eeda3..f4b3dc278d 100644 --- a/phpBB/phpbb/cache/driver/base.php +++ b/phpBB/phpbb/cache/driver/base.php @@ -49,7 +49,9 @@ abstract class base implements \phpbb\cache\driver\driver_interface  				$this->remove_dir($fileInfo->getPathname());  			}  			else if (strpos($filename, 'container_') === 0 || +				strpos($filename, 'autoload_') === 0 ||  				strpos($filename, 'url_matcher') === 0 || +				strpos($filename, 'url_generator') === 0 ||  				strpos($filename, 'sql_') === 0 ||  				strpos($filename, 'data_') === 0)  			{ @@ -95,14 +97,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; @@ -181,13 +183,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 fae4614039..a210d877f0 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_container; + +		$this->cache_dir = !is_null($cache_dir) ? $cache_dir : $phpbb_container->getParameter('core.cache_dir'); +		$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;  		} @@ -570,16 +576,17 @@ class file extends \phpbb\cache\driver\base  			if (function_exists('opcache_invalidate'))  			{ -				@opcache_invalidate($file); +				@opcache_invalidate($this->cache_file);  			} -			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..cc03804705 100644 --- a/phpBB/phpbb/cache/driver/memory.php +++ b/phpBB/phpbb/cache/driver/memory.php @@ -25,9 +25,9 @@ abstract class memory extends \phpbb\cache\driver\base  	*/  	function __construct()  	{ -		global $phpbb_root_path, $dbname, $table_prefix; +		global $phpbb_root_path, $dbname, $table_prefix, $phpbb_container; -		$this->cache_dir	= $phpbb_root_path . 'cache/'; +		$this->cache_dir	= $phpbb_container->getParameter('core.cache_dir');  		$this->key_prefix	= substr(md5($dbname . $table_prefix), 0, 8) . '_';  		if (!isset($this->extension) || !extension_loaded($this->extension)) @@ -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/cache/service.php b/phpBB/phpbb/cache/service.php index 56727c2ad5..a022c00bc6 100644 --- a/phpBB/phpbb/cache/service.php +++ b/phpBB/phpbb/cache/service.php @@ -141,6 +141,7 @@ class service  				$icons[$row['icons_id']]['img'] = $row['icons_url'];  				$icons[$row['icons_id']]['width'] = (int) $row['icons_width'];  				$icons[$row['icons_id']]['height'] = (int) $row['icons_height']; +				$icons[$row['icons_id']]['alt'] = ($row['icons_alt']) ? $row['icons_alt'] : '';  				$icons[$row['icons_id']]['display'] = (bool) $row['display_on_posting'];  			}  			$this->db->sql_freeresult($result); diff --git a/phpBB/phpbb/captcha/gd.php b/phpBB/phpbb/captcha/gd.php index 652df28f8a..e9538439c6 100644 --- a/phpBB/phpbb/captcha/gd.php +++ b/phpBB/phpbb/captcha/gd.php @@ -97,13 +97,12 @@ class gd  		if ($config['captcha_gd_3d_noise'])  		{ -			$xoffset = mt_rand(0,9);  			$noise_bitmaps = $this->captcha_noise_bg_bitmaps();  			for ($i = 0; $i < $code_len; ++$i)  			{  				$noise[$i] = new char_cube3d($noise_bitmaps, mt_rand(1, sizeof($noise_bitmaps['data']))); -				list($min, $max) = $noise[$i]->range(); +				$noise[$i]->range();  				//$box = $noise[$i]->dimensions($sizes[$i]);  			}  			$xoffset = 0; @@ -151,8 +150,6 @@ class gd  	*/  	function wave($img)  	{ -		global $config; -  		$period_x = mt_rand(12,18);  		$period_y = mt_rand(7,14);  		$amp_x = mt_rand(5,10); diff --git a/phpBB/phpbb/captcha/gd_wave.php b/phpBB/phpbb/captcha/gd_wave.php index d48fc753a5..f2ec4137d2 100644 --- a/phpBB/phpbb/captcha/gd_wave.php +++ b/phpBB/phpbb/captcha/gd_wave.php @@ -23,8 +23,6 @@ class gd_wave  	function execute($code, $seed)  	{ -		global $starttime; -  		// seed the random generator  		mt_srand($seed); @@ -77,7 +75,6 @@ class gd_wave  		// TODO  		$background = imagecolorallocate($img, mt_rand(155, 255), mt_rand(155, 255), mt_rand(155, 255));  		imagefill($img, 0, 0, $background); -		$black = imagecolorallocate($img, 0, 0, 0);  		$random = array();  		$fontcolors = array(); @@ -155,7 +152,7 @@ class gd_wave  		// rather than recalculating from absolute coordinates  		// What we cache into the $img_buffer contains the raised text coordinates.  		$img_pos_prev	= $img_buffer[0][0] = array($box['upper_left']['x'], $box['upper_left']['y']); -		$cur_height		= $prev_height = $this->wave_height(0, 0, $subdivision_factor); +		$prev_height = $this->wave_height(0, 0, $subdivision_factor);  		$full_x			= $plane_x * $subdivision_factor;  		$full_y			= $plane_y * $subdivision_factor; diff --git a/phpBB/phpbb/captcha/plugins/captcha_abstract.php b/phpBB/phpbb/captcha/plugins/captcha_abstract.php index 24ed7f939d..82b08704ff 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, $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; @@ -56,8 +56,6 @@ abstract class captcha_abstract  	function execute_demo()  	{ -		global $user; -  		$this->code = gen_rand_string_friendly(mt_rand(CAPTCHA_MIN_CHARS, CAPTCHA_MAX_CHARS));  		$this->seed = hexdec(substr(unique_id(), 4, 10)); @@ -117,7 +115,7 @@ abstract class captcha_abstract  	function get_demo_template($id)  	{ -		global $config, $user, $template, $phpbb_admin_path, $phpEx; +		global $config, $template, $request, $phpbb_admin_path, $phpEx;  		$variables = ''; @@ -125,7 +123,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]);  			}  		} @@ -153,7 +151,7 @@ abstract class captcha_abstract  	function garbage_collect($type)  	{ -		global $db, $config; +		global $db;  		$sql = 'SELECT DISTINCT c.session_id  			FROM ' . CONFIRM_TABLE . ' c @@ -193,9 +191,9 @@ abstract class captcha_abstract  	function validate()  	{ -		global $config, $db, $user; +		global $user; -		if (empty($user->lang)) +		if (!$user->is_setup())  		{  			$user->setup();  		} @@ -350,7 +348,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..831e5bcfdf 100644 --- a/phpBB/phpbb/captcha/plugins/gd.php +++ b/phpBB/phpbb/captcha/plugins/gd.php @@ -53,38 +53,31 @@ class gd extends captcha_abstract  	function acp_page($id, &$module)  	{ -		global $db, $user, $auth, $template; -		global $config, $phpbb_root_path, $phpbb_admin_path, $phpEx; +		global $user, $template, $phpbb_log, $request; +		global $config;  		$user->add_lang('acp/board'); -		$config_vars = array( -			'enable_confirm'		=> 'REG_ENABLE', -			'enable_post_confirm'	=> 'POST_ENABLE', -			'confirm_refresh'		=> 'CONFIRM_REFRESH', -			'captcha_gd'			=> 'CAPTCHA_GD', -		); -  		$module->tpl_name = 'captcha_gd_acp';  		$module->page_title = 'ACP_VC_SETTINGS';  		$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 +88,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 +102,7 @@ class gd extends captcha_abstract  	function execute_demo()  	{ -		global $config; +		global $config, $request;  		$config_old = $config; @@ -121,7 +114,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/gd_wave.php b/phpBB/phpbb/captcha/plugins/gd_wave.php index e1d44df778..bde46f8815 100644 --- a/phpBB/phpbb/captcha/plugins/gd_wave.php +++ b/phpBB/phpbb/captcha/plugins/gd_wave.php @@ -35,7 +35,7 @@ class gd_wave extends captcha_abstract  	function acp_page($id, &$module)  	{ -		global $config, $db, $template, $user; +		global $user;  		trigger_error($user->lang['CAPTCHA_NO_OPTIONS'] . adm_back_link($module->u_action));  	} diff --git a/phpBB/phpbb/captcha/plugins/qa.php b/phpBB/phpbb/captcha/plugins/qa.php index a9d133d8f2..9d481acc5d 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; @@ -135,9 +135,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);  	} @@ -334,10 +334,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( @@ -572,9 +571,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 . ' @@ -626,7 +625,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();  		} @@ -639,8 +640,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'); @@ -655,9 +655,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(); @@ -762,7 +762,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));  				}  			} @@ -848,7 +848,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)) @@ -859,9 +861,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..152709a9ea 100644 --- a/phpBB/phpbb/captcha/plugins/recaptcha.php +++ b/phpBB/phpbb/captcha/plugins/recaptcha.php @@ -18,12 +18,6 @@ class recaptcha extends captcha_abstract  	var $recaptcha_server = 'http://www.google.com/recaptcha/api';  	var $recaptcha_server_secure = 'https://www.google.com/recaptcha/api'; // class constants :( -	// We are opening a socket to port 80 of this host and send -	// the POST request asking for verification to the path specified here. -	var $recaptcha_verify_server = 'www.google.com'; -	var $recaptcha_verify_path = '/recaptcha/api/verify'; - -	var $challenge;  	var $response;  	/** @@ -37,12 +31,11 @@ class recaptcha extends captcha_abstract  	function init($type)  	{ -		global $config, $db, $user; +		global $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->response = $request->variable('g-recaptcha-response', '');  	}  	public function is_available() @@ -75,7 +68,7 @@ class recaptcha extends captcha_abstract  	function acp_page($id, &$module)  	{ -		global $config, $db, $template, $user; +		global $config, $template, $user, $phpbb_log, $request;  		$captcha_vars = array(  			'recaptcha_pubkey'				=> 'RECAPTCHA_PUBKEY', @@ -87,21 +80,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 +105,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);  			} @@ -151,7 +144,6 @@ class recaptcha extends captcha_abstract  			$template->assign_vars(array(  				'RECAPTCHA_SERVER'			=> $this->recaptcha_server,  				'RECAPTCHA_PUBKEY'			=> isset($config['recaptcha_pubkey']) ? $config['recaptcha_pubkey'] : '', -				'RECAPTCHA_ERRORGET'		=> '',  				'S_RECAPTCHA_AVAILABLE'		=> self::is_available(),  				'S_CONFIRM_CODE'			=> true,  				'S_TYPE'					=> $this->type, @@ -202,106 +194,25 @@ class recaptcha extends captcha_abstract  		}  	} -// Code from here on is based on recaptchalib.php -/* - * This is a PHP library that handles calling reCAPTCHA. - *	- Documentation and latest version - *		  http://recaptcha.net/plugins/php/ - *	- Get a reCAPTCHA API Key - *		  http://recaptcha.net/api/getkey - *	- Discussion group - *		  http://groups.google.com/group/recaptcha - * - * Copyright (c) 2007 reCAPTCHA -- http://recaptcha.net - * AUTHORS: - *   Mike Crawford - *   Ben Maurer - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -	/** -	* Submits an HTTP POST to a reCAPTCHA server -	* @param string $host -	* @param string $path -	* @param array $data -	* @param int port -	* @return array response -	*/ -	function _recaptcha_http_post($host, $path, $data, $port = 80) -	{ -		$req = $this->_recaptcha_qsencode ($data); - -		$http_request  = "POST $path HTTP/1.0\r\n"; -		$http_request .= "Host: $host\r\n"; -		$http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n"; -		$http_request .= "Content-Length: " . strlen($req) . "\r\n"; -		$http_request .= "User-Agent: reCAPTCHA/PHP/phpBB\r\n"; -		$http_request .= "\r\n"; -		$http_request .= $req; - -		$response = ''; -		if (false == ($fs = @fsockopen($host, $port, $errno, $errstr, 10))) -		{ -			trigger_error('RECAPTCHA_SOCKET_ERROR', E_USER_ERROR); -		} - -		fwrite($fs, $http_request); - -		while (!feof($fs)) -		{ -			// One TCP-IP packet -			$response .= fgets($fs, 1160); -		} -		fclose($fs); -		$response = explode("\r\n\r\n", $response, 2); - -		return $response; -	} -  	/**  	* Calls an HTTP POST function to verify if the user's guess was correct -	* @param array $extra_params an array of extra variables to post to the server -	* @return ReCaptchaResponse +	* +	* @return bool|string Returns false on success or error string on failure.  	*/ -	function recaptcha_check_answer($extra_params = array()) +	function recaptcha_check_answer()  	{  		global $config, $user;  		//discard spam submissions -		if ($this->challenge == null || strlen($this->challenge) == 0 || $this->response == null || strlen($this->response) == 0) +		if ($this->response == null || strlen($this->response) == 0)  		{  			return $user->lang['RECAPTCHA_INCORRECT'];  		} -		$response = $this->_recaptcha_http_post($this->recaptcha_verify_server, $this->recaptcha_verify_path, -			array( -				'privatekey'	=> $config['recaptcha_privkey'], -				'remoteip'		=> $user->ip, -				'challenge'		=> $this->challenge, -				'response'		=> $this->response -			) + $extra_params -		); - -		$answers = explode("\n", $response[1]); +		$recaptcha = new \ReCaptcha\ReCaptcha($config['recaptcha_privkey']); +		$result = $recaptcha->verify($this->response, $user->ip); -		if (trim($answers[0]) === 'true') +		if ($result->isSuccess())  		{  			$this->solved = true;  			return false; @@ -311,22 +222,4 @@ class recaptcha extends captcha_abstract  			return $user->lang['RECAPTCHA_INCORRECT'];  		}  	} - -	/** -	* Encodes the given data into a query string format -	* @param $data - array of string elements to be encoded -	* @return string - encoded request -	*/ -	function _recaptcha_qsencode($data) -	{ -		$req = ''; -		foreach ($data as $key => $value) -		{ -			$req .= $key . '=' . urlencode(stripslashes($value)) . '&'; -		} - -		// Cut the last '&' -		$req = substr($req, 0, strlen($req) - 1); -		return $req; -	}  } diff --git a/phpBB/phpbb/composer.json b/phpBB/phpbb/composer.json index 6b3888ef64..0b6299d6bc 100644 --- a/phpBB/phpbb/composer.json +++ b/phpBB/phpbb/composer.json @@ -22,11 +22,11 @@  		"classmap": [""]  	},  	"require": { -		"php": ">=5.3.3" +		"php": ">=5.4"  	},  	"extra": {  		"branch-alias": { -			"dev-master": "3.1.x-dev" +			"dev-master": "3.3.x-dev"  		}  	}  } diff --git a/phpBB/phpbb/console/application.php b/phpBB/phpbb/console/application.php index bc4897af18..dc9b8016b2 100644 --- a/phpBB/phpbb/console/application.php +++ b/phpBB/phpbb/console/application.php @@ -13,6 +13,7 @@  namespace phpbb\console; +use Symfony\Component\Console\Input\InputDefinition;  use Symfony\Component\Console\Shell;  use Symfony\Component\Console\Input\InputInterface;  use Symfony\Component\Console\Input\InputOption; @@ -26,18 +27,18 @@ class application extends \Symfony\Component\Console\Application  	protected $in_shell = false;  	/** -	* @var \phpbb\user User object +	* @var \phpbb\language\language User object  	*/ -	protected $user; +	protected $language;  	/** -	* @param string			$name		The name of the application -	* @param string			$version	The version of the application -	* @param \phpbb\user	$user		The user which runs the application (used for translation) +	* @param string						$name		The name of the application +	* @param string						$version	The version of the application +	* @param \phpbb\language\language	$language	The user which runs the application (used for translation)  	*/ -	public function __construct($name, $version, \phpbb\user $user) +	public function __construct($name, $version, \phpbb\language\language $language)  	{ -		$this->user = $user; +		$this->language = $language;  		parent::__construct($name, $version);  	} @@ -49,12 +50,7 @@ class application extends \Symfony\Component\Console\Application  	{  		$input_definition = parent::getDefaultInputDefinition(); -		$input_definition->addOption(new InputOption( -			'safe-mode', -			null, -			InputOption::VALUE_NONE, -			$this->user->lang('CLI_DESCRIPTION_OPTION_SAFE_MODE') -		)); +		$this->register_global_options($input_definition);  		return $input_definition;  	} @@ -76,12 +72,20 @@ class application extends \Symfony\Component\Console\Application  			return parent::getHelp();  		} -		$this->getDefinition()->addOption(new InputOption( -			'--shell', -			'-s', -			InputOption::VALUE_NONE, -			$this->user->lang('CLI_DESCRIPTION_OPTION_SHELL') -		)); +		try +		{ +			$definition = $this->getDefinition(); +			$definition->addOption(new InputOption( +				'--shell', +				'-s', +				InputOption::VALUE_NONE, +				$this->language->lang('CLI_DESCRIPTION_OPTION_SHELL') +			)); +		} +		catch (\LogicException $e) +		{ +			// Do nothing +		}  		return parent::getHelp();  	} @@ -117,4 +121,33 @@ class application extends \Symfony\Component\Console\Application  		return parent::doRun($input, $output);  	} + +	/** +	 * Register global options +	 * +	 * @param InputDefinition $definition An InputDefinition instance +	 */ +	protected function register_global_options(InputDefinition $definition) +	{ +		try +		{ +			$definition->addOption(new InputOption( +				'safe-mode', +				null, +				InputOption::VALUE_NONE, +				$this->language->lang('CLI_DESCRIPTION_OPTION_SAFE_MODE') +			)); + +			$definition->addOption(new InputOption( +				'env', +				'e', +				InputOption::VALUE_REQUIRED, +				$this->language->lang('CLI_DESCRIPTION_OPTION_ENV') +			)); +		} +		catch (\LogicException $e) +		{ +			// Do nothing +		} +	}  } diff --git a/phpBB/phpbb/console/command/command.php b/phpBB/phpbb/console/command/command.php index 638c989da2..0124c00d22 100644 --- a/phpBB/phpbb/console/command/command.php +++ b/phpBB/phpbb/console/command/command.php @@ -13,6 +13,10 @@  namespace phpbb\console\command; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +  abstract class command extends \Symfony\Component\Console\Command\Command  {  	/** @var \phpbb\user */ @@ -28,4 +32,45 @@ abstract class command extends \Symfony\Component\Console\Command\Command  		$this->user = $user;  		parent::__construct();  	} + +	/** +	 * Create a styled progress bar +	 * +	 * @param int             $max     Max value for the progress bar +	 * @param SymfonyStyle    $io      Symfony style output decorator +	 * @param OutputInterface $output  The output stream, used to print messages +	 * @param bool            $message Should we display message output under the progress bar? +	 * @return ProgressBar +	 */ +	public function create_progress_bar($max, SymfonyStyle $io, OutputInterface $output, $message = false) +	{ +		$progress = $io->createProgressBar($max); +		if ($output->getVerbosity() === OutputInterface::VERBOSITY_VERBOSE) +		{ +			$progress->setFormat('<info>[%percent:3s%%]</info> %message%'); +			$progress->setOverwrite(false); +		} +		else if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE) +		{ +			$progress->setFormat('<info>[%current:s%/%max:s%]</info><comment>[%elapsed%/%estimated%][%memory%]</comment> %message%'); +			$progress->setOverwrite(false); +		} +		else +		{ +			$io->newLine(2); +			$progress->setFormat( +				"    %current:s%/%max:s% %bar%  %percent:3s%%\n" . +				"        " . ($message ? '%message%' : '                ') . " %elapsed:6s%/%estimated:-6s% %memory:6s%\n"); +			$progress->setBarWidth(60); +		} + +		if (!defined('PHP_WINDOWS_VERSION_BUILD')) +		{ +			$progress->setEmptyBarCharacter('â–‘'); // light shade character \u2591 +			$progress->setProgressCharacter(''); +			$progress->setBarCharacter('â–“'); // dark shade character \u2593 +		} + +		return $progress; +	}  } diff --git a/phpBB/phpbb/console/command/cron/run.php b/phpBB/phpbb/console/command/cron/run.php index a9648fcd41..dea6493007 100644 --- a/phpBB/phpbb/console/command/cron/run.php +++ b/phpBB/phpbb/console/command/cron/run.php @@ -13,6 +13,7 @@  namespace phpbb\console\command\cron; +use phpbb\exception\runtime_exception;  use Symfony\Component\Console\Input\InputInterface;  use Symfony\Component\Console\Input\InputArgument;  use Symfony\Component\Console\Output\OutputInterface; @@ -93,8 +94,7 @@ class run extends \phpbb\console\command\command  		}  		else  		{ -			$output->writeln('<error>' . $this->user->lang('CRON_LOCK_ERROR') . '</error>'); -			return 1; +			throw new runtime_exception('CRON_LOCK_ERROR', array(), null, 1);  		}  	} @@ -165,8 +165,7 @@ class run extends \phpbb\console\command\command  		}  		else  		{ -			$output->writeln('<error>' . $this->user->lang('CRON_NO_SUCH_TASK', $task_name) . '</error>'); -			return 2; +			throw new runtime_exception('CRON_NO_SUCH_TASK', array( $task_name), null, 2);  		}  	}  } diff --git a/phpBB/phpbb/console/command/db/console_migrator_output_handler.php b/phpBB/phpbb/console/command/db/console_migrator_output_handler.php index b9741a3838..568b2646d4 100644 --- a/phpBB/phpbb/console/command/db/console_migrator_output_handler.php +++ b/phpBB/phpbb/console/command/db/console_migrator_output_handler.php @@ -13,8 +13,8 @@  namespace phpbb\console\command\db; +use phpbb\db\output_handler\migrator_output_handler_interface;  use phpbb\user; -use phpbb\db\migrator_output_handler_interface;  use Symfony\Component\Console\Output\OutputInterface;  class console_migrator_output_handler implements migrator_output_handler_interface diff --git a/phpBB/phpbb/console/command/db/list_command.php b/phpBB/phpbb/console/command/db/list_command.php new file mode 100644 index 0000000000..708107b592 --- /dev/null +++ b/phpBB/phpbb/console/command/db/list_command.php @@ -0,0 +1,73 @@ +<?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\console\command\db; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class list_command extends \phpbb\console\command\db\migration_command +{ +	protected function configure() +	{ +		$this +			->setName('db:list') +			->setDescription($this->user->lang('CLI_DESCRIPTION_DB_LIST')) +			->addOption( +				'available', +				'u', +				InputOption::VALUE_NONE, +				$this->user->lang('CLI_MIGRATIONS_ONLY_AVAILABLE') +			) +		; +	} + +	protected function execute(InputInterface $input, OutputInterface $output) +	{ +		$show_installed = !$input->getOption('available'); +		$installed = $available = array(); + +		foreach ($this->load_migrations() as $name) +		{ +			if ($this->migrator->migration_state($name) !== false) +			{ +				$installed[] = $name; +			} +			else +			{ +				$available[] = $name; +			} +		} + +		if ($show_installed) +		{ +			$output->writeln('<info>' . $this->user->lang('CLI_MIGRATIONS_INSTALLED') . $this->user->lang('COLON') . '</info>'); +			$output->writeln($installed); + +			if (empty($installed)) +			{ +				$output->writeln($this->user->lang('CLI_MIGRATIONS_EMPTY')); +			} + +			$output->writeln(''); +		} + +		$output->writeln('<info>' . $this->user->lang('CLI_MIGRATIONS_AVAILABLE') . $this->user->lang('COLON') . '</info>'); +		$output->writeln($available); + +		if (empty($available)) +		{ +			$output->writeln($this->user->lang('CLI_MIGRATIONS_EMPTY')); +		} +	} +} diff --git a/phpBB/phpbb/console/command/db/migrate.php b/phpBB/phpbb/console/command/db/migrate.php index 87c2a057d1..ae4211f7be 100644 --- a/phpBB/phpbb/console/command/db/migrate.php +++ b/phpBB/phpbb/console/command/db/migrate.php @@ -12,38 +12,31 @@  */  namespace phpbb\console\command\db; +use phpbb\db\output_handler\log_wrapper_migrator_output_handler;  use Symfony\Component\Console\Input\InputInterface;  use Symfony\Component\Console\Output\OutputInterface; -class migrate extends \phpbb\console\command\command +class migrate extends \phpbb\console\command\db\migration_command  { -	/** @var \phpbb\db\migrator */ -	protected $migrator; - -	/** @var \phpbb\extension\manager */ -	protected $extension_manager; - -	/** @var \phpbb\config\config */ -	protected $config; - -	/** @var \phpbb\cache\service */ -	protected $cache; -  	/** @var \phpbb\log\log */  	protected $log;  	/** @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; + +	/** @var \phpbb\language\language */ +	protected $language; + +	function __construct(\phpbb\user $user, \phpbb\language\language $language, \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->language = $language;  		$this->log = $log; +		$this->filesystem = $filesystem;  		$this->phpbb_root_path = $phpbb_root_path; -		parent::__construct($user); +		parent::__construct($user, $migrator, $extension_manager, $config, $cache);  		$this->user->add_lang(array('common', 'install', 'migrator'));  	} @@ -57,7 +50,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 log_wrapper_migrator_output_handler($this->language, new console_migrator_output_handler($this->user, $output), $this->phpbb_root_path . 'store/migrations_' . time() . '.log', $this->filesystem));  		$this->migrator->create_migrations_table(); @@ -87,21 +80,4 @@ class migrate extends \phpbb\console\command\command  		$this->finalise_update();  		$output->writeln($this->user->lang['DATABASE_UPDATE_COMPLETE']);  	} - -	protected function load_migrations() -	{ -		$migrations = $this->extension_manager -			->get_finder() -			->core_path('phpbb/db/migration/data/') -			->extension_directory('/migrations') -			->get_classes(); - -		$this->migrator->set_migrations($migrations); -	} - -	protected function finalise_update() -	{ -		$this->cache->purge(); -		$this->config->increment('assets_version', 1); -	}  } diff --git a/phpBB/phpbb/console/command/db/migration_command.php b/phpBB/phpbb/console/command/db/migration_command.php new file mode 100644 index 0000000000..b951560588 --- /dev/null +++ b/phpBB/phpbb/console/command/db/migration_command.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\console\command\db; + +abstract class migration_command extends \phpbb\console\command\command +{ +	/** @var \phpbb\db\migrator */ +	protected $migrator; + +	/** @var \phpbb\extension\manager */ +	protected $extension_manager; + +	/** @var \phpbb\config\config */ +	protected $config; + +	/** @var \phpbb\cache\service */ +	protected $cache; + +	function __construct(\phpbb\user $user, \phpbb\db\migrator $migrator, \phpbb\extension\manager $extension_manager, \phpbb\config\config $config, \phpbb\cache\service $cache) +	{ +		$this->migrator = $migrator; +		$this->extension_manager = $extension_manager; +		$this->config = $config; +		$this->cache = $cache; +		parent::__construct($user); +	} + +	protected function load_migrations() +	{ +		$migrations = $this->extension_manager +			->get_finder() +			->core_path('phpbb/db/migration/data/') +			->extension_directory('/migrations') +			->get_classes(); + +		$this->migrator->set_migrations($migrations); + +		return $this->migrator->get_migrations(); +	} + +	protected function finalise_update() +	{ +		$this->cache->purge(); +		$this->config->increment('assets_version', 1); +	} +} diff --git a/phpBB/phpbb/console/command/db/revert.php b/phpBB/phpbb/console/command/db/revert.php new file mode 100644 index 0000000000..3fa2e17515 --- /dev/null +++ b/phpBB/phpbb/console/command/db/revert.php @@ -0,0 +1,88 @@ +<?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\console\command\db; + +use phpbb\db\output_handler\log_wrapper_migrator_output_handler; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class revert extends \phpbb\console\command\db\migration_command +{ +	/** @var string phpBB root path */ +	protected $phpbb_root_path; + +	/** @var  \phpbb\filesystem\filesystem_interface */ +	protected $filesystem; + +	/** @var \phpbb\language\language */ +	protected $language; + +	function __construct(\phpbb\user $user, \phpbb\language\language $language, \phpbb\db\migrator $migrator, \phpbb\extension\manager $extension_manager, \phpbb\config\config $config, \phpbb\cache\service $cache, \phpbb\filesystem\filesystem_interface $filesystem, $phpbb_root_path) +	{ +		$this->filesystem = $filesystem; +		$this->language = $language; +		$this->phpbb_root_path = $phpbb_root_path; +		parent::__construct($user, $migrator, $extension_manager, $config, $cache); +		$this->user->add_lang(array('common', 'migrator')); +	} + +	protected function configure() +	{ +		$this +			->setName('db:revert') +			->setDescription($this->user->lang('CLI_DESCRIPTION_DB_REVERT')) +			->addArgument( +				'name', +				InputArgument::REQUIRED, +				$this->user->lang('CLI_MIGRATION_NAME') +			) +		; +	} + +	protected function execute(InputInterface $input, OutputInterface $output) +	{ +		$name = str_replace('/', '\\', $input->getArgument('name')); + +		$this->migrator->set_output_handler(new log_wrapper_migrator_output_handler($this->language, new console_migrator_output_handler($this->user, $output), $this->phpbb_root_path . 'store/migrations_' . time() . '.log', $this->filesystem)); + +		$this->cache->purge(); + +		if (!in_array($name, $this->load_migrations())) +		{ +			$output->writeln('<error>' . $this->user->lang('MIGRATION_NOT_VALID', $name) . '</error>'); +			return 1; +		} +		else if ($this->migrator->migration_state($name) === false) +		{ +			$output->writeln('<error>' . $this->user->lang('MIGRATION_NOT_INSTALLED', $name) . '</error>'); +			return 1; +		} + +		try +		{ +			while ($this->migrator->migration_state($name) !== false) +			{ +				$this->migrator->revert($name); +			} +		} +		catch (\phpbb\db\migration\exception $e) +		{ +			$output->writeln('<error>' . $e->getLocalisedMessage($this->user) . '</error>'); +			$this->finalise_update(); +			return 1; +		} + +		$this->finalise_update(); +	} +} diff --git a/phpBB/phpbb/console/command/reparser/list_all.php b/phpBB/phpbb/console/command/reparser/list_all.php new file mode 100644 index 0000000000..e42c3ac782 --- /dev/null +++ b/phpBB/phpbb/console/command/reparser/list_all.php @@ -0,0 +1,69 @@ +<?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\console\command\reparser; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class list_all extends \phpbb\console\command\command +{ +	/** +	* @var string[] Names of the reparser services +	*/ +	protected $reparser_names; + +	/** +	* Constructor +	* +	* @param \phpbb\user $user +	* @param \phpbb\di\service_collection $reparsers +	*/ +	public function __construct(\phpbb\user $user, \phpbb\di\service_collection $reparsers) +	{ +		parent::__construct($user); +		$this->reparser_names = array(); +		foreach ($reparsers as $name => $reparser) +		{ +			// Store the names without the "text_reparser." prefix +			$this->reparser_names[] = preg_replace('(^text_reparser\\.)', '', $name); +		} +	} + +	/** +	* Sets the command name and description +	* +	* @return null +	*/ +	protected function configure() +	{ +		$this +			->setName('reparser:list') +			->setDescription($this->user->lang('CLI_DESCRIPTION_REPARSER_LIST')) +		; +	} + +	/** +	* Executes the command reparser:reparse +	* +	* @param InputInterface $input +	* @param OutputInterface $output +	* @return integer +	*/ +	protected function execute(InputInterface $input, OutputInterface $output) +	{ +		$output->writeln('<info>' . implode(', ', $this->reparser_names) . '</info>'); + +		return 0; +	} +} diff --git a/phpBB/phpbb/console/command/reparser/reparse.php b/phpBB/phpbb/console/command/reparser/reparse.php new file mode 100644 index 0000000000..b10bd56a58 --- /dev/null +++ b/phpBB/phpbb/console/command/reparser/reparse.php @@ -0,0 +1,246 @@ +<?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\console\command\reparser; + +use phpbb\exception\runtime_exception; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class reparse extends \phpbb\console\command\command +{ +	/** +	* @var InputInterface +	*/ +	protected $input; + +	/** +	* @var SymfonyStyle +	*/ +	protected $io; + +	/** +	* @var OutputInterface +	*/ +	protected $output; + +	/** +	 * @var \phpbb\lock\db +	 */ +	protected $reparse_lock; + +	/** +	 * @var \phpbb\textreparser\manager +	 */ +	protected $reparser_manager; + +	/** +	* @var \phpbb\di\service_collection +	*/ +	protected $reparsers; + +	/** +	* @var array The reparser's last $current ID as values +	*/ +	protected $resume_data; + +	/** +	* Constructor +	* +	* @param \phpbb\user $user +	* @param \phpbb\lock\db $reparse_lock +	* @param \phpbb\textreparser\manager $reparser_manager +	* @param \phpbb\di\service_collection $reparsers +	*/ +	public function __construct(\phpbb\user $user, \phpbb\lock\db $reparse_lock, \phpbb\textreparser\manager $reparser_manager, \phpbb\di\service_collection $reparsers) +	{ +		require_once __DIR__ . '/../../../../includes/functions_content.php'; + +		$this->reparse_lock = $reparse_lock; +		$this->reparser_manager = $reparser_manager; +		$this->reparsers = $reparsers; +		parent::__construct($user); +	} + +	/** +	* Sets the command name and description +	* +	* @return null +	*/ +	protected function configure() +	{ +		$this +			->setName('reparser:reparse') +			->setDescription($this->user->lang('CLI_DESCRIPTION_REPARSER_REPARSE')) +			->addArgument('reparser-name', InputArgument::OPTIONAL, $this->user->lang('CLI_DESCRIPTION_REPARSER_REPARSE_ARG_1')) +			->addOption( +				'dry-run', +				null, +				InputOption::VALUE_NONE, +				$this->user->lang('CLI_DESCRIPTION_REPARSER_REPARSE_OPT_DRY_RUN') +			) +			->addOption( +				'resume', +				null, +				InputOption::VALUE_NONE, +				$this->user->lang('CLI_DESCRIPTION_REPARSER_REPARSE_OPT_RESUME') +			) +			->addOption( +				'range-min', +				null, +				InputOption::VALUE_REQUIRED, +				$this->user->lang('CLI_DESCRIPTION_REPARSER_REPARSE_OPT_RANGE_MIN'), +				1 +			) +			->addOption( +				'range-max', +				null, +				InputOption::VALUE_REQUIRED, +				$this->user->lang('CLI_DESCRIPTION_REPARSER_REPARSE_OPT_RANGE_MAX') +			) +			->addOption( +				'range-size', +				null, +				InputOption::VALUE_REQUIRED, +				$this->user->lang('CLI_DESCRIPTION_REPARSER_REPARSE_OPT_RANGE_SIZE'), +				100 +			); +		; +	} + +	/** +	* Executes the command reparser:reparse +	* +	* @param InputInterface $input +	* @param OutputInterface $output +	* @return integer +	*/ +	protected function execute(InputInterface $input, OutputInterface $output) +	{ +		$this->input = $input; +		$this->output = $output; +		$this->io = new SymfonyStyle($input, $output); + +		if (!$this->reparse_lock->acquire()) +		{ +			throw new runtime_exception('REPARSE_LOCK_ERROR', array(), null, 1); +		} + +		$name = $input->getArgument('reparser-name'); +		if (isset($name)) +		{ +			// Allow "post_text" to be an alias for "text_reparser.post_text" +			if (!isset($this->reparsers[$name])) +			{ +				$name = 'text_reparser.' . $name; +			} +			$this->reparse($name); +		} +		else +		{ +			foreach ($this->reparsers as $name => $service) +			{ +				$this->reparse($name); +			} +		} + +		$this->io->success($this->user->lang('CLI_REPARSER_REPARSE_SUCCESS')); + +		$this->reparse_lock->release(); + +		return 0; +	} + +	/** +	* Get an option value, adjusted for given reparser +	* +	* Will use the last saved value if --resume is set and the option was not specified +	* on the command line +	* +	* @param  string  $option_name   Option name +	* @return integer +	*/ +	protected function get_option($option_name) +	{ +		// Return the option from the resume_data if applicable +		if ($this->input->getOption('resume') && isset($this->resume_data[$option_name]) && !$this->input->hasParameterOption('--' . $option_name)) +		{ +			return $this->resume_data[$option_name]; +		} + +		return $this->input->getOption($option_name); +	} + +	/** +	* Reparse all text handled by given reparser within given range +	* +	* @param string $name Reparser name +	*/ +	protected function reparse($name) +	{ +		$reparser = $this->reparsers[$name]; +		$this->resume_data = $this->reparser_manager->get_resume_data($name); +		if ($this->input->getOption('dry-run')) +		{ +			$reparser->disable_save(); +		} +		else +		{ +			$reparser->enable_save(); +		} + +		// Start at range-max if specified or at the highest ID otherwise +		$max  = $this->get_option('range-max'); +		$min  = $this->get_option('range-min'); +		$size = $this->get_option('range-size'); + +		// range-max has no default value, it must be computed for each reparser +		if ($max == null) +		{ +			$max = $reparser->get_max_id(); +		} + +		if ($max < $min) +		{ +			return; +		} + +		$this->io->section($this->user->lang('CLI_REPARSER_REPARSE_REPARSING', preg_replace('(^text_reparser\\.)', '', $name), $min, $max)); + +		$progress = $this->create_progress_bar($max, $this->io, $this->output, true); +		$progress->setMessage($this->user->lang('CLI_REPARSER_REPARSE_REPARSING_START', preg_replace('(^text_reparser\\.)', '', $name))); +		$progress->start(); + +		// Start from $max and decrement $current by $size until we reach $min +		$current = $max; +		while ($current >= $min) +		{ +			$start = max($min, $current + 1 - $size); +			$end   = max($min, $current); + +			$progress->setMessage($this->user->lang('CLI_REPARSER_REPARSE_REPARSING', preg_replace('(^text_reparser\\.)', '', $name), $start, $end)); +			$reparser->reparse_range($start, $end); + +			$current = $start - 1; +			$progress->setProgress($max + 1 - $start); + +			$this->reparser_manager->update_resume_data($name, $min, $current, $size, !$this->input->getOption('dry-run')); +		} +		$progress->finish(); + +		$this->io->newLine(2); +	} +} diff --git a/phpBB/phpbb/console/command/thumbnail/delete.php b/phpBB/phpbb/console/command/thumbnail/delete.php new file mode 100644 index 0000000000..cfa9891fbc --- /dev/null +++ b/phpBB/phpbb/console/command/thumbnail/delete.php @@ -0,0 +1,153 @@ +<?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\console\command\thumbnail; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class delete extends \phpbb\console\command\command +{ +	/** +	* @var \phpbb\db\driver\driver_interface +	*/ +	protected $db; + +	/** +	* phpBB root path +	* @var string +	*/ +	protected $phpbb_root_path; + +	/** +	* Constructor +	* +	* @param \phpbb\user $user The user object (used to get language information) +	* @param \phpbb\db\driver\driver_interface $db Database connection +	* @param string $phpbb_root_path Root path +	*/ +	public function __construct(\phpbb\user $user, \phpbb\db\driver\driver_interface $db, $phpbb_root_path) +	{ +		$this->db = $db; +		$this->phpbb_root_path = $phpbb_root_path; + +		parent::__construct($user); +	} + +	/** +	* Sets the command name and description +	* +	* @return null +	*/ +	protected function configure() +	{ +		$this +			->setName('thumbnail:delete') +			->setDescription($this->user->lang('CLI_DESCRIPTION_THUMBNAIL_DELETE')) +		; +	} + +	/** +	* Executes the command thumbnail:delete. +	* +	* Deletes all existing thumbnails and updates the database accordingly. +	* +	* @param InputInterface $input The input stream used to get the argument and verbose option. +	* @param OutputInterface $output The output stream, used for printing verbose-mode and error information. +	* +	* @return int 0 if all is ok, 1 if a thumbnail couldn't be deleted. +	*/ +	protected function execute(InputInterface $input, OutputInterface $output) +	{ +		$io = new SymfonyStyle($input, $output); + +		$io->section($this->user->lang('CLI_THUMBNAIL_DELETING')); + +		$sql = 'SELECT COUNT(*) AS nb_missing_thumbnails +			FROM ' . ATTACHMENTS_TABLE . ' +			WHERE thumbnail = 1'; +		$result = $this->db->sql_query($sql); +		$nb_missing_thumbnails = (int) $this->db->sql_fetchfield('nb_missing_thumbnails'); +		$this->db->sql_freeresult($result); + +		if ($nb_missing_thumbnails === 0) +		{ +			$io->warning($this->user->lang('CLI_THUMBNAIL_NOTHING_TO_DELETE')); +			return 0; +		} + +		$sql = 'SELECT attach_id, physical_filename, extension, real_filename, mimetype +			FROM ' . ATTACHMENTS_TABLE . ' +			WHERE thumbnail = 1'; +		$result = $this->db->sql_query($sql); + +		$progress = $this->create_progress_bar($nb_missing_thumbnails, $io, $output); + +		$progress->setMessage($this->user->lang('CLI_THUMBNAIL_DELETING')); + +		$progress->start(); + +		$thumbnail_deleted = array(); +		$return = 0; +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$thumbnail_path = $this->phpbb_root_path . 'files/thumb_' . $row['physical_filename']; + +			if (@unlink($thumbnail_path)) +			{ +				$thumbnail_deleted[] = $row['attach_id']; + +				if (sizeof($thumbnail_deleted) === 250) +				{ +					$this->commit_changes($thumbnail_deleted); +					$thumbnail_deleted = array(); +				} + +				$progress->setMessage($this->user->lang('CLI_THUMBNAIL_DELETED', $row['real_filename'], $row['physical_filename'])); +			} +			else +			{ +				$return = 1; +				$progress->setMessage('<error>' . $this->user->lang('CLI_THUMBNAIL_SKIPPED', $row['real_filename'], $row['physical_filename']) . '</error>'); +			} + +			$progress->advance(); +		} +		$this->db->sql_freeresult($result); + +		if (!empty($thumbnail_deleted)) +		{ +			$this->commit_changes($thumbnail_deleted); +		} + +		$progress->finish(); + +		$io->newLine(2); +		$io->success($this->user->lang('CLI_THUMBNAIL_DELETING_DONE')); + +		return $return; +	} + +	/** +	* Commits the changes to the database +	* +	* @param array $thumbnail_deleted +	*/ +	protected function commit_changes(array $thumbnail_deleted) +	{ +		$sql = 'UPDATE ' . ATTACHMENTS_TABLE . ' +				SET thumbnail = 0 +				WHERE ' . $this->db->sql_in_set('attach_id', $thumbnail_deleted); +		$this->db->sql_query($sql); +	} +} diff --git a/phpBB/phpbb/console/command/thumbnail/generate.php b/phpBB/phpbb/console/command/thumbnail/generate.php new file mode 100644 index 0000000000..64f7555336 --- /dev/null +++ b/phpBB/phpbb/console/command/thumbnail/generate.php @@ -0,0 +1,179 @@ +<?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\console\command\thumbnail; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class generate extends \phpbb\console\command\command +{ +	/** +	* @var \phpbb\db\driver\driver_interface +	*/ +	protected $db; + +	/** +	* @var \phpbb\cache\service +	*/ +	protected $cache; + +	/** +	* phpBB root path +	* @var string +	*/ +	protected $phpbb_root_path; + +	/** +	* PHP extension. +	* +	* @var string +	*/ +	protected $php_ext; + +	/** +	* Constructor +	* +	* @param \phpbb\user $user The user object (used to get language information) +	* @param \phpbb\db\driver\driver_interface $db Database connection +	* @param \phpbb\cache\service $cache The cache service +	* @param string $phpbb_root_path Root path +	* @param string $php_ext PHP extension +	*/ +	public function __construct(\phpbb\user $user, \phpbb\db\driver\driver_interface $db, \phpbb\cache\service $cache, $phpbb_root_path, $php_ext) +	{ +		$this->db = $db; +		$this->cache = $cache; +		$this->phpbb_root_path = $phpbb_root_path; +		$this->php_ext = $php_ext; + +		parent::__construct($user); +	} + +	/** +	* Sets the command name and description +	* +	* @return null +	*/ +	protected function configure() +	{ +		$this +			->setName('thumbnail:generate') +			->setDescription($this->user->lang('CLI_DESCRIPTION_THUMBNAIL_GENERATE')) +		; +	} + +	/** +	* Executes the command thumbnail:generate. +	* +	* Generate a thumbnail for all attachments which need one and don't have it yet. +	* +	* @param InputInterface $input The input stream used to get the argument and verboe option. +	* @param OutputInterface $output The output stream, used for printing verbose-mode and error information. +	* +	* @return int 0. +	*/ +	protected function execute(InputInterface $input, OutputInterface $output) +	{ +		$io = new SymfonyStyle($input, $output); + +		$io->section($this->user->lang('CLI_THUMBNAIL_GENERATING')); + +		$sql = 'SELECT COUNT(*) AS nb_missing_thumbnails +			FROM ' . ATTACHMENTS_TABLE . ' +			WHERE thumbnail = 0'; +		$result = $this->db->sql_query($sql); +		$nb_missing_thumbnails = (int) $this->db->sql_fetchfield('nb_missing_thumbnails'); +		$this->db->sql_freeresult($result); + +		if ($nb_missing_thumbnails === 0) +		{ +			$io->warning($this->user->lang('CLI_THUMBNAIL_NOTHING_TO_GENERATE')); +			return 0; +		} + +		$extensions = $this->cache->obtain_attach_extensions(true); + +		$sql = 'SELECT attach_id, physical_filename, extension, real_filename, mimetype +			FROM ' . ATTACHMENTS_TABLE . ' +			WHERE thumbnail = 0'; +		$result = $this->db->sql_query($sql); + +		if (!function_exists('create_thumbnail')) +		{ +			require($this->phpbb_root_path . 'includes/functions_posting.' . $this->php_ext); +		} + +		$progress = $this->create_progress_bar($nb_missing_thumbnails, $io, $output); + +		$progress->setMessage($this->user->lang('CLI_THUMBNAIL_GENERATING')); + +		$progress->start(); + +		$thumbnail_created = array(); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if (isset($extensions[$row['extension']]['display_cat']) && $extensions[$row['extension']]['display_cat'] == ATTACHMENT_CATEGORY_IMAGE) +			{ +				$source = $this->phpbb_root_path . 'files/' . $row['physical_filename']; +				$destination = $this->phpbb_root_path . 'files/thumb_' . $row['physical_filename']; + +				if (create_thumbnail($source, $destination, $row['mimetype'])) +				{ +					$thumbnail_created[] = (int) $row['attach_id']; + +					if (count($thumbnail_created) === 250) +					{ +						$this->commit_changes($thumbnail_created); +						$thumbnail_created = array(); +					} + +					$progress->setMessage($this->user->lang('CLI_THUMBNAIL_GENERATED', $row['real_filename'], $row['physical_filename'])); +				} +				else +				{ +					$progress->setMessage('<info>' . $this->user->lang('CLI_THUMBNAIL_SKIPPED', $row['real_filename'], $row['physical_filename']) . '</info>'); +				} +			} + +			$progress->advance(); +		} +		$this->db->sql_freeresult($result); + +		if (!empty($thumbnail_created)) +		{ +			$this->commit_changes($thumbnail_created); +		} + +		$progress->finish(); + +		$io->newLine(2); +		$io->success($this->user->lang('CLI_THUMBNAIL_GENERATING_DONE')); + +		return 0; +	} + +	/** +	* Commits the changes to the database +	* +	* @param array $thumbnail_created +	*/ +	protected function commit_changes(array $thumbnail_created) +	{ +		$sql = 'UPDATE ' . ATTACHMENTS_TABLE . ' +				SET thumbnail = 1 +				WHERE ' . $this->db->sql_in_set('attach_id', $thumbnail_created); +		$this->db->sql_query($sql); +	} +} diff --git a/phpBB/phpbb/console/command/thumbnail/recreate.php b/phpBB/phpbb/console/command/thumbnail/recreate.php new file mode 100644 index 0000000000..382da290bf --- /dev/null +++ b/phpBB/phpbb/console/command/thumbnail/recreate.php @@ -0,0 +1,72 @@ +<?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\console\command\thumbnail; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Output\OutputInterface; + +class recreate extends \phpbb\console\command\command +{ +	/** +	* Sets the command name and description +	* +	* @return null +	*/ +	protected function configure() +	{ +		$this +			->setName('thumbnail:recreate') +			->setDescription($this->user->lang('CLI_DESCRIPTION_THUMBNAIL_RECREATE')) +		; +	} + +	/** +	* Executes the command thumbnail:recreate. +	* +	* This command is a "macro" to execute thumbnail:delete and then thumbnail:generate. +	* +	* @param InputInterface $input The input stream used to get the argument and verboe option. +	* @param OutputInterface $output The output stream, used for printing verbose-mode and error information. +	* +	* @return int 0 if all is ok, 1 if a thumbnail couldn't be deleted. +	*/ +	protected function execute(InputInterface $input, OutputInterface $output) +	{ +		$parameters = array( +			'command' => 'thumbnail:delete' +		); + +		if ($input->getOption('verbose')) +		{ +			$parameters['-' . str_repeat('v', $output->getVerbosity() - 1)] = true; +		} + +		$this->getApplication()->setAutoExit(false); + +		$input_delete = new ArrayInput($parameters); +		$return = $this->getApplication()->run($input_delete, $output); + +		if ($return === 0) +		{ +			$parameters['command'] = 'thumbnail:generate'; + +			$input_create = new ArrayInput($parameters); +			$return = $this->getApplication()->run($input_create, $output); +		} + +		$this->getApplication()->setAutoExit(true); + +		return $return; +	} +} diff --git a/phpBB/phpbb/console/command/user/activate.php b/phpBB/phpbb/console/command/user/activate.php new file mode 100644 index 0000000000..9c85718b4c --- /dev/null +++ b/phpBB/phpbb/console/command/user/activate.php @@ -0,0 +1,218 @@ +<?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\console\command\user; + +use phpbb\config\config; +use phpbb\console\command\command; +use phpbb\db\driver\driver_interface; +use phpbb\language\language; +use phpbb\log\log_interface; +use phpbb\notification\manager; +use phpbb\user; +use phpbb\user_loader; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class activate extends command +{ +	/** @var driver_interface */ +	protected $db; + +	/** @var config */ +	protected $config; + +	/** @var language */ +	protected $language; + +	/** @var log_interface */ +	protected $log; + +	/** @var manager */ +	protected $notifications; + +	/** @var user_loader */ +	protected $user_loader; + +	/** +	 * phpBB root path +	 * +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * PHP extension. +	 * +	 * @var string +	 */ +	protected $php_ext; + +	/** +	 * Construct method +	 * +	 * @param user             $user +	 * @param driver_interface $db +	 * @param config           $config +	 * @param language         $language +	 * @param log_interface    $log +	 * @param manager          $notifications +	 * @param user_loader      $user_loader +	 * @param string           $phpbb_root_path +	 * @param string           $php_ext +	 */ +	public function __construct(user $user, driver_interface $db, config $config, language $language, log_interface $log, manager $notifications, user_loader $user_loader, $phpbb_root_path, $php_ext) +	{ +		$this->db = $db; +		$this->config = $config; +		$this->language = $language; +		$this->log = $log; +		$this->notifications = $notifications; +		$this->user_loader = $user_loader; +		$this->phpbb_root_path = $phpbb_root_path; +		$this->php_ext = $php_ext; + +		$this->language->add_lang('acp/users'); +		parent::__construct($user); +	} + +	/** +	 * Sets the command name and description +	 * +	 * @return null +	 */ +	protected function configure() +	{ +		$this +			->setName('user:activate') +			->setDescription($this->language->lang('CLI_DESCRIPTION_USER_ACTIVATE')) +			->setHelp($this->language->lang('CLI_HELP_USER_ACTIVATE')) +			->addArgument( +				'username', +				InputArgument::REQUIRED, +				$this->language->lang('CLI_DESCRIPTION_USER_ACTIVATE_USERNAME') +			) +			->addOption( +				'deactivate', +				'd', +				InputOption::VALUE_NONE, +				$this->language->lang('CLI_DESCRIPTION_USER_ACTIVATE_DEACTIVATE') +			) +			->addOption( +				'send-email', +				null, +				InputOption::VALUE_NONE, +				$this->language->lang('CLI_DESCRIPTION_USER_ADD_OPTION_NOTIFY') +			) +		; +	} + +	/** +	 * Executes the command user:activate +	 * +	 * Activate (or deactivate) a user account +	 * +	 * @param InputInterface  $input  The input stream used to get the options +	 * @param OutputInterface $output The output stream, used to print messages +	 * +	 * @return int 0 if all is well, 1 if any errors occurred +	 */ +	protected function execute(InputInterface $input, OutputInterface $output) +	{ +		$io = new SymfonyStyle($input, $output); + +		$name = $input->getArgument('username'); +		$mode = ($input->getOption('deactivate')) ? 'deactivate' : 'activate'; + +		$user_id  = $this->user_loader->load_user_by_username($name); +		$user_row = $this->user_loader->get_user($user_id); + +		if ($user_row['user_id'] == ANONYMOUS) +		{ +			$io->error($this->language->lang('NO_USER')); +			return 1; +		} + +		// Check if the user is already active (or inactive) +		if ($mode == 'activate' && $user_row['user_type'] != USER_INACTIVE) +		{ +			$io->error($this->language->lang('CLI_DESCRIPTION_USER_ACTIVATE_ACTIVE')); +			return 1; +		} +		else if ($mode == 'deactivate' && $user_row['user_type'] == USER_INACTIVE) +		{ +			$io->error($this->language->lang('CLI_DESCRIPTION_USER_ACTIVATE_INACTIVE')); +			return 1; +		} + +		// Activate the user account +		if (!function_exists('user_active_flip')) +		{ +			require($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); +		} + +		user_active_flip($mode, $user_row['user_id']); + +		// Notify the user upon activation +		if ($mode == 'activate' && $this->config['require_activation'] == USER_ACTIVATION_ADMIN) +		{ +			$this->send_notification($user_row, $input); +		} + +		// Log and display the result +		$msg = ($mode == 'activate') ? 'USER_ADMIN_ACTIVATED' : 'USER_ADMIN_DEACTIVED'; +		$log = ($mode == 'activate') ? 'LOG_USER_ACTIVE' : 'LOG_USER_INACTIVE'; + +		$this->log->add('admin', ANONYMOUS, '', $log, false, array($user_row['username'])); +		$this->log->add('user', ANONYMOUS, '', $log . '_USER', false, array( +			'reportee_id' => $user_row['user_id'] +		)); + +		$io->success($this->language->lang($msg)); + +		return 0; +	} + +	/** +	 * Send account activation notification to user +	 * +	 * @param array           $user_row The user data array +	 * @param InputInterface  $input    The input stream used to get the options +	 * @return null +	 */ +	protected function send_notification($user_row, InputInterface $input) +	{ +		$this->notifications->delete_notifications('notification.type.admin_activate_user', $user_row['user_id']); + +		if ($input->getOption('send-email')) +		{ +			if (!class_exists('messenger')) +			{ +				require($this->phpbb_root_path . 'includes/functions_messenger.' . $this->php_ext); +			} + +			$messenger = new \messenger(false); +			$messenger->template('admin_welcome_activated', $user_row['user_lang']); +			$messenger->set_addresses($user_row); +			$messenger->anti_abuse_headers($this->config, $this->user); +			$messenger->assign_vars(array( +					'USERNAME'	=> htmlspecialchars_decode($user_row['username'])) +			); + +			$messenger->send(NOTIFY_EMAIL); +		} +	} +} diff --git a/phpBB/phpbb/console/command/user/add.php b/phpBB/phpbb/console/command/user/add.php new file mode 100644 index 0000000000..c60a059251 --- /dev/null +++ b/phpBB/phpbb/console/command/user/add.php @@ -0,0 +1,334 @@ +<?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\console\command\user; + +use phpbb\config\config; +use phpbb\console\command\command; +use phpbb\db\driver\driver_interface; +use phpbb\exception\runtime_exception; +use phpbb\language\language; +use phpbb\passwords\manager; +use phpbb\user; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Style\SymfonyStyle; + +class add extends command +{ +	/** @var array Array of interactively acquired options */ +	protected $data; + +	/** @var driver_interface */ +	protected $db; + +	/** @var config */ +	protected $config; + +	/** @var language */ +	protected $language; + +	/** @var manager */ +	protected $password_manager; + +	/** +	 * phpBB root path +	 * +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * PHP extension. +	 * +	 * @var string +	 */ +	protected $php_ext; + +	/** +	 * Construct method +	 * +	 * @param user             $user +	 * @param driver_interface $db +	 * @param config           $config +	 * @param language         $language +	 * @param manager          $password_manager +	 * @param string           $phpbb_root_path +	 * @param string           $php_ext +	 */ +	public function __construct(user $user, driver_interface $db, config $config, language $language, manager $password_manager, $phpbb_root_path, $php_ext) +	{ +		$this->db = $db; +		$this->config = $config; +		$this->language = $language; +		$this->password_manager = $password_manager; +		$this->phpbb_root_path = $phpbb_root_path; +		$this->php_ext = $php_ext; + +		$this->language->add_lang('ucp'); +		parent::__construct($user); +	} + +	/** +	 * Sets the command name and description +	 * +	 * @return null +	 */ +	protected function configure() +	{ +		$this +			->setName('user:add') +			->setDescription($this->language->lang('CLI_DESCRIPTION_USER_ADD')) +			->setHelp($this->language->lang('CLI_HELP_USER_ADD')) +			->addOption( +				'username', +				'U', +				InputOption::VALUE_REQUIRED, +				$this->language->lang('CLI_DESCRIPTION_USER_ADD_OPTION_USERNAME') +			) +			->addOption( +				'password', +				'P', +				InputOption::VALUE_REQUIRED, +				$this->language->lang('CLI_DESCRIPTION_USER_ADD_OPTION_PASSWORD') +			) +			->addOption( +				'email', +				'E', +				InputOption::VALUE_REQUIRED, +				$this->language->lang('CLI_DESCRIPTION_USER_ADD_OPTION_EMAIL') +			) +			->addOption( +				'send-email', +				null, +				InputOption::VALUE_NONE, +				$this->language->lang('CLI_DESCRIPTION_USER_ADD_OPTION_NOTIFY') +			) +		; +	} + +	/** +	 * Executes the command user:add +	 * +	 * Adds a new user to the database. If options are not provided, it will ask for the username, password and email. +	 * User is added to the registered user group. Language and timezone default to $config settings. +	 * +	 * @param InputInterface  $input  The input stream used to get the options +	 * @param OutputInterface $output The output stream, used to print messages +	 * +	 * @return int 0 if all is well, 1 if any errors occurred +	 */ +	protected function execute(InputInterface $input, OutputInterface $output) +	{ +		$io = new SymfonyStyle($input, $output); + +		try +		{ +			$this->validate_user_data(); +			$group_id = $this->get_group_id(); +		} +		catch (runtime_exception $e) +		{ +			$io->error($e->getMessage()); +			return 1; +		} + +		$user_row = array( +			'username'      => $this->data['username'], +			'user_password' => $this->password_manager->hash($this->data['new_password']), +			'user_email'    => $this->data['email'], +			'group_id'      => $group_id, +			'user_timezone' => $this->config['board_timezone'], +			'user_lang'     => $this->config['default_lang'], +			'user_type'     => USER_NORMAL, +			'user_regdate'  => time(), +		); + +		$user_id = (int) user_add($user_row); + +		if (!$user_id) +		{ +			$io->error($this->language->lang('AUTH_NO_PROFILE_CREATED')); +			return 1; +		} + +		if ($input->getOption('send-email') && $this->config['email_enable']) +		{ +			$this->send_activation_email($user_id); +		} + +		$io->success($this->language->lang('CLI_USER_ADD_SUCCESS', $this->data['username'])); + +		return 0; +	} + +	/** +	 * Interacts with the user. +	 * +	 * @param InputInterface  $input  An InputInterface instance +	 * @param OutputInterface $output An OutputInterface instance +	 */ +	protected function interact(InputInterface $input, OutputInterface $output) +	{ +		$helper = $this->getHelper('question'); + +		$this->data = array( +			'username'     => $input->getOption('username'), +			'new_password' => $input->getOption('password'), +			'email'        => $input->getOption('email'), +		); + +		if (!$this->data['username']) +		{ +			$question = new Question($this->ask_user('USERNAME')); +			$this->data['username'] = $helper->ask($input, $output, $question); +		} + +		if (!$this->data['new_password']) +		{ +			$question = new Question($this->ask_user('PASSWORD')); +			$question->setValidator(function ($value) use ($helper, $input, $output) { +				$question = new Question($this->ask_user('CONFIRM_PASSWORD')); +				$question->setHidden(true); +				if ($helper->ask($input, $output, $question) != $value) +				{ +					throw new runtime_exception($this->language->lang('NEW_PASSWORD_ERROR')); +				} +				return $value; +			}); +			$question->setHidden(true); +			$question->setMaxAttempts(5); + +			$this->data['new_password'] = $helper->ask($input, $output, $question); +		} + +		if (!$this->data['email']) +		{ +			$question = new Question($this->ask_user('EMAIL_ADDRESS')); +			$this->data['email'] = $helper->ask($input, $output, $question); +		} +	} + +	/** +	 * Validate the submitted user data +	 * +	 * @throws runtime_exception if any data fails validation +	 * @return null +	 */ +	protected function validate_user_data() +	{ +		if (!function_exists('validate_data')) +		{ +			require($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); +		} + +		$error = validate_data($this->data, array( +			'username'     => array( +				array('string', false, $this->config['min_name_chars'], $this->config['max_name_chars']), +				array('username', '')), +			'new_password' => array( +				array('string', false, $this->config['min_pass_chars'], $this->config['max_pass_chars']), +				array('password')), +			'email'        => array( +				array('string', false, 6, 60), +				array('user_email')), +		)); + +		if ($error) +		{ +			throw new runtime_exception(implode("\n", array_map(array($this->language, 'lang'), $error))); +		} +	} + +	/** +	 * Get the group id +	 * +	 * Go and find in the database the group_id corresponding to 'REGISTERED' +	 * +	 * @throws runtime_exception if the group id does not exist in database. +	 * @return null +	 */ +	protected function get_group_id() +	{ +		$sql = 'SELECT group_id +			FROM ' . GROUPS_TABLE . " +			WHERE group_name = '" . $this->db->sql_escape('REGISTERED') . "' +				AND group_type = " . GROUP_SPECIAL; +		$result = $this->db->sql_query($sql); +		$row = $this->db->sql_fetchrow($result); +		$this->db->sql_freeresult($result); + +		if (!$row || !$row['group_id']) +		{ +			throw new runtime_exception($this->language->lang('NO_GROUP')); +		} + +		return $row['group_id']; +	} + +	/** +	 * Send account activation email +	 * +	 * @param int   $user_id The new user's id +	 * @return null +	 */ +	protected function send_activation_email($user_id) +	{ +		switch ($this->config['require_activation']) +		{ +			case USER_ACTIVATION_SELF: +				$email_template = 'user_welcome_inactive'; +				$user_actkey = gen_rand_string(mt_rand(6, 10)); +			break; +			case USER_ACTIVATION_ADMIN: +				$email_template = 'admin_welcome_inactive'; +				$user_actkey = gen_rand_string(mt_rand(6, 10)); +			break; +			default: +				$email_template = 'user_welcome'; +				$user_actkey = ''; +			break; +		} + +		if (!class_exists('messenger')) +		{ +			require($this->phpbb_root_path . 'includes/functions_messenger.' . $this->php_ext); +		} + +		$messenger = new \messenger(false); +		$messenger->template($email_template, $this->user->lang_name); +		$messenger->to($this->data['email'], $this->data['username']); +		$messenger->anti_abuse_headers($this->config, $this->user); +		$messenger->assign_vars(array( +			'WELCOME_MSG' => htmlspecialchars_decode($this->language->lang('WELCOME_SUBJECT', $this->config['sitename'])), +			'USERNAME'    => htmlspecialchars_decode($this->data['username']), +			'PASSWORD'    => htmlspecialchars_decode($this->data['new_password']), +			'U_ACTIVATE'  => generate_board_url() . "/ucp.{$this->php_ext}?mode=activate&u=$user_id&k=$user_actkey") +		); + +		$messenger->send(NOTIFY_EMAIL); +	} + +	/** +	 * Helper to translate questions to the user +	 * +	 * @param string $key The language key +	 * @return string The language key translated with a colon and space appended +	 */ +	protected function ask_user($key) +	{ +		return $this->language->lang($key) . $this->language->lang('COLON') . ' '; +	} +} diff --git a/phpBB/phpbb/console/command/user/delete.php b/phpBB/phpbb/console/command/user/delete.php new file mode 100644 index 0000000000..8593541c1a --- /dev/null +++ b/phpBB/phpbb/console/command/user/delete.php @@ -0,0 +1,170 @@ +<?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\console\command\user; + +use phpbb\console\command\command; +use phpbb\db\driver\driver_interface; +use phpbb\language\language; +use phpbb\log\log_interface; +use phpbb\user; +use phpbb\user_loader; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Style\SymfonyStyle; + +class delete extends command +{ +	/** @var driver_interface */ +	protected $db; + +	/** @var language */ +	protected $language; + +	/** @var log_interface */ +	protected $log; + +	/** @var user_loader */ +	protected $user_loader; + +	/** +	 * phpBB root path +	 * +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * PHP extension. +	 * +	 * @var string +	 */ +	protected $php_ext; + +	/** +	 * Construct method +	 * +	 * @param user             $user +	 * @param driver_interface $db +	 * @param language         $language +	 * @param log_interface    $log +	 * @param user_loader      $user_loader +	 * @param string           $phpbb_root_path +	 * @param string           $php_ext +	 */ +	public function __construct(user $user, driver_interface $db, language $language, log_interface $log, user_loader $user_loader, $phpbb_root_path, $php_ext) +	{ +		$this->db = $db; +		$this->language = $language; +		$this->log = $log; +		$this->user_loader = $user_loader; +		$this->phpbb_root_path = $phpbb_root_path; +		$this->php_ext = $php_ext; + +		$this->language->add_lang('acp/users'); +		parent::__construct($user); +	} + +	/** +	 * Sets the command name and description +	 * +	 * @return null +	 */ +	protected function configure() +	{ +		$this +			->setName('user:delete') +			->setDescription($this->language->lang('CLI_DESCRIPTION_USER_DELETE')) +			->addArgument( +				'username', +				InputArgument::REQUIRED, +				$this->language->lang('CLI_DESCRIPTION_USER_DELETE_USERNAME') +			) +			->addOption( +				'delete-posts', +				null, +				InputOption::VALUE_NONE, +				$this->language->lang('CLI_DESCRIPTION_USER_DELETE_OPTION_POSTS') +			) +		; +	} + +	/** +	 * Executes the command user:delete +	 * +	 * Deletes a user from the database. An option to delete the user's posts +	 * is available, by default posts will be retained. +	 * +	 * @param InputInterface  $input  The input stream used to get the options +	 * @param OutputInterface $output The output stream, used to print messages +	 * +	 * @return int 0 if all is well, 1 if any errors occurred +	 */ +	protected function execute(InputInterface $input, OutputInterface $output) +	{ +		$name = $input->getArgument('username'); +		$mode = ($input->getOption('delete-posts')) ? 'remove' : 'retain'; + +		if ($name) +		{ +			$io = new SymfonyStyle($input, $output); + +			$user_id  = $this->user_loader->load_user_by_username($name); +			$user_row = $this->user_loader->get_user($user_id); + +			if ($user_row['user_id'] == ANONYMOUS) +			{ +				$io->error($this->language->lang('NO_USER')); +				return 1; +			} + +			if (!function_exists('user_delete')) +			{ +				require($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); +			} + +			user_delete($mode, $user_row['user_id'], $user_row['username']); + +			$this->log->add('admin', ANONYMOUS, '', 'LOG_USER_DELETED', false, array($user_row['username'])); + +			$io->success($this->language->lang('USER_DELETED')); +		} + +		return 0; +	} + +	/** +	 * Interacts with the user. +	 * Confirm they really want to delete the account...last chance! +	 * +	 * @param InputInterface  $input  An InputInterface instance +	 * @param OutputInterface $output An OutputInterface instance +	 */ +	protected function interact(InputInterface $input, OutputInterface $output) +	{ +		$helper = $this->getHelper('question'); + +		$question = new ConfirmationQuestion( +			$this->language->lang('CLI_USER_DELETE_CONFIRM', $input->getArgument('username')), +			false +		); + +		if (!$helper->ask($input, $output, $question)) +		{ +			$input->setArgument('username', false); +		} +	} +} diff --git a/phpBB/phpbb/console/command/user/reclean.php b/phpBB/phpbb/console/command/user/reclean.php new file mode 100644 index 0000000000..1a89f13382 --- /dev/null +++ b/phpBB/phpbb/console/command/user/reclean.php @@ -0,0 +1,158 @@ +<?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\console\command\user; + +use phpbb\console\command\command; +use phpbb\db\driver\driver_interface; +use phpbb\language\language; +use phpbb\user; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class reclean extends command +{ +	/** @var driver_interface */ +	protected $db; + +	/** @var language */ +	protected $language; + +	/** @var int A count of the number of re-cleaned user names */ +	protected $processed; + +	/** @var ProgressBar */ +	protected $progress; + +	/** +	 * Construct method +	 * +	 * @param user             $user +	 * @param driver_interface $db +	 * @param language         $language +	 */ +	public function __construct(user $user, driver_interface $db, language $language) +	{ +		$this->db = $db; +		$this->language = $language; + +		parent::__construct($user); +	} + +	/** +	 * Sets the command name and description +	 * +	 * @return null +	 */ +	protected function configure() +	{ +		$this +			->setName('user:reclean') +			->setDescription($this->language->lang('CLI_DESCRIPTION_USER_RECLEAN')) +			->setHelp($this->language->lang('CLI_HELP_USER_RECLEAN')) +		; +	} + +	/** +	 * Executes the command user:reclean +	 * +	 * Cleans user names that are unclean. +	 * +	 * @param InputInterface  $input  The input stream used to get the options +	 * @param OutputInterface $output The output stream, used to print messages +	 * +	 * @return int 0 if all is well, 1 if any errors occurred +	 */ +	protected function execute(InputInterface $input, OutputInterface $output) +	{ +		$io = new SymfonyStyle($input, $output); + +		$io->section($this->language->lang('CLI_USER_RECLEAN_START')); + +		$this->processed = 0; + +		$this->progress = $this->create_progress_bar($this->get_count(), $io, $output); +		$this->progress->setMessage($this->language->lang('CLI_USER_RECLEAN_START')); +		$this->progress->start(); + +		$stage = 0; +		while ($stage !== true) +		{ +			$stage = $this->reclean_usernames($stage); +		} + +		$this->progress->finish(); + +		$io->newLine(2); +		$io->success($this->language->lang('CLI_USER_RECLEAN_DONE', $this->processed)); + +		return 0; +	} + +	/** +	 * Re-clean user names +	 * Only user names that are unclean will be re-cleaned +	 * +	 * @param int $start An offset index +	 * @return bool|int Return the next offset index or true if all records have been processed. +	 */ +	protected function reclean_usernames($start = 0) +	{ +		$limit = 500; +		$i = 0; + +		$this->db->sql_transaction('begin'); + +		$sql = 'SELECT user_id, username, username_clean FROM ' . USERS_TABLE; +		$result = $this->db->sql_query_limit($sql, $limit, $start); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$i++; +			$username_clean = $this->db->sql_escape(utf8_clean_string($row['username'])); + +			if ($username_clean != $row['username_clean']) +			{ +				$sql = 'UPDATE ' . USERS_TABLE . " +					SET username_clean = '$username_clean' +					WHERE user_id = {$row['user_id']}"; +				$this->db->sql_query($sql); + +				$this->processed++; +			} + +			$this->progress->advance(); +		} +		$this->db->sql_freeresult($result); + +		$this->db->sql_transaction('commit'); + +		return ($i < $limit) ? true : $start + $i; +	} + +	/** +	 * Get the count of users in the database +	 * +	 * @return int +	 */ +	protected function get_count() +	{ +		$sql = 'SELECT COUNT(user_id) AS count FROM ' . USERS_TABLE; +		$result = $this->db->sql_query($sql); +		$count = (int) $this->db->sql_fetchfield('count'); +		$this->db->sql_freeresult($result); + +		return $count; +	} +} diff --git a/phpBB/phpbb/console/exception_subscriber.php b/phpBB/phpbb/console/exception_subscriber.php new file mode 100644 index 0000000000..b920d4abae --- /dev/null +++ b/phpBB/phpbb/console/exception_subscriber.php @@ -0,0 +1,74 @@ +<?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\console; + +use phpbb\exception\exception_interface; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleExceptionEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class exception_subscriber implements EventSubscriberInterface +{ +	/** +	 * @var \phpbb\language\language +	 */ +	protected $language; + +	/** +	 * Construct method +	 * +	 * @param \phpbb\language\language $language Language object +	 * @param bool $debug Debug mode +	 */ +	public function __construct(\phpbb\language\language $language, $debug = false) +	{ +		$this->language = $language; +		$this->debug = $debug; +	} + +	/** +	 * This listener is run when the ConsoleEvents::EXCEPTION event is triggered. +	 * It translate the exception message. If din debug mode the original exception is embedded. +	 * +	 * @param ConsoleExceptionEvent $event +	 */ +	public function on_exception(ConsoleExceptionEvent $event) +	{ +		$original_exception = $event->getException(); + +		if ($original_exception instanceof exception_interface) +		{ +			$parameters = array_merge(array($original_exception->getMessage()), $original_exception->get_parameters()); +			$message = call_user_func_array(array($this->language, 'lang'), $parameters); + +			if ($this->debug) +			{ +				$exception = new \RuntimeException($message , $original_exception->getCode(), $original_exception); +			} +			else +			{ +				$exception = new \RuntimeException($message , $original_exception->getCode()); +			} + +			$event->setException($exception); +		} +	} + +	static public function getSubscribedEvents() +	{ +		return array( +			ConsoleEvents::EXCEPTION => 'on_exception', +		); +	} +} 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 ce6bfba981..664b4f4e0f 100644 --- a/phpBB/phpbb/controller/helper.php +++ b/phpBB/phpbb/controller/helper.php @@ -15,9 +15,7 @@ namespace phpbb\controller;  use Symfony\Component\HttpFoundation\JsonResponse;  use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Generator\UrlGenerator;  use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Symfony\Component\Routing\RequestContext;  /**  * Controller helper class, contains methods that do things for controllers @@ -49,49 +47,28 @@ class helper  	protected $request;  	/** -	* @var \phpbb\filesystem The filesystem object -	*/ -	protected $filesystem; - -	/** -	* phpBB root path -	* @var string -	*/ -	protected $phpbb_root_path; - -	/** -	* PHP file extension -	* @var string -	*/ -	protected $php_ext; +	 * @var \phpbb\routing\helper +	 */ +	protected $routing_helper;  	/** -	* Constructor -	* -	* @param \phpbb\template\template $template Template object -	* @param \phpbb\user $user User object -	* @param \phpbb\config\config $config Config object +	 * Constructor  	 * -	 * @param \phpbb\controller\provider $provider Path provider -	* @param \phpbb\extension\manager $manager Extension manager object -	* @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 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) +	 * @param \phpbb\template\template $template Template object +	 * @param \phpbb\user $user User object +	 * @param \phpbb\config\config $config Config object +	 * @param \phpbb\symfony_request $symfony_request Symfony Request object +	 * @param \phpbb\request\request_interface $request phpBB request object +	 * @param \phpbb\routing\helper $routing_helper Helper to generate the routes +	 */ +	public function __construct(\phpbb\template\template $template, \phpbb\user $user, \phpbb\config\config $config, \phpbb\symfony_request $symfony_request, \phpbb\request\request_interface $request, \phpbb\routing\helper $routing_helper)  	{  		$this->template = $template;  		$this->user = $user;  		$this->config = $config;  		$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(); +		$this->routing_helper = $routing_helper;  	}  	/** @@ -134,70 +111,7 @@ class helper  	*/  	public function route($route, array $params = array(), $is_amp = true, $session_id = false, $reference_type = UrlGeneratorInterface::ABSOLUTE_PATH)  	{ -		$anchor = ''; -		if (isset($params['#'])) -		{ -			$anchor = '#' . $params['#']; -			unset($params['#']); -		} - -		$context = new RequestContext(); -		$context->fromRequest($this->symfony_request); - -		if ($this->config['force_server_vars']) -		{ -			$context->setHost($this->config['server_name']); -			$context->setScheme(substr($this->config['server_protocol'], 0, -3)); -			$context->setHttpPort($this->config['server_port']); -			$context->setHttpsPort($this->config['server_port']); -			$context->setBaseUrl(rtrim($this->config['script_path'], '/')); -		} - -		$script_name = $this->symfony_request->getScriptName(); -		$page_name = substr($script_name, -1, 1) == '/' ? '' : utf8_basename($script_name); - -		$base_url = $context->getBaseUrl(); - -		// Append page name if base URL does not contain it -		if (!empty($page_name) && strpos($base_url, '/' . $page_name) === false) -		{ -			$base_url .= '/' . $page_name; -		} - -		// If enable_mod_rewrite is false we need to replace the current front-end by app.php, otherwise we need to remove it. -		$base_url = str_replace('/' . $page_name, empty($this->config['enable_mod_rewrite']) ? '/app.' . $this->php_ext : '', $base_url); - -		// We need to update the base url to move to the directory of the app.php file if the current script is not app.php -		if ($page_name !== 'app.php' && !$this->config['force_server_vars']) -		{ -			if (empty($this->config['enable_mod_rewrite'])) -			{ -				$base_url = str_replace('/app.' . $this->php_ext, '/' . $this->phpbb_root_path . 'app.' . $this->php_ext, $base_url); -			} -			else -			{ -				$base_url .= preg_replace(get_preg_expression('path_remove_dot_trailing_slash'), '$2', $this->phpbb_root_path); -			} -		} - -		$base_url = $this->request->escape($this->filesystem->clean_path($base_url), true); - -		$context->setBaseUrl($base_url); - -		$url_generator = new UrlGenerator($this->route_collection, $context); -		$route_url = $url_generator->generate($route, $params, $reference_type); - -		if ($is_amp) -		{ -			$route_url = str_replace(array('&', '&'), array('&', '&'), $route_url); -		} - -		if ($reference_type === UrlGeneratorInterface::RELATIVE_PATH && empty($this->config['enable_mod_rewrite'])) -		{ -			$route_url = 'app.' . $this->php_ext . '/' . $route_url; -		} - -		return append_sid($route_url . $anchor, false, $is_amp, $session_id, true); +		return $this->routing_helper->route($route, $params, $is_amp, $session_id, $reference_type);  	}  	/** @@ -256,6 +170,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..f8dffc12de 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); @@ -134,9 +126,21 @@ class resolver implements ControllerResolverInterface  	*/  	public function getArguments(Request $request, $controller)  	{ -		// At this point, $controller contains the object and method name -		list($object, $method) = $controller; -		$mirror = new \ReflectionMethod($object, $method); +		// At this point, $controller should be a callable +		if (is_array($controller)) +		{ +			list($object, $method) = $controller; +			$mirror = new \ReflectionMethod($object, $method); +		} +		else if (is_object($controller) && !$controller instanceof \Closure) +		{ +			$mirror = new \ReflectionObject($controller); +			$mirror = $mirror->getMethod('__invoke'); +		} +		else +		{ +			$mirror = new \ReflectionFunction($controller); +		}  		$arguments = array();  		$parameters = $mirror->getParameters(); @@ -166,7 +170,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/queue.php b/phpBB/phpbb/cron/task/core/queue.php index a9345a44df..eca69a5041 100644 --- a/phpBB/phpbb/cron/task/core/queue.php +++ b/phpBB/phpbb/cron/task/core/queue.php @@ -20,20 +20,23 @@ class queue extends \phpbb\cron\task\base  {  	protected $phpbb_root_path;  	protected $php_ext; +	protected $cache_dir;  	protected $config;  	/** -	* Constructor. -	* -	* @param string $phpbb_root_path The root path -	* @param string $php_ext PHP file extension -	* @param \phpbb\config\config $config The config -	*/ -	public function __construct($phpbb_root_path, $php_ext, \phpbb\config\config $config) +	 * Constructor. +	 * +	 * @param string $phpbb_root_path The root path +	 * @param string $php_ext PHP file extension +	 * @param \phpbb\config\config $config The config +	 * @param string $cache_dir phpBB cache directory +	 */ +	public function __construct($phpbb_root_path, $php_ext, \phpbb\config\config $config, $cache_dir)  	{  		$this->phpbb_root_path = $phpbb_root_path;  		$this->php_ext = $php_ext;  		$this->config = $config; +		$this->cache_dir = $cache_dir;  	}  	/** @@ -60,7 +63,7 @@ class queue extends \phpbb\cron\task\base  	*/  	public function is_runnable()  	{ -		return file_exists($this->phpbb_root_path . 'cache/queue.' . $this->php_ext); +		return file_exists($this->cache_dir . 'queue.' . $this->php_ext);  	}  	/** diff --git a/phpBB/phpbb/cron/task/core/tidy_plupload.php b/phpBB/phpbb/cron/task/core/tidy_plupload.php index b6aeecf4b4..37d0e9b21a 100644 --- a/phpBB/phpbb/cron/task/core/tidy_plupload.php +++ b/phpBB/phpbb/cron/task/core/tidy_plupload.php @@ -42,6 +42,12 @@ class tidy_plupload extends \phpbb\cron\task\base  	*/  	protected $config; +	/** @var \phpbb\log\log_interface */ +	protected $log; + +	/** @var \phpbb\user */ +	protected $user; +  	/**  	* Directory where plupload stores temporary files.  	* @var string @@ -53,11 +59,15 @@ class tidy_plupload extends \phpbb\cron\task\base  	*  	* @param string $phpbb_root_path The root path  	* @param \phpbb\config\config $config The config +	* @param \phpbb\log\log_interface $log Log +	* @param \phpbb\user $user User object  	*/ -	public function __construct($phpbb_root_path, \phpbb\config\config $config) +	public function __construct($phpbb_root_path, \phpbb\config\config $config, \phpbb\log\log_interface $log, \phpbb\user $user)  	{  		$this->phpbb_root_path = $phpbb_root_path;  		$this->config = $config; +		$this->log = $log; +		$this->user = $user;  		$this->plupload_upload_path = $this->phpbb_root_path . $this->config['upload_path'] . '/plupload';  	} @@ -88,13 +98,11 @@ class tidy_plupload extends \phpbb\cron\task\base  		}  		catch (\UnexpectedValueException $e)  		{ -			add_log( -				'critical', -				'LOG_PLUPLOAD_TIDY_FAILED', +			$this->log->add('critical', $this->user->data['user_id'], $this->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/cron/task/text_reparser/reparser.php b/phpBB/phpbb/cron/task/text_reparser/reparser.php new file mode 100644 index 0000000000..aa644de827 --- /dev/null +++ b/phpBB/phpbb/cron/task/text_reparser/reparser.php @@ -0,0 +1,168 @@ +<?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\cron\task\text_reparser; + +/** + * Reparse text cron task + */ +class reparser extends \phpbb\cron\task\base +{ +	const MIN = 1; +	const SIZE = 100; + +	/** +	 * @var \phpbb\config\config +	 */ +	protected $config; + +	/** +	 * @var \phpbb\config\db_text +	 */ +	protected $config_text; + +	/** +	 * @var \phpbb\lock\db +	 */ +	protected $reparse_lock; + +	/** +	 * @var \phpbb\textreparser\manager +	 */ +	protected $reparser_manager; + +	/** +	 * @var string +	 */ +	protected $reparser_name; + +	/** +	 * @var \phpbb\di\service_collection +	 */ +	protected $reparsers; + +	/** +	 * @var array +	 */ +	protected $resume_data; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\config\config			$config +	 * @param \phpbb\config\db_text			$config_text +	 * @param \phpbb\lock\db				$reparse_lock +	 * @param \phpbb\textreparser\manager	$reparser_manager +	 * @param \phpbb\di\service_collection	$reparsers +	 */ +	public function __construct(\phpbb\config\config $config, \phpbb\config\db_text $config_text, \phpbb\lock\db $reparse_lock, \phpbb\textreparser\manager $reparser_manager, \phpbb\di\service_collection $reparsers) +	{ +		$this->config = $config; +		$this->config_text = $config_text; +		$this->reparse_lock = $reparse_lock; +		$this->reparser_manager = $reparser_manager; +		$this->reparsers = $reparsers; +	} + +	/** +	 * Sets the reparser for this cron task +	 * +	 * @param string	$reparser +	 */ +	public function set_reparser($reparser) +	{ +		$this->reparser_name = (!isset($this->reparsers[$reparser]) ? 'text_reparser.' : '') . $reparser; + +		if ($this->resume_data === null) +		{ +			$this->reparser_manager->get_resume_data($this->reparser_name); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function is_runnable() +	{ +		if ($this->resume_data === null) +		{ +			$this->reparser_manager->get_resume_data($this->reparser_name); +		} + +		if (empty($this->resume_data['range-max']) || $this->resume_data['range-max'] >= $this->resume_data['range-min']) +		{ +			return true; +		} + +		return false; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function should_run() +	{ +		if (!empty($this->config['reparse_lock'])) +		{ +			$last_run = explode(' ', $this->config['reparse_lock']); + +			if ($last_run[0] + 3600 >= time()) +			{ +				return false; +			} +		} + +		if ($this->config[$this->reparser_name . '_cron_interval']) +		{ +			return $this->config[$this->reparser_name . '_last_cron'] < time() - $this->config[$this->reparser_name . '_cron_interval']; +		} + +		return false; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		if ($this->reparse_lock->acquire()) +		{ +			if ($this->resume_data === null) +			{ +				$this->resume_data = $this->reparser_manager->get_resume_data($this->reparser_name); +			} + +			/** +			 * @var \phpbb\textreparser\reparser_interface $reparser +			 */ +			$reparser = $this->reparsers[$this->reparser_name]; + +			$min = !empty($this->resume_data['range-min']) ? $this->resume_data['range-min'] : self::MIN; +			$current = !empty($this->resume_data['range-max']) ? $this->resume_data['range-max'] : $reparser->get_max_id(); +			$size = !empty($this->resume_data['range-size']) ? $this->resume_data['range-size'] : self::SIZE; + +			if ($current >= $min) +			{ +				$start = max($min, $current + 1 - $size); +				$end = max($min, $current); + +				$reparser->reparse_range($start, $end); + +				$this->reparser_manager->update_resume_data($this->reparser_name, $min, $start - 1, $size); +			} + +			$this->config->set($this->reparser_name . '_last_cron', time()); +			$this->reparse_lock->release(); +		} +	} +} diff --git a/phpBB/phpbb/db/driver/driver.php b/phpBB/phpbb/db/driver/driver.php index 01dd66cd6e..214c5590e7 100644 --- a/phpBB/phpbb/db/driver/driver.php +++ b/phpBB/phpbb/db/driver/driver.php @@ -66,6 +66,15 @@ abstract class driver implements driver_interface  	*/  	var $sql_server_version = false; +	const LOGICAL_OP = 0; +	const STATEMENTS = 1; +	const LEFT_STMT = 0; +	const COMPARE_OP = 1; +	const RIGHT_STMT = 2; +	const SUBQUERY_OP = 3; +	const SUBQUERY_SELECT_TYPE = 4; +	const SUBQUERY_BUILD = 5; +  	/**  	* Constructor  	*/ @@ -271,7 +280,7 @@ abstract class driver implements driver_interface  			$query_id = $this->query_result;  		} -		if ($query_id !== false) +		if ($query_id)  		{  			$result = array();  			while ($row = $this->sql_fetchrow($query_id)) @@ -302,7 +311,7 @@ abstract class driver implements driver_interface  			return $cache->sql_rowseek($rownum, $query_id);  		} -		if ($query_id === false) +		if (!$query_id)  		{  			return false;  		} @@ -310,7 +319,7 @@ abstract class driver implements driver_interface  		$this->sql_freeresult($query_id);  		$query_id = $this->sql_query($this->last_query_text); -		if ($query_id === false) +		if (!$query_id)  		{  			return false;  		} @@ -339,7 +348,7 @@ abstract class driver implements driver_interface  			$query_id = $this->query_result;  		} -		if ($query_id !== false) +		if ($query_id)  		{  			if ($rownum !== false)  			{ @@ -363,8 +372,8 @@ abstract class driver implements driver_interface  	*/  	function sql_like_expression($expression)  	{ -		$expression = utf8_str_replace(array('_', '%'), array("\_", "\%"), $expression); -		$expression = utf8_str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression); +		$expression = str_replace(array('_', '%'), array("\_", "\%"), $expression); +		$expression = str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression);  		return $this->_sql_like_expression('LIKE \'' . $this->sql_escape($expression) . '\'');  	} @@ -374,8 +383,8 @@ abstract class driver implements driver_interface  	*/  	function sql_not_like_expression($expression)  	{ -		$expression = utf8_str_replace(array('_', '%'), array("\_", "\%"), $expression); -		$expression = utf8_str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression); +		$expression = str_replace(array('_', '%'), array("\_", "\%"), $expression); +		$expression = str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression);  		return $this->_sql_not_like_expression('NOT LIKE \'' . $this->sql_escape($expression) . '\'');  	} @@ -774,7 +783,18 @@ abstract class driver implements driver_interface  				if (!empty($array['WHERE']))  				{ -					$sql .= ' WHERE ' . $this->_sql_custom_build('WHERE', $array['WHERE']); +					$sql .= ' WHERE '; + +					if (is_array($array['WHERE'])) +					{ +						$sql_where = $this->_process_boolean_tree_first($array['WHERE']); +					} +					else +					{ +						$sql_where = $array['WHERE']; +					} + +					$sql .= $this->_sql_custom_build('WHERE', $sql_where);  				}  				if (!empty($array['GROUP_BY'])) @@ -793,6 +813,130 @@ abstract class driver implements driver_interface  		return $sql;  	} + +	protected function _process_boolean_tree_first($operations_ary) +	{ +		// In cases where an array exists but there is no head condition, +		// it should be because there's only 1 WHERE clause. This seems the best way to deal with it. +		if ($operations_ary[self::LOGICAL_OP] !== 'AND' && +			$operations_ary[self::LOGICAL_OP] !== 'OR') +		{ +			$operations_ary = array('AND', array($operations_ary)); +		} +		return $this->_process_boolean_tree($operations_ary) . "\n"; +	} + +	protected function _process_boolean_tree($operations_ary) +	{ +		$operation = $operations_ary[self::LOGICAL_OP]; + +		foreach ($operations_ary[self::STATEMENTS] as &$condition) +		{ +			switch ($condition[self::LOGICAL_OP]) +			{ +				case 'AND': +				case 'OR': + +					$condition = ' ( ' . $this->_process_boolean_tree($condition) . ') '; + +				break; +				case 'NOT': + +					$condition = ' NOT (' . $this->_process_boolean_tree($condition) . ') '; + +				break; + +				default: + +					switch (sizeof($condition)) +					{ +						case 3: + +							// Typical 3 element clause with {left hand} {operator} {right hand} +							switch ($condition[self::COMPARE_OP]) +							{ +								case 'IN': +								case 'NOT_IN': + +									// As this is used with an IN, assume it is a set of elements for sql_in_set() +									$condition = $this->sql_in_set($condition[self::LEFT_STMT], $condition[self::RIGHT_STMT], $condition[self::COMPARE_OP] === 'NOT_IN', true); + +								break; + +								case 'LIKE': + +									$condition = $condition[self::LEFT_STMT] . ' ' . $this->sql_like_expression($condition[self::RIGHT_STMT]) . ' '; + +								break; + +								case 'NOT_LIKE': + +									$condition = $condition[self::LEFT_STMT] . ' ' . $this->sql_not_like_expression($condition[self::RIGHT_STMT]) . ' '; + +								break; + +								case 'IS_NOT': + +									$condition[self::COMPARE_OP] = 'IS NOT'; + +								// no break +								case 'IS': + +									// If the value is NULL, the string of it is the empty string ('') which is not the intended result. +									// this should solve that +									if ($condition[self::RIGHT_STMT] === null) +									{ +										$condition[self::RIGHT_STMT] = 'NULL'; +									} + +									$condition = implode(' ', $condition); + +								break; + +								default: + +									$condition = implode(' ', $condition); + +								break; +							} + +						break; + +						case 5: + +							// Subquery with {left hand} {operator} {compare kind} {SELECT Kind } {Sub Query} + +							$condition = $condition[self::LEFT_STMT] . ' ' . $condition[self::COMPARE_OP] . ' ' . $condition[self::SUBQUERY_OP] . ' ( '; +							$condition .= $this->sql_build_query($condition[self::SUBQUERY_SELECT_TYPE], $condition[self::SUBQUERY_BUILD]); +							$condition .= ' )'; + +						break; + +						default: +							// This is an unpredicted clause setup. Just join all elements. +							$condition = implode(' ', $condition); + +						break; +					} + +				break; +			} + +		} + +		if ($operation === 'NOT') +		{ +			$operations_ary =  implode("", $operations_ary[self::STATEMENTS]); +		} +		else +		{ +			$operations_ary = implode(" \n	$operation ", $operations_ary[self::STATEMENTS]); +		} + +		return $operations_ary; +	} + +  	/**  	* {@inheritDoc}  	*/ @@ -868,7 +1012,7 @@ abstract class driver implements driver_interface  	*/  	function sql_report($mode, $query = '')  	{ -		global $cache, $starttime, $phpbb_root_path, $phpbb_path_helper, $user; +		global $cache, $starttime, $phpbb_root_path, $phpbb_path_helper;  		global $request;  		if (is_object($request) && !$request->variable('explain', false)) diff --git a/phpBB/phpbb/db/driver/mssql.php b/phpBB/phpbb/db/driver/mssql.php index f9ea884ce2..dfdbfe15e6 100644 --- a/phpBB/phpbb/db/driver/mssql.php +++ b/phpBB/phpbb/db/driver/mssql.php @@ -71,8 +71,8 @@ class mssql extends \phpbb\db\driver\driver  			$row = false;  			if ($result_id)  			{ -				$row = @mssql_fetch_assoc($result_id); -				@mssql_free_result($result_id); +				$row = mssql_fetch_assoc($result_id); +				mssql_free_result($result_id);  			}  			$this->sql_server_version = ($row) ? trim(implode(' ', $row)) : 0; @@ -161,12 +161,17 @@ class mssql extends \phpbb\db\driver\driver  					$this->sql_time += microtime(true) - $this->curtime;  				} +				if (!$this->query_result) +				{ +					return false; +				} +  				if ($cache && $cache_ttl)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result;  					$this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl);  				} -				else if (strpos($query, 'SELECT') === 0 && $this->query_result) +				else if (strpos($query, 'SELECT') === 0 && $this->query_result !== true)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result;  				} @@ -241,12 +246,12 @@ class mssql extends \phpbb\db\driver\driver  			return $cache->sql_fetchrow($query_id);  		} -		if ($query_id === false) +		if (!$query_id || $query_id === true)  		{  			return false;  		} -		$row = @mssql_fetch_assoc($query_id); +		$row = mssql_fetch_assoc($query_id);  		// I hope i am able to remove this later... hopefully only a PHP or MSSQL bug  		if ($row) @@ -272,12 +277,17 @@ class mssql extends \phpbb\db\driver\driver  			$query_id = $this->query_result;  		} +		if ($query_id === true) +		{ +			return false; +		} +  		if ($cache && $cache->sql_exists($query_id))  		{  			return $cache->sql_rowseek($rownum, $query_id);  		} -		return ($query_id !== false) ? @mssql_data_seek($query_id, $rownum) : false; +		return ($query_id) ? @mssql_data_seek($query_id, $rownum) : false;  	}  	/** @@ -288,12 +298,12 @@ class mssql extends \phpbb\db\driver\driver  		$result_id = @mssql_query('SELECT SCOPE_IDENTITY()', $this->db_connect_id);  		if ($result_id)  		{ -			if ($row = @mssql_fetch_assoc($result_id)) +			if ($row = mssql_fetch_assoc($result_id))  			{ -				@mssql_free_result($result_id); +				mssql_free_result($result_id);  				return $row['computed'];  			} -			@mssql_free_result($result_id); +			mssql_free_result($result_id);  		}  		return false; @@ -311,6 +321,11 @@ class mssql extends \phpbb\db\driver\driver  			$query_id = $this->query_result;  		} +		if ($query_id === true) +		{ +			return false; +		} +  		if ($cache && !is_object($query_id) && $cache->sql_exists($query_id))  		{  			return $cache->sql_freeresult($query_id); @@ -319,7 +334,7 @@ class mssql extends \phpbb\db\driver\driver  		if (isset($this->open_queries[(int) $query_id]))  		{  			unset($this->open_queries[(int) $query_id]); -			return @mssql_free_result($query_id); +			return mssql_free_result($query_id);  		}  		return false; @@ -376,9 +391,9 @@ class mssql extends \phpbb\db\driver\driver  			$result_id = @mssql_query('SELECT @@ERROR as code', $this->db_connect_id);  			if ($result_id)  			{ -				$row = @mssql_fetch_assoc($result_id); +				$row = mssql_fetch_assoc($result_id);  				$error['code'] = $row['code']; -				@mssql_free_result($result_id); +				mssql_free_result($result_id);  			}  			// Get full error message if possible @@ -389,12 +404,12 @@ class mssql extends \phpbb\db\driver\driver  			if ($result_id)  			{ -				$row = @mssql_fetch_assoc($result_id); +				$row = mssql_fetch_assoc($result_id);  				if (!empty($row['message']))  				{  					$error['message'] .= '<br />' . $row['message'];  				} -				@mssql_free_result($result_id); +				mssql_free_result($result_id);  			}  		}  		else @@ -440,13 +455,13 @@ class mssql extends \phpbb\db\driver\driver  				if ($result = @mssql_query($query, $this->db_connect_id))  				{  					@mssql_next_result($result); -					while ($row = @mssql_fetch_row($result)) +					while ($row = mssql_fetch_row($result))  					{  						$html_table = $this->sql_report('add_select_row', $query, $html_table, $row);  					}  				}  				@mssql_query('SET SHOWPLAN_TEXT OFF;', $this->db_connect_id); -				@mssql_free_result($result); +				mssql_free_result($result);  				if ($html_table)  				{ @@ -459,11 +474,14 @@ class mssql extends \phpbb\db\driver\driver  				$endtime = $endtime[0] + $endtime[1];  				$result = @mssql_query($query, $this->db_connect_id); -				while ($void = @mssql_fetch_assoc($result)) +				if ($result)  				{ -					// Take the time spent on parsing rows into account +					while ($void = mssql_fetch_assoc($result)) +					{ +						// Take the time spent on parsing rows into account +					} +					mssql_free_result($result);  				} -				@mssql_free_result($result);  				$splittime = explode(' ', microtime());  				$splittime = $splittime[0] + $splittime[1]; diff --git a/phpBB/phpbb/db/driver/mssql_odbc.php b/phpBB/phpbb/db/driver/mssql_odbc.php index 8e5d4c7a4c..9d9ad603e0 100644 --- a/phpBB/phpbb/db/driver/mssql_odbc.php +++ b/phpBB/phpbb/db/driver/mssql_odbc.php @@ -98,8 +98,8 @@ class mssql_odbc extends \phpbb\db\driver\mssql_base  			$row = false;  			if ($result_id)  			{ -				$row = @odbc_fetch_array($result_id); -				@odbc_free_result($result_id); +				$row = odbc_fetch_array($result_id); +				odbc_free_result($result_id);  			}  			$this->sql_server_version = ($row) ? trim(implode(' ', $row)) : 0; @@ -181,12 +181,17 @@ class mssql_odbc extends \phpbb\db\driver\mssql_base  					$this->sql_time += microtime(true) - $this->curtime;  				} +				if (!$this->query_result) +				{ +					return false; +				} +  				if ($cache && $cache_ttl)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result;  					$this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl);  				} -				else if (strpos($query, 'SELECT') === 0 && $this->query_result) +				else if (strpos($query, 'SELECT') === 0)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result;  				} @@ -261,7 +266,7 @@ class mssql_odbc extends \phpbb\db\driver\mssql_base  			return $cache->sql_fetchrow($query_id);  		} -		return ($query_id !== false) ? @odbc_fetch_array($query_id) : false; +		return ($query_id) ? odbc_fetch_array($query_id) : false;  	}  	/** @@ -273,13 +278,13 @@ class mssql_odbc extends \phpbb\db\driver\mssql_base  		if ($result_id)  		{ -			if (@odbc_fetch_array($result_id)) +			if (odbc_fetch_array($result_id))  			{ -				$id = @odbc_result($result_id, 1); -				@odbc_free_result($result_id); +				$id = odbc_result($result_id, 1); +				odbc_free_result($result_id);  				return $id;  			} -			@odbc_free_result($result_id); +			odbc_free_result($result_id);  		}  		return false; @@ -305,7 +310,7 @@ class mssql_odbc extends \phpbb\db\driver\mssql_base  		if (isset($this->open_queries[(int) $query_id]))  		{  			unset($this->open_queries[(int) $query_id]); -			return @odbc_free_result($query_id); +			return odbc_free_result($query_id);  		}  		return false; @@ -360,11 +365,14 @@ class mssql_odbc extends \phpbb\db\driver\mssql_base  				$endtime = $endtime[0] + $endtime[1];  				$result = @odbc_exec($this->db_connect_id, $query); -				while ($void = @odbc_fetch_array($result)) +				if ($result)  				{ -					// Take the time spent on parsing rows into account +					while ($void = odbc_fetch_array($result)) +					{ +						// Take the time spent on parsing rows into account +					} +					odbc_free_result($result);  				} -				@odbc_free_result($result);  				$splittime = explode(' ', microtime());  				$splittime = $splittime[0] + $splittime[1]; diff --git a/phpBB/phpbb/db/driver/mssqlnative.php b/phpBB/phpbb/db/driver/mssqlnative.php index 46a9b3a477..50dce35baa 100644 --- a/phpBB/phpbb/db/driver/mssqlnative.php +++ b/phpBB/phpbb/db/driver/mssqlnative.php @@ -154,12 +154,17 @@ class mssqlnative extends \phpbb\db\driver\mssql_base  					$this->sql_time += microtime(true) - $this->curtime;  				} +				if (!$this->query_result) +				{ +					return false; +				} +  				if ($cache && $cache_ttl)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result;  					$this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl);  				} -				else if (strpos($query, 'SELECT') === 0 && $this->query_result) +				else if (strpos($query, 'SELECT') === 0)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result;  				} @@ -242,12 +247,12 @@ class mssqlnative extends \phpbb\db\driver\mssql_base  			return $cache->sql_fetchrow($query_id);  		} -		if ($query_id === false) +		if (!$query_id)  		{  			return false;  		} -		$row = @sqlsrv_fetch_array($query_id, SQLSRV_FETCH_ASSOC); +		$row = sqlsrv_fetch_array($query_id, SQLSRV_FETCH_ASSOC);  		if ($row)  		{ @@ -272,11 +277,11 @@ class mssqlnative extends \phpbb\db\driver\mssql_base  	{  		$result_id = @sqlsrv_query($this->db_connect_id, 'SELECT @@IDENTITY'); -		if ($result_id !== false) +		if ($result_id)  		{ -			$row = @sqlsrv_fetch_array($result_id); +			$row = sqlsrv_fetch_array($result_id);  			$id = $row[0]; -			@sqlsrv_free_stmt($result_id); +			sqlsrv_free_stmt($result_id);  			return $id;  		}  		else @@ -305,7 +310,7 @@ class mssqlnative extends \phpbb\db\driver\mssql_base  		if (isset($this->open_queries[(int) $query_id]))  		{  			unset($this->open_queries[(int) $query_id]); -			return @sqlsrv_free_stmt($query_id); +			return sqlsrv_free_stmt($query_id);  		}  		return false; @@ -378,14 +383,14 @@ class mssqlnative extends \phpbb\db\driver\mssql_base  				@sqlsrv_query($this->db_connect_id, 'SET SHOWPLAN_TEXT ON;');  				if ($result = @sqlsrv_query($this->db_connect_id, $query))  				{ -					@sqlsrv_next_result($result); -					while ($row = @sqlsrv_fetch_array($result)) +					sqlsrv_next_result($result); +					while ($row = sqlsrv_fetch_array($result))  					{  						$html_table = $this->sql_report('add_select_row', $query, $html_table, $row);  					} +					sqlsrv_free_stmt($result);  				}  				@sqlsrv_query($this->db_connect_id, 'SET SHOWPLAN_TEXT OFF;'); -				@sqlsrv_free_stmt($result);  				if ($html_table)  				{ @@ -398,11 +403,14 @@ class mssqlnative extends \phpbb\db\driver\mssql_base  				$endtime = $endtime[0] + $endtime[1];  				$result = @sqlsrv_query($this->db_connect_id, $query); -				while ($void = @sqlsrv_fetch_array($result)) +				if ($result)  				{ -					// Take the time spent on parsing rows into account +					while ($void = sqlsrv_fetch_array($result)) +					{ +						// Take the time spent on parsing rows into account +					} +					sqlsrv_free_stmt($result);  				} -				@sqlsrv_free_stmt($result);  				$splittime = explode(' ', microtime());  				$splittime = $splittime[0] + $splittime[1]; diff --git a/phpBB/phpbb/db/driver/mysql.php b/phpBB/phpbb/db/driver/mysql.php index e93c7239e8..a94e88b331 100644 --- a/phpBB/phpbb/db/driver/mysql.php +++ b/phpBB/phpbb/db/driver/mysql.php @@ -70,9 +70,16 @@ class mysql extends \phpbb\db\driver\mysql_base  					if (version_compare($this->sql_server_info(true), '5.0.2', '>='))  					{  						$result = @mysql_query('SELECT @@session.sql_mode AS sql_mode', $this->db_connect_id); -						$row = @mysql_fetch_assoc($result); -						@mysql_free_result($result); -						$modes = array_map('trim', explode(',', $row['sql_mode'])); +						if ($result) +						{ +							$row = mysql_fetch_assoc($result); +							mysql_free_result($result); +							$modes = array_map('trim', explode(',', $row['sql_mode'])); +						} +						else +						{ +							$modes = array(); +						}  						// TRADITIONAL includes STRICT_ALL_TABLES and STRICT_TRANS_TABLES  						if (!in_array('TRADITIONAL', $modes)) @@ -114,14 +121,17 @@ class mysql extends \phpbb\db\driver\mysql_base  		if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mysql_version')) === false)  		{  			$result = @mysql_query('SELECT VERSION() AS version', $this->db_connect_id); -			$row = @mysql_fetch_assoc($result); -			@mysql_free_result($result); +			if ($result) +			{ +				$row = mysql_fetch_assoc($result); +				mysql_free_result($result); -			$this->sql_server_version = $row['version']; +				$this->sql_server_version = $row['version']; -			if (!empty($cache) && $use_cache) -			{ -				$cache->put('mysql_version', $this->sql_server_version); +				if (!empty($cache) && $use_cache) +				{ +					$cache->put('mysql_version', $this->sql_server_version); +				}  			}  		} @@ -190,12 +200,17 @@ class mysql extends \phpbb\db\driver\mysql_base  					$this->sql_time += microtime(true) - $this->curtime;  				} +				if (!$this->query_result) +				{ +					return false; +				} +  				if ($cache && $cache_ttl)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result;  					$this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl);  				} -				else if (strpos($query, 'SELECT') === 0 && $this->query_result) +				else if (strpos($query, 'SELECT') === 0)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result;  				} @@ -257,7 +272,7 @@ class mysql extends \phpbb\db\driver\mysql_base  			return $cache->sql_fetchrow($query_id);  		} -		return ($query_id !== false) ? @mysql_fetch_assoc($query_id) : false; +		return ($query_id) ? mysql_fetch_assoc($query_id) : false;  	}  	/** @@ -308,7 +323,7 @@ class mysql extends \phpbb\db\driver\mysql_base  		if (isset($this->open_queries[(int) $query_id]))  		{  			unset($this->open_queries[(int) $query_id]); -			return @mysql_free_result($query_id); +			return mysql_free_result($query_id);  		}  		return false; @@ -411,12 +426,12 @@ class mysql extends \phpbb\db\driver\mysql_base  					if ($result = @mysql_query("EXPLAIN $explain_query", $this->db_connect_id))  					{ -						while ($row = @mysql_fetch_assoc($result)) +						while ($row = mysql_fetch_assoc($result))  						{  							$html_table = $this->sql_report('add_select_row', $query, $html_table, $row);  						} +						mysql_free_result($result);  					} -					@mysql_free_result($result);  					if ($html_table)  					{ @@ -431,7 +446,7 @@ class mysql extends \phpbb\db\driver\mysql_base  						if ($result = @mysql_query('SHOW PROFILE ALL;', $this->db_connect_id))  						{  							$this->html_hold .= '<br />'; -							while ($row = @mysql_fetch_assoc($result)) +							while ($row = mysql_fetch_assoc($result))  							{  								// make <unknown> HTML safe  								if (!empty($row['Source_function'])) @@ -449,8 +464,8 @@ class mysql extends \phpbb\db\driver\mysql_base  								}  								$html_table = $this->sql_report('add_select_row', $query, $html_table, $row);  							} +							mysql_free_result($result);  						} -						@mysql_free_result($result);  						if ($html_table)  						{ @@ -468,11 +483,14 @@ class mysql extends \phpbb\db\driver\mysql_base  				$endtime = $endtime[0] + $endtime[1];  				$result = @mysql_query($query, $this->db_connect_id); -				while ($void = @mysql_fetch_assoc($result)) +				if ($result)  				{ -					// Take the time spent on parsing rows into account +					while ($void = mysql_fetch_assoc($result)) +					{ +						// Take the time spent on parsing rows into account +					} +					mysql_free_result($result);  				} -				@mysql_free_result($result);  				$splittime = explode(' ', microtime());  				$splittime = $splittime[0] + $splittime[1]; diff --git a/phpBB/phpbb/db/driver/mysqli.php b/phpBB/phpbb/db/driver/mysqli.php index c0ddfbf76c..d43e201526 100644 --- a/phpBB/phpbb/db/driver/mysqli.php +++ b/phpBB/phpbb/db/driver/mysqli.php @@ -74,9 +74,10 @@ class mysqli extends \phpbb\db\driver\mysql_base  			if (version_compare($this->sql_server_info(true), '5.0.2', '>='))  			{  				$result = @mysqli_query($this->db_connect_id, 'SELECT @@session.sql_mode AS sql_mode'); -				if ($result !== null) +				if ($result)  				{ -					$row = @mysqli_fetch_assoc($result); +					$row = mysqli_fetch_assoc($result); +					mysqli_free_result($result);  					$modes = array_map('trim', explode(',', $row['sql_mode']));  				} @@ -84,7 +85,6 @@ class mysqli extends \phpbb\db\driver\mysql_base  				{  					$modes = array();  				} -				@mysqli_free_result($result);  				// TRADITIONAL includes STRICT_ALL_TABLES and STRICT_TRANS_TABLES  				if (!in_array('TRADITIONAL', $modes)) @@ -119,9 +119,10 @@ class mysqli extends \phpbb\db\driver\mysql_base  		if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mysqli_version')) === false)  		{  			$result = @mysqli_query($this->db_connect_id, 'SELECT VERSION() AS version'); -			if ($result !== null) +			if ($result)  			{ -				$row = @mysqli_fetch_assoc($result); +				$row = mysqli_fetch_assoc($result); +				mysqli_free_result($result);  				$this->sql_server_version = $row['version']; @@ -130,7 +131,6 @@ class mysqli extends \phpbb\db\driver\mysql_base  					$cache->put('mysqli_version', $this->sql_server_version);  				}  			} -			@mysqli_free_result($result);  		}  		return ($raw) ? $this->sql_server_version : 'MySQL(i) ' . $this->sql_server_version; @@ -202,6 +202,11 @@ class mysqli extends \phpbb\db\driver\mysql_base  					$this->sql_time += microtime(true) - $this->curtime;  				} +				if (!$this->query_result) +				{ +					return false; +				} +  				if ($cache && $cache_ttl)  				{  					$this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); @@ -245,9 +250,9 @@ class mysqli extends \phpbb\db\driver\mysql_base  			return $cache->sql_fetchrow($query_id);  		} -		if ($query_id !== false && $query_id !== null) +		if ($query_id)  		{ -			$result = @mysqli_fetch_assoc($query_id); +			$result = mysqli_fetch_assoc($query_id);  			return $result !== null ? $result : false;  		} @@ -271,7 +276,7 @@ class mysqli extends \phpbb\db\driver\mysql_base  			return $cache->sql_rowseek($rownum, $query_id);  		} -		return ($query_id !== false) ? @mysqli_data_seek($query_id, $rownum) : false; +		return ($query_id) ? @mysqli_data_seek($query_id, $rownum) : false;  	}  	/** @@ -299,7 +304,17 @@ class mysqli extends \phpbb\db\driver\mysql_base  			return $cache->sql_freeresult($query_id);  		} -		return @mysqli_free_result($query_id); +		if (!$query_id) +		{ +			return false; +		} + +		if ($query_id === true) +		{ +			return true; +		} + +		return mysqli_free_result($query_id);  	}  	/** @@ -398,12 +413,12 @@ class mysqli extends \phpbb\db\driver\mysql_base  					if ($result = @mysqli_query($this->db_connect_id, "EXPLAIN $explain_query"))  					{ -						while ($row = @mysqli_fetch_assoc($result)) +						while ($row = mysqli_fetch_assoc($result))  						{  							$html_table = $this->sql_report('add_select_row', $query, $html_table, $row);  						} +						mysqli_free_result($result);  					} -					@mysqli_free_result($result);  					if ($html_table)  					{ @@ -418,7 +433,7 @@ class mysqli extends \phpbb\db\driver\mysql_base  						if ($result = @mysqli_query($this->db_connect_id, 'SHOW PROFILE ALL;'))  						{  							$this->html_hold .= '<br />'; -							while ($row = @mysqli_fetch_assoc($result)) +							while ($row = mysqli_fetch_assoc($result))  							{  								// make <unknown> HTML safe  								if (!empty($row['Source_function'])) @@ -436,8 +451,8 @@ class mysqli extends \phpbb\db\driver\mysql_base  								}  								$html_table = $this->sql_report('add_select_row', $query, $html_table, $row);  							} +							mysqli_free_result($result);  						} -						@mysqli_free_result($result);  						if ($html_table)  						{ @@ -455,14 +470,14 @@ class mysqli extends \phpbb\db\driver\mysql_base  				$endtime = $endtime[0] + $endtime[1];  				$result = @mysqli_query($this->db_connect_id, $query); -				if ($result !== null) +				if ($result)  				{ -					while ($void = @mysqli_fetch_assoc($result)) +					while ($void = mysqli_fetch_assoc($result))  					{  						// Take the time spent on parsing rows into account  					} +					mysqli_free_result($result);  				} -				@mysqli_free_result($result);  				$splittime = explode(' ', microtime());  				$splittime = $splittime[0] + $splittime[1]; diff --git a/phpBB/phpbb/db/driver/oracle.php b/phpBB/phpbb/db/driver/oracle.php index 6dcab5dd7d..54238a15ef 100644 --- a/phpBB/phpbb/db/driver/oracle.php +++ b/phpBB/phpbb/db/driver/oracle.php @@ -84,8 +84,6 @@ class oracle extends \phpbb\db\driver\driver  		* but I assume its because the Oracle extension provides a direct method to access it  		* without a query.  		*/ - -		$use_cache = false;  /*  		global $cache; @@ -439,12 +437,17 @@ class oracle extends \phpbb\db\driver\driver  					$this->sql_time += microtime(true) - $this->curtime;  				} +				if (!$this->query_result) +				{ +					return false; +				} +  				if ($cache && $cache_ttl)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result;  					$this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl);  				} -				else if (strpos($query, 'SELECT') === 0 && $this->query_result) +				else if (strpos($query, 'SELECT') === 0)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result;  				} @@ -499,10 +502,10 @@ class oracle extends \phpbb\db\driver\driver  			return $cache->sql_fetchrow($query_id);  		} -		if ($query_id !== false) +		if ($query_id)  		{  			$row = array(); -			$result = @ocifetchinto($query_id, $row, OCI_ASSOC + OCI_RETURN_NULLS); +			$result = ocifetchinto($query_id, $row, OCI_ASSOC + OCI_RETURN_NULLS);  			if (!$result || !$row)  			{ @@ -550,7 +553,7 @@ class oracle extends \phpbb\db\driver\driver  			return $cache->sql_rowseek($rownum, $query_id);  		} -		if ($query_id === false) +		if (!$query_id)  		{  			return false;  		} @@ -583,18 +586,24 @@ class oracle extends \phpbb\db\driver\driver  			{  				$query = 'SELECT ' . $tablename[1] . '_seq.currval FROM DUAL';  				$stmt = @ociparse($this->db_connect_id, $query); -				@ociexecute($stmt, OCI_DEFAULT); +				if ($stmt) +				{ +					$success = @ociexecute($stmt, OCI_DEFAULT); -				$temp_result = @ocifetchinto($stmt, $temp_array, OCI_ASSOC + OCI_RETURN_NULLS); -				@ocifreestatement($stmt); +					if ($success) +					{ +						$temp_result = ocifetchinto($stmt, $temp_array, OCI_ASSOC + OCI_RETURN_NULLS); +						ocifreestatement($stmt); -				if ($temp_result) -				{ -					return $temp_array['CURRVAL']; -				} -				else -				{ -					return false; +						if ($temp_result) +						{ +							return $temp_array['CURRVAL']; +						} +						else +						{ +							return false; +						} +					}  				}  			}  		} @@ -622,7 +631,7 @@ class oracle extends \phpbb\db\driver\driver  		if (isset($this->open_queries[(int) $query_id]))  		{  			unset($this->open_queries[(int) $query_id]); -			return @ocifreestatement($query_id); +			return ocifreestatement($query_id);  		}  		return false; @@ -787,14 +796,20 @@ class oracle extends \phpbb\db\driver\driver  				$endtime = $endtime[0] + $endtime[1];  				$result = @ociparse($this->db_connect_id, $query); -				$success = @ociexecute($result, OCI_DEFAULT); -				$row = array(); - -				while (@ocifetchinto($result, $row, OCI_ASSOC + OCI_RETURN_NULLS)) +				if ($result)  				{ -					// Take the time spent on parsing rows into account +					$success = @ociexecute($result, OCI_DEFAULT); +					if ($success) +					{ +						$row = array(); + +						while (ocifetchinto($result, $row, OCI_ASSOC + OCI_RETURN_NULLS)) +						{ +							// Take the time spent on parsing rows into account +						} +						@ocifreestatement($result); +					}  				} -				@ocifreestatement($result);  				$splittime = explode(' ', microtime());  				$splittime = $splittime[0] + $splittime[1]; diff --git a/phpBB/phpbb/db/driver/postgres.php b/phpBB/phpbb/db/driver/postgres.php index a3b9aa4c6b..44476612c3 100644 --- a/phpBB/phpbb/db/driver/postgres.php +++ b/phpBB/phpbb/db/driver/postgres.php @@ -123,14 +123,17 @@ class postgres extends \phpbb\db\driver\driver  		if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('pgsql_version')) === false)  		{  			$query_id = @pg_query($this->db_connect_id, 'SELECT VERSION() AS version'); -			$row = @pg_fetch_assoc($query_id, null); -			@pg_free_result($query_id); +			if ($query_id) +			{ +				$row = pg_fetch_assoc($query_id, null); +				pg_free_result($query_id); -			$this->sql_server_version = (!empty($row['version'])) ? trim(substr($row['version'], 10)) : 0; +				$this->sql_server_version = (!empty($row['version'])) ? trim(substr($row['version'], 10)) : 0; -			if (!empty($cache) && $use_cache) -			{ -				$cache->put('pgsql_version', $this->sql_server_version); +				if (!empty($cache) && $use_cache) +				{ +					$cache->put('pgsql_version', $this->sql_server_version); +				}  			}  		} @@ -200,12 +203,17 @@ class postgres extends \phpbb\db\driver\driver  					$this->sql_time += microtime(true) - $this->curtime;  				} +				if (!$this->query_result) +				{ +					return false; +				} +  				if ($cache && $cache_ttl)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result;  					$this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl);  				} -				else if (strpos($query, 'SELECT') === 0 && $this->query_result) +				else if (strpos($query, 'SELECT') === 0)  				{  					$this->open_queries[(int) $this->query_result] = $this->query_result;  				} @@ -275,7 +283,7 @@ class postgres extends \phpbb\db\driver\driver  			return $cache->sql_fetchrow($query_id);  		} -		return ($query_id !== false) ? @pg_fetch_assoc($query_id, null) : false; +		return ($query_id) ? pg_fetch_assoc($query_id, null) : false;  	}  	/** @@ -295,7 +303,7 @@ class postgres extends \phpbb\db\driver\driver  			return $cache->sql_rowseek($rownum, $query_id);  		} -		return ($query_id !== false) ? @pg_result_seek($query_id, $rownum) : false; +		return ($query_id) ? @pg_result_seek($query_id, $rownum) : false;  	}  	/** @@ -317,8 +325,8 @@ class postgres extends \phpbb\db\driver\driver  					return false;  				} -				$temp_result = @pg_fetch_assoc($temp_q_id, null); -				@pg_free_result($query_id); +				$temp_result = pg_fetch_assoc($temp_q_id, null); +				pg_free_result($query_id);  				return ($temp_result) ? $temp_result['last_value'] : false;  			} @@ -347,7 +355,7 @@ class postgres extends \phpbb\db\driver\driver  		if (isset($this->open_queries[(int) $query_id]))  		{  			unset($this->open_queries[(int) $query_id]); -			return @pg_free_result($query_id); +			return pg_free_result($query_id);  		}  		return false; @@ -453,12 +461,12 @@ class postgres extends \phpbb\db\driver\driver  					if ($result = @pg_query($this->db_connect_id, "EXPLAIN $explain_query"))  					{ -						while ($row = @pg_fetch_assoc($result, null)) +						while ($row = pg_fetch_assoc($result, null))  						{  							$html_table = $this->sql_report('add_select_row', $query, $html_table, $row);  						} +						pg_free_result($result);  					} -					@pg_free_result($result);  					if ($html_table)  					{ @@ -473,11 +481,14 @@ class postgres extends \phpbb\db\driver\driver  				$endtime = $endtime[0] + $endtime[1];  				$result = @pg_query($this->db_connect_id, $query); -				while ($void = @pg_fetch_assoc($result, null)) +				if ($result)  				{ -					// Take the time spent on parsing rows into account +					while ($void = pg_fetch_assoc($result, null)) +					{ +						// Take the time spent on parsing rows into account +					} +					pg_free_result($result);  				} -				@pg_free_result($result);  				$splittime = explode(' ', microtime());  				$splittime = $splittime[0] + $splittime[1]; diff --git a/phpBB/phpbb/db/driver/sqlite.php b/phpBB/phpbb/db/driver/sqlite.php index d5da0e2438..8e205ebb81 100644 --- a/phpBB/phpbb/db/driver/sqlite.php +++ b/phpBB/phpbb/db/driver/sqlite.php @@ -70,13 +70,16 @@ class sqlite extends \phpbb\db\driver\driver  		if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('sqlite_version')) === false)  		{  			$result = @sqlite_query('SELECT sqlite_version() AS version', $this->db_connect_id); -			$row = @sqlite_fetch_array($result, SQLITE_ASSOC); +			if ($result) +			{ +				$row = sqlite_fetch_array($result, SQLITE_ASSOC); -			$this->sql_server_version = (!empty($row['version'])) ? $row['version'] : 0; +				$this->sql_server_version = (!empty($row['version'])) ? $row['version'] : 0; -			if (!empty($cache) && $use_cache) -			{ -				$cache->put('sqlite_version', $this->sql_server_version); +				if (!empty($cache) && $use_cache) +				{ +					$cache->put('sqlite_version', $this->sql_server_version); +				}  			}  		} @@ -145,14 +148,14 @@ class sqlite extends \phpbb\db\driver\driver  					$this->sql_time += microtime(true) - $this->curtime;  				} -				if ($cache && $cache_ttl) +				if (!$this->query_result)  				{ -					$this->open_queries[(int) $this->query_result] = $this->query_result; -					$this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); +					return false;  				} -				else if (strpos($query, 'SELECT') === 0 && $this->query_result) + +				if ($cache && $cache_ttl)  				{ -					$this->open_queries[(int) $this->query_result] = $this->query_result; +					$this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl);  				}  			}  			else if (defined('DEBUG')) @@ -211,7 +214,7 @@ class sqlite extends \phpbb\db\driver\driver  			return $cache->sql_fetchrow($query_id);  		} -		return ($query_id !== false) ? @sqlite_fetch_array($query_id, SQLITE_ASSOC) : false; +		return ($query_id) ? sqlite_fetch_array($query_id, SQLITE_ASSOC) : false;  	}  	/** @@ -231,7 +234,7 @@ class sqlite extends \phpbb\db\driver\driver  			return $cache->sql_rowseek($rownum, $query_id);  		} -		return ($query_id !== false) ? @sqlite_seek($query_id, $rownum) : false; +		return ($query_id) ? @sqlite_seek($query_id, $rownum) : false;  	}  	/** @@ -362,9 +365,12 @@ class sqlite extends \phpbb\db\driver\driver  				$endtime = $endtime[0] + $endtime[1];  				$result = @sqlite_query($query, $this->db_connect_id); -				while ($void = @sqlite_fetch_array($result, SQLITE_ASSOC)) +				if ($result)  				{ -					// Take the time spent on parsing rows into account +					while ($void = sqlite_fetch_array($result, SQLITE_ASSOC)) +					{ +						// Take the time spent on parsing rows into account +					}  				}  				$splittime = explode(' ', microtime()); diff --git a/phpBB/phpbb/db/driver/sqlite3.php b/phpBB/phpbb/db/driver/sqlite3.php index cc3352af34..0508500c52 100644 --- a/phpBB/phpbb/db/driver/sqlite3.php +++ b/phpBB/phpbb/db/driver/sqlite3.php @@ -102,7 +102,7 @@ class sqlite3 extends \phpbb\db\driver\driver  			break;  			case 'rollback': -				return $this->dbo->exec('ROLLBACK'); +				return @$this->dbo->exec('ROLLBACK');  			break;  		} @@ -134,9 +134,26 @@ class sqlite3 extends \phpbb\db\driver\driver  			if ($this->query_result === false)  			{ +				if ($this->transaction === true && strpos($query, 'INSERT') === 0) +				{ +					$query = preg_replace('/^INSERT INTO/', 'INSERT OR ROLLBACK INTO', $query); +				} +  				if (($this->query_result = @$this->dbo->query($query)) === false)  				{ -					$this->sql_error($query); +					// Try to recover a lost database connection +					if ($this->dbo && !@$this->dbo->lastErrorMsg()) +					{ +						if ($this->sql_connect($this->server, $this->user, '', $this->dbname)) +						{ +							$this->query_result = @$this->dbo->query($query); +						} +					} + +					if ($this->query_result === false) +					{ +						$this->sql_error($query); +					}  				}  				if (defined('DEBUG')) @@ -148,6 +165,11 @@ class sqlite3 extends \phpbb\db\driver\driver  					$this->sql_time += microtime(true) - $this->curtime;  				} +				if (!$this->query_result) +				{ +					return false; +				} +  				if ($cache && $cache_ttl)  				{  					$this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); @@ -208,6 +230,7 @@ class sqlite3 extends \phpbb\db\driver\driver  		if ($query_id === false)  		{ +			/** @var \SQLite3Result $query_id */  			$query_id = $this->query_result;  		} @@ -216,7 +239,7 @@ class sqlite3 extends \phpbb\db\driver\driver  			return $cache->sql_fetchrow($query_id);  		} -		return is_object($query_id) ? $query_id->fetchArray(SQLITE3_ASSOC) : false; +		return is_object($query_id) ? @$query_id->fetchArray(SQLITE3_ASSOC) : false;  	}  	/** @@ -389,9 +412,12 @@ class sqlite3 extends \phpbb\db\driver\driver  				$endtime = $endtime[0] + $endtime[1];  				$result = $this->dbo->query($query); -				while ($void = $result->fetchArray(SQLITE3_ASSOC)) +				if ($result)  				{ -					// Take the time spent on parsing rows into account +						while ($void = $result->fetchArray(SQLITE3_ASSOC)) +						{ +							// Take the time spent on parsing rows into account +						}  				}  				$splittime = explode(' ', microtime()); diff --git a/phpBB/phpbb/db/extractor/base_extractor.php b/phpBB/phpbb/db/extractor/base_extractor.php new file mode 100644 index 0000000000..547c85f066 --- /dev/null +++ b/phpBB/phpbb/db/extractor/base_extractor.php @@ -0,0 +1,252 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +use phpbb\db\extractor\exception\invalid_format_exception; +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +/** + * Abstract base class for database extraction + */ +abstract class base_extractor implements extractor_interface +{ +	/** +	 * @var    string    phpBB root path +	 */ +	protected $phpbb_root_path; + +	/** +	 * @var    \phpbb\request\request_interface +	 */ +	protected $request; + +	/** +	 * @var    \phpbb\db\driver\driver_interface +	 */ +	protected $db; + +	/** +	 * @var    bool +	 */ +	protected $download; + +	/** +	 * @var    bool +	 */ +	protected $store; + +	/** +	 * @var    int +	 */ +	protected $time; + +	/** +	 * @var    string +	 */ +	protected $format; + +	/** +	 * @var    resource +	 */ +	protected $fp; + +	/** +	 * @var string +	 */ +	protected $write; + +	/** +	 * @var string +	 */ +	protected $close; + +	/** +	 * @var bool +	 */ +	protected $run_comp; + +	/** +	 * @var bool +	 */ +	protected $is_initialized; + +	/** +	 * Constructor +	 * +	 * @param string $phpbb_root_path +	 * @param \phpbb\request\request_interface $request +	 * @param \phpbb\db\driver\driver_interface $db +	 */ +	public function __construct($phpbb_root_path, \phpbb\request\request_interface $request, \phpbb\db\driver\driver_interface $db) +	{ +		$this->phpbb_root_path	= $phpbb_root_path; +		$this->request			= $request; +		$this->db				= $db; +		$this->fp				= null; + +		$this->is_initialized   = false; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function init_extractor($format, $filename, $time, $download = false, $store = false) +	{ +		$this->download			= $download; +		$this->store			= $store; +		$this->time				= $time; +		$this->format			= $format; + +		switch ($format) +		{ +			case 'text': +				$ext = '.sql'; +				$open = 'fopen'; +				$this->write = 'fwrite'; +				$this->close = 'fclose'; +				$mimetype = 'text/x-sql'; +			break; +			case 'bzip2': +				$ext = '.sql.bz2'; +				$open = 'bzopen'; +				$this->write = 'bzwrite'; +				$this->close = 'bzclose'; +				$mimetype = 'application/x-bzip2'; +			break; +			case 'gzip': +				$ext = '.sql.gz'; +				$open = 'gzopen'; +				$this->write = 'gzwrite'; +				$this->close = 'gzclose'; +				$mimetype = 'application/x-gzip'; +			break; +			default: +				throw new invalid_format_exception(); +			break; +		} + +		if ($download === true) +		{ +			$name = $filename . $ext; +			header('Cache-Control: private, no-cache'); +			header("Content-Type: $mimetype; name=\"$name\""); +			header("Content-disposition: attachment; filename=$name"); + +			switch ($format) +			{ +				case 'bzip2': +					ob_start(); +				break; + +				case 'gzip': +					if (strpos($this->request->header('Accept-Encoding'), 'gzip') !== false && strpos(strtolower($this->request->header('User-Agent')), 'msie') === false) +					{ +						ob_start('ob_gzhandler'); +					} +					else +					{ +						$this->run_comp = true; +					} +				break; +			} +		} + +		if ($store === true) +		{ +			$file = $this->phpbb_root_path . 'store/' . $filename . $ext; + +			$this->fp = $open($file, 'w'); + +			if (!$this->fp) +			{ +				trigger_error('FILE_WRITE_FAIL', E_USER_ERROR); +			} +		} + +		$this->is_initialized = true; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_end() +	{ +		static $close; + +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		if ($this->store) +		{ +			if ($close === null) +			{ +				$close = $this->close; +			} +			$close($this->fp); +		} + +		// bzip2 must be written all the way at the end +		if ($this->download && $this->format === 'bzip2') +		{ +			$c = ob_get_clean(); +			echo bzcompress($c); +		} +	} + +	/** +	* {@inheritdoc} +	*/ +	public function flush($data) +	{ +		static $write; + +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		if ($this->store === true) +		{ +			if ($write === null) +			{ +				$write = $this->write; +			} +			$write($this->fp, $data); +		} + +		if ($this->download === true) +		{ +			if ($this->format === 'bzip2' || $this->format === 'text' || ($this->format === 'gzip' && !$this->run_comp)) +			{ +				echo $data; +			} + +			// we can write the gzip data as soon as we get it +			if ($this->format === 'gzip') +			{ +				if ($this->run_comp) +				{ +					echo gzencode($data); +				} +				else +				{ +					ob_flush(); +					flush(); +				} +			} +		} +	} +} diff --git a/phpBB/phpbb/db/extractor/exception/extractor_not_initialized_exception.php b/phpBB/phpbb/db/extractor/exception/extractor_not_initialized_exception.php new file mode 100644 index 0000000000..62eb434be1 --- /dev/null +++ b/phpBB/phpbb/db/extractor/exception/extractor_not_initialized_exception.php @@ -0,0 +1,24 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor\exception; + +use phpbb\exception\runtime_exception; + +/** +* This exception is thrown when invalid format is given to the extractor +*/ +class extractor_not_initialized_exception extends runtime_exception +{ + +} diff --git a/phpBB/phpbb/db/extractor/exception/invalid_format_exception.php b/phpBB/phpbb/db/extractor/exception/invalid_format_exception.php new file mode 100644 index 0000000000..6be24cb5dc --- /dev/null +++ b/phpBB/phpbb/db/extractor/exception/invalid_format_exception.php @@ -0,0 +1,22 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor\exception; + +/** +* This exception is thrown when invalid format is given to the extractor +*/ +class invalid_format_exception extends \InvalidArgumentException +{ + +} diff --git a/phpBB/phpbb/db/extractor/extractor_interface.php b/phpBB/phpbb/db/extractor/extractor_interface.php new file mode 100644 index 0000000000..ff45df9bb7 --- /dev/null +++ b/phpBB/phpbb/db/extractor/extractor_interface.php @@ -0,0 +1,80 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +/** +* Database extractor interface +*/ +interface extractor_interface +{ +	/** +	* Start the extraction of the database +	* +	* This function initialize the database extraction. It is required to call this +	* function before calling any other extractor functions. +	* +	* @param string	$format +	* @param string	$filename +	* @param int	$time +	* @param bool	$download +	* @param bool	$store +	* @return null +	* @throws \phpbb\db\extractor\exception\invalid_format_exception when $format is invalid +	*/ +	public function init_extractor($format, $filename, $time, $download = false, $store = false); + +	/** +	* Writes header comments to the database backup +	* +	* @param	string	$table_prefix	prefix of phpBB database tables +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	public function write_start($table_prefix); + +	/** +	* Closes file and/or dumps download data +	* +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	public function write_end(); + +	/** +	* Extracts database table structure +	* +	* @param	string	$table_name	name of the database table +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	public function write_table($table_name); + +	/** +	* Extracts data from database table +	* +	* @param	string	$table_name	name of the database table +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	public function write_data($table_name); + +	/** +	* Writes data to file/download content +	* +	* @param string	$data +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	public function flush($data); +} diff --git a/phpBB/phpbb/db/extractor/factory.php b/phpBB/phpbb/db/extractor/factory.php new file mode 100644 index 0000000000..a1ffb65595 --- /dev/null +++ b/phpBB/phpbb/db/extractor/factory.php @@ -0,0 +1,79 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +/** +* A factory which serves the suitable extractor instance for the given dbal +*/ +class factory +{ +	/** +	 * @var \phpbb\db\driver\driver_interface +	 */ +	protected $db; + +	/** +	 * @var \Symfony\Component\DependencyInjection\ContainerInterface +	 */ +	protected $container; + +	/** +	* Extractor factory constructor +	* +	* @param \phpbb\db\driver\driver_interface							$db +	* @param \Symfony\Component\DependencyInjection\ContainerInterface	$container +	*/ +	public function __construct(\phpbb\db\driver\driver_interface $db, \Symfony\Component\DependencyInjection\ContainerInterface $container) +	{ +		$this->db			= $db; +		$this->container	= $container; +	} + +	/** +	* DB extractor factory getter +	* +	* @return \phpbb\db\extractor\extractor_interface an appropriate instance of the database extractor for the used database driver +	* @throws \InvalidArgumentException when the database driver is unknown +	*/ +	public function get() +	{ +		// Return the appropriate DB extractor +		if ($this->db instanceof \phpbb\db\driver\mssql || $this->db instanceof \phpbb\db\driver\mssql_base) +		{ +			return $this->container->get('dbal.extractor.extractors.mssql_extractor'); +		} +		else if ($this->db instanceof \phpbb\db\driver\mysql_base) +		{ +			return $this->container->get('dbal.extractor.extractors.mysql_extractor'); +		} +		else if ($this->db instanceof \phpbb\db\driver\oracle) +		{ +			return $this->container->get('dbal.extractor.extractors.oracle_extractor'); +		} +		else if ($this->db instanceof \phpbb\db\driver\postgres) +		{ +			return $this->container->get('dbal.extractor.extractors.postgres_extractor'); +		} +		else if ($this->db instanceof \phpbb\db\driver\sqlite) +		{ +			return $this->container->get('dbal.extractor.extractors.sqlite_extractor'); +		} +		else if ($this->db instanceof \phpbb\db\driver\sqlite3) +		{ +			return $this->container->get('dbal.extractor.extractors.sqlite3_extractor'); +		} + +		throw new \InvalidArgumentException('Invalid database driver given'); +	} +} diff --git a/phpBB/phpbb/db/extractor/mssql_extractor.php b/phpBB/phpbb/db/extractor/mssql_extractor.php new file mode 100644 index 0000000000..fc30f4789d --- /dev/null +++ b/phpBB/phpbb/db/extractor/mssql_extractor.php @@ -0,0 +1,524 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class mssql_extractor extends base_extractor +{ +	/** +	* Writes closing line(s) to database backup +	* +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	public function write_end() +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$this->flush("COMMIT\nGO\n"); +		parent::write_end(); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_start($table_prefix) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql_data = "--\n"; +		$sql_data .= "-- phpBB Backup Script\n"; +		$sql_data .= "-- Dump of tables for $table_prefix\n"; +		$sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; +		$sql_data .= "--\n"; +		$sql_data .= "BEGIN TRANSACTION\n"; +		$sql_data .= "GO\n"; +		$this->flush($sql_data); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_table($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql_data = '-- Table: ' . $table_name . "\n"; +		$sql_data .= "IF OBJECT_ID(N'$table_name', N'U') IS NOT NULL\n"; +		$sql_data .= "DROP TABLE $table_name;\n"; +		$sql_data .= "GO\n"; +		$sql_data .= "\nCREATE TABLE [$table_name] (\n"; +		$rows = array(); + +		$text_flag = false; + +		$sql = "SELECT COLUMN_NAME, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') as IS_IDENTITY +			FROM INFORMATION_SCHEMA.COLUMNS +			WHERE TABLE_NAME = '$table_name'"; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$line = "\t[{$row['COLUMN_NAME']}] [{$row['DATA_TYPE']}]"; + +			if ($row['DATA_TYPE'] == 'text') +			{ +				$text_flag = true; +			} + +			if ($row['IS_IDENTITY']) +			{ +				$line .= ' IDENTITY (1 , 1)'; +			} + +			if ($row['CHARACTER_MAXIMUM_LENGTH'] && $row['DATA_TYPE'] !== 'text') +			{ +				$line .= ' (' . $row['CHARACTER_MAXIMUM_LENGTH'] . ')'; +			} + +			if ($row['IS_NULLABLE'] == 'YES') +			{ +				$line .= ' NULL'; +			} +			else +			{ +				$line .= ' NOT NULL'; +			} + +			if ($row['COLUMN_DEFAULT']) +			{ +				$line .= ' DEFAULT ' . $row['COLUMN_DEFAULT']; +			} + +			$rows[] = $line; +		} +		$this->db->sql_freeresult($result); + +		$sql_data .= implode(",\n", $rows); +		$sql_data .= "\n) ON [PRIMARY]"; + +		if ($text_flag) +		{ +			$sql_data .= " TEXTIMAGE_ON [PRIMARY]"; +		} + +		$sql_data .= "\nGO\n\n"; +		$rows = array(); + +		$sql = "SELECT CONSTRAINT_NAME, COLUMN_NAME +			FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE +			WHERE TABLE_NAME = '$table_name'"; +		$result = $this->db->sql_query($sql); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if (!sizeof($rows)) +			{ +				$sql_data .= "ALTER TABLE [$table_name] WITH NOCHECK ADD\n"; +				$sql_data .= "\tCONSTRAINT [{$row['CONSTRAINT_NAME']}] PRIMARY KEY  CLUSTERED \n\t(\n"; +			} +			$rows[] = "\t\t[{$row['COLUMN_NAME']}]"; +		} +		if (sizeof($rows)) +		{ +			$sql_data .= implode(",\n", $rows); +			$sql_data .= "\n\t)  ON [PRIMARY] \nGO\n"; +		} +		$this->db->sql_freeresult($result); + +		$index = array(); +		$sql = "EXEC sp_statistics '$table_name'"; +		$result = $this->db->sql_query($sql); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if ($row['TYPE'] == 3) +			{ +				$index[$row['INDEX_NAME']][] = '[' . $row['COLUMN_NAME'] . ']'; +			} +		} +		$this->db->sql_freeresult($result); + +		foreach ($index as $index_name => $column_name) +		{ +			$index[$index_name] = implode(', ', $column_name); +		} + +		foreach ($index as $index_name => $columns) +		{ +			$sql_data .= "\nCREATE  INDEX [$index_name] ON [$table_name]($columns) ON [PRIMARY]\nGO\n"; +		} +		$this->flush($sql_data); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_data($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		if ($this->db->get_sql_layer() === 'mssql') +		{ +			$this->write_data_mssql($table_name); +		} +		else if ($this->db->get_sql_layer() === 'mssqlnative') +		{ +			$this->write_data_mssqlnative($table_name); +		} +		else +		{ +			$this->write_data_odbc($table_name); +		} +	} + +	/** +	* Extracts data from database table (for MSSQL driver) +	* +	* @param	string	$table_name	name of the database table +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	protected function write_data_mssql($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$ary_type = $ary_name = array(); +		$ident_set = false; +		$sql_data = ''; + +		// Grab all of the data from current table. +		$sql = "SELECT * +			FROM $table_name"; +		$result = $this->db->sql_query($sql); + +		$retrieved_data = mssql_num_rows($result); + +		$i_num_fields = mssql_num_fields($result); + +		for ($i = 0; $i < $i_num_fields; $i++) +		{ +			$ary_type[$i] = mssql_field_type($result, $i); +			$ary_name[$i] = mssql_field_name($result, $i); +		} + +		if ($retrieved_data) +		{ +			$sql = "SELECT 1 as has_identity +				FROM INFORMATION_SCHEMA.COLUMNS +				WHERE COLUMNPROPERTY(object_id('$table_name'), COLUMN_NAME, 'IsIdentity') = 1"; +			$result2 = $this->db->sql_query($sql); +			$row2 = $this->db->sql_fetchrow($result2); +			if (!empty($row2['has_identity'])) +			{ +				$sql_data .= "\nSET IDENTITY_INSERT $table_name ON\nGO\n"; +				$ident_set = true; +			} +			$this->db->sql_freeresult($result2); +		} + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$schema_vals = $schema_fields = array(); + +			// Build the SQL statement to recreate the data. +			for ($i = 0; $i < $i_num_fields; $i++) +			{ +				$str_val = $row[$ary_name[$i]]; + +				if (preg_match('#char|text|bool|varbinary#i', $ary_type[$i])) +				{ +					$str_quote = ''; +					$str_empty = "''"; +					$str_val = sanitize_data_mssql(str_replace("'", "''", $str_val)); +				} +				else if (preg_match('#date|timestamp#i', $ary_type[$i])) +				{ +					if (empty($str_val)) +					{ +						$str_quote = ''; +					} +					else +					{ +						$str_quote = "'"; +					} +				} +				else +				{ +					$str_quote = ''; +					$str_empty = 'NULL'; +				} + +				if (empty($str_val) && $str_val !== '0' && !(is_int($str_val) || is_float($str_val))) +				{ +					$str_val = $str_empty; +				} + +				$schema_vals[$i] = $str_quote . $str_val . $str_quote; +				$schema_fields[$i] = $ary_name[$i]; +			} + +			// Take the ordered fields and their associated data and build it +			// into a valid sql statement to recreate that field in the data. +			$sql_data .= "INSERT INTO $table_name (" . implode(', ', $schema_fields) . ') VALUES (' . implode(', ', $schema_vals) . ");\nGO\n"; + +			$this->flush($sql_data); +			$sql_data = ''; +		} +		$this->db->sql_freeresult($result); + +		if ($retrieved_data && $ident_set) +		{ +			$sql_data .= "\nSET IDENTITY_INSERT $table_name OFF\nGO\n"; +		} +		$this->flush($sql_data); +	} + +	/** +	* Extracts data from database table (for MSSQL Native driver) +	* +	* @param	string	$table_name	name of the database table +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	protected function write_data_mssqlnative($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$ary_type = $ary_name = array(); +		$ident_set = false; +		$sql_data = ''; + +		// Grab all of the data from current table. +		$sql = "SELECT * FROM $table_name"; +		$this->db->mssqlnative_set_query_options(array('Scrollable' => SQLSRV_CURSOR_STATIC)); +		$result = $this->db->sql_query($sql); + +		$retrieved_data = $this->db->mssqlnative_num_rows($result); + +		if (!$retrieved_data) +		{ +			$this->db->sql_freeresult($result); +			return; +		} + +		$sql = "SELECT COLUMN_NAME, DATA_TYPE +			FROM INFORMATION_SCHEMA.COLUMNS +			WHERE INFORMATION_SCHEMA.COLUMNS.TABLE_NAME = '" . $this->db->sql_escape($table_name) . "'"; +		$result_fields = $this->db->sql_query($sql); + +		$i_num_fields = 0; +		while ($row = $this->db->sql_fetchrow($result_fields)) +		{ +			$ary_type[$i_num_fields] = $row['DATA_TYPE']; +			$ary_name[$i_num_fields] = $row['COLUMN_NAME']; +			$i_num_fields++; +		} +		$this->db->sql_freeresult($result_fields); + +		$sql = "SELECT 1 as has_identity +			FROM INFORMATION_SCHEMA.COLUMNS +			WHERE COLUMNPROPERTY(object_id('$table_name'), COLUMN_NAME, 'IsIdentity') = 1"; +		$result2 = $this->db->sql_query($sql); +		$row2 = $this->db->sql_fetchrow($result2); + +		if (!empty($row2['has_identity'])) +		{ +			$sql_data .= "\nSET IDENTITY_INSERT $table_name ON\nGO\n"; +			$ident_set = true; +		} +		$this->db->sql_freeresult($result2); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$schema_vals = $schema_fields = array(); + +			// Build the SQL statement to recreate the data. +			for ($i = 0; $i < $i_num_fields; $i++) +			{ +				$str_val = $row[$ary_name[$i]]; + +				// defaults to type number - better quote just to be safe, so check for is_int too +				if (is_int($ary_type[$i]) || preg_match('#char|text|bool|varbinary#i', $ary_type[$i])) +				{ +					$str_quote = ''; +					$str_empty = "''"; +					$str_val = sanitize_data_mssql(str_replace("'", "''", $str_val)); +				} +				else if (preg_match('#date|timestamp#i', $ary_type[$i])) +				{ +					if (empty($str_val)) +					{ +						$str_quote = ''; +					} +					else +					{ +						$str_quote = "'"; +					} +				} +				else +				{ +					$str_quote = ''; +					$str_empty = 'NULL'; +				} + +				if (empty($str_val) && $str_val !== '0' && !(is_int($str_val) || is_float($str_val))) +				{ +					$str_val = $str_empty; +				} + +				$schema_vals[$i] = $str_quote . $str_val . $str_quote; +				$schema_fields[$i] = $ary_name[$i]; +			} + +			// Take the ordered fields and their associated data and build it +			// into a valid sql statement to recreate that field in the data. +			$sql_data .= "INSERT INTO $table_name (" . implode(', ', $schema_fields) . ') VALUES (' . implode(', ', $schema_vals) . ");\nGO\n"; + +			$this->flush($sql_data); +			$sql_data = ''; +		} +		$this->db->sql_freeresult($result); + +		if ($ident_set) +		{ +			$sql_data .= "\nSET IDENTITY_INSERT $table_name OFF\nGO\n"; +		} +		$this->flush($sql_data); +	} + +	/** +	* Extracts data from database table (for ODBC driver) +	* +	* @param	string	$table_name	name of the database table +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	protected function write_data_odbc($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$ary_type = $ary_name = array(); +		$ident_set = false; +		$sql_data = ''; + +		// Grab all of the data from current table. +		$sql = "SELECT * +			FROM $table_name"; +		$result = $this->db->sql_query($sql); + +		$retrieved_data = odbc_num_rows($result); + +		if ($retrieved_data) +		{ +			$sql = "SELECT 1 as has_identity +				FROM INFORMATION_SCHEMA.COLUMNS +				WHERE COLUMNPROPERTY(object_id('$table_name'), COLUMN_NAME, 'IsIdentity') = 1"; +			$result2 = $this->db->sql_query($sql); +			$row2 = $this->db->sql_fetchrow($result2); +			if (!empty($row2['has_identity'])) +			{ +				$sql_data .= "\nSET IDENTITY_INSERT $table_name ON\nGO\n"; +				$ident_set = true; +			} +			$this->db->sql_freeresult($result2); +		} + +		$i_num_fields = odbc_num_fields($result); + +		for ($i = 0; $i < $i_num_fields; $i++) +		{ +			$ary_type[$i] = odbc_field_type($result, $i + 1); +			$ary_name[$i] = odbc_field_name($result, $i + 1); +		} + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$schema_vals = $schema_fields = array(); + +			// Build the SQL statement to recreate the data. +			for ($i = 0; $i < $i_num_fields; $i++) +			{ +				$str_val = $row[$ary_name[$i]]; + +				if (preg_match('#char|text|bool|varbinary#i', $ary_type[$i])) +				{ +					$str_quote = ''; +					$str_empty = "''"; +					$str_val = sanitize_data_mssql(str_replace("'", "''", $str_val)); +				} +				else if (preg_match('#date|timestamp#i', $ary_type[$i])) +				{ +					if (empty($str_val)) +					{ +						$str_quote = ''; +					} +					else +					{ +						$str_quote = "'"; +					} +				} +				else +				{ +					$str_quote = ''; +					$str_empty = 'NULL'; +				} + +				if (empty($str_val) && $str_val !== '0' && !(is_int($str_val) || is_float($str_val))) +				{ +					$str_val = $str_empty; +				} + +				$schema_vals[$i] = $str_quote . $str_val . $str_quote; +				$schema_fields[$i] = $ary_name[$i]; +			} + +			// Take the ordered fields and their associated data and build it +			// into a valid sql statement to recreate that field in the data. +			$sql_data .= "INSERT INTO $table_name (" . implode(', ', $schema_fields) . ') VALUES (' . implode(', ', $schema_vals) . ");\nGO\n"; + +			$this->flush($sql_data); + +			$sql_data = ''; + +		} +		$this->db->sql_freeresult($result); + +		if ($retrieved_data && $ident_set) +		{ +			$sql_data .= "\nSET IDENTITY_INSERT $table_name OFF\nGO\n"; +		} +		$this->flush($sql_data); +	} +} diff --git a/phpBB/phpbb/db/extractor/mysql_extractor.php b/phpBB/phpbb/db/extractor/mysql_extractor.php new file mode 100644 index 0000000000..34e309c19e --- /dev/null +++ b/phpBB/phpbb/db/extractor/mysql_extractor.php @@ -0,0 +1,403 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class mysql_extractor extends base_extractor +{ +	/** +	* {@inheritdoc} +	*/ +	public function write_start($table_prefix) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql_data = "#\n"; +		$sql_data .= "# phpBB Backup Script\n"; +		$sql_data .= "# Dump of tables for $table_prefix\n"; +		$sql_data .= "# DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; +		$sql_data .= "#\n"; +		$this->flush($sql_data); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_table($table_name) +	{ +		static $new_extract; + +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		if ($new_extract === null) +		{ +			if ($this->db->get_sql_layer() === 'mysqli' || version_compare($this->db->sql_server_info(true), '3.23.20', '>=')) +			{ +				$new_extract = true; +			} +			else +			{ +				$new_extract = false; +			} +		} + +		if ($new_extract) +		{ +			$this->new_write_table($table_name); +		} +		else +		{ +			$this->old_write_table($table_name); +		} +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_data($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		if ($this->db->get_sql_layer() === 'mysqli') +		{ +			$this->write_data_mysqli($table_name); +		} +		else +		{ +			$this->write_data_mysql($table_name); +		} +	} + +	/** +	* Extracts data from database table (for MySQLi driver) +	* +	* @param	string	$table_name	name of the database table +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	protected function write_data_mysqli($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql = "SELECT * +			FROM $table_name"; +		$result = mysqli_query($this->db->get_db_connect_id(), $sql, MYSQLI_USE_RESULT); +		if ($result != false) +		{ +			$fields_cnt = mysqli_num_fields($result); + +			// Get field information +			$field = mysqli_fetch_fields($result); +			$field_set = array(); + +			for ($j = 0; $j < $fields_cnt; $j++) +			{ +				$field_set[] = $field[$j]->name; +			} + +			$search			= array("\\", "'", "\x00", "\x0a", "\x0d", "\x1a", '"'); +			$replace		= array("\\\\", "\\'", '\0', '\n', '\r', '\Z', '\\"'); +			$fields			= implode(', ', $field_set); +			$sql_data		= 'INSERT INTO ' . $table_name . ' (' . $fields . ') VALUES '; +			$first_set		= true; +			$query_len		= 0; +			$max_len		= get_usable_memory(); + +			while ($row = mysqli_fetch_row($result)) +			{ +				$values	= array(); +				if ($first_set) +				{ +					$query = $sql_data . '('; +				} +				else +				{ +					$query  .= ',('; +				} + +				for ($j = 0; $j < $fields_cnt; $j++) +				{ +					if (!isset($row[$j]) || is_null($row[$j])) +					{ +						$values[$j] = 'NULL'; +					} +					else if (($field[$j]->flags & 32768) && !($field[$j]->flags & 1024)) +					{ +						$values[$j] = $row[$j]; +					} +					else +					{ +						$values[$j] = "'" . str_replace($search, $replace, $row[$j]) . "'"; +					} +				} +				$query .= implode(', ', $values) . ')'; + +				$query_len += strlen($query); +				if ($query_len > $max_len) +				{ +					$this->flush($query . ";\n\n"); +					$query = ''; +					$query_len = 0; +					$first_set = true; +				} +				else +				{ +					$first_set = false; +				} +			} +			mysqli_free_result($result); + +			// check to make sure we have nothing left to flush +			if (!$first_set && $query) +			{ +				$this->flush($query . ";\n\n"); +			} +		} +	} + +	/** +	* Extracts data from database table (for MySQL driver) +	* +	* @param	string	$table_name	name of the database table +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	protected function write_data_mysql($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql = "SELECT * +			FROM $table_name"; +		$result = mysql_unbuffered_query($sql, $this->db->get_db_connect_id()); + +		if ($result != false) +		{ +			$fields_cnt = mysql_num_fields($result); + +			// Get field information +			$field = array(); +			for ($i = 0; $i < $fields_cnt; $i++) +			{ +				$field[] = mysql_fetch_field($result, $i); +			} +			$field_set = array(); + +			for ($j = 0; $j < $fields_cnt; $j++) +			{ +				$field_set[] = $field[$j]->name; +			} + +			$search			= array("\\", "'", "\x00", "\x0a", "\x0d", "\x1a", '"'); +			$replace		= array("\\\\", "\\'", '\0', '\n', '\r', '\Z', '\\"'); +			$fields			= implode(', ', $field_set); +			$sql_data		= 'INSERT INTO ' . $table_name . ' (' . $fields . ') VALUES '; +			$first_set		= true; +			$query_len		= 0; +			$max_len		= get_usable_memory(); + +			while ($row = mysql_fetch_row($result)) +			{ +				$values = array(); +				if ($first_set) +				{ +					$query = $sql_data . '('; +				} +				else +				{ +					$query  .= ',('; +				} + +				for ($j = 0; $j < $fields_cnt; $j++) +				{ +					if (!isset($row[$j]) || is_null($row[$j])) +					{ +						$values[$j] = 'NULL'; +					} +					else if ($field[$j]->numeric && ($field[$j]->type !== 'timestamp')) +					{ +						$values[$j] = $row[$j]; +					} +					else +					{ +						$values[$j] = "'" . str_replace($search, $replace, $row[$j]) . "'"; +					} +				} +				$query .= implode(', ', $values) . ')'; + +				$query_len += strlen($query); +				if ($query_len > $max_len) +				{ +					$this->flush($query . ";\n\n"); +					$query = ''; +					$query_len = 0; +					$first_set = true; +				} +				else +				{ +					$first_set = false; +				} +			} +			mysql_free_result($result); + +			// check to make sure we have nothing left to flush +			if (!$first_set && $query) +			{ +				$this->flush($query . ";\n\n"); +			} +		} +	} + +	/** +	* Extracts database table structure (for MySQLi or MySQL 3.23.20+) +	* +	* @param	string	$table_name	name of the database table +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	protected function new_write_table($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql = 'SHOW CREATE TABLE ' . $table_name; +		$result = $this->db->sql_query($sql); +		$row = $this->db->sql_fetchrow($result); + +		$sql_data = '# Table: ' . $table_name . "\n"; +		$sql_data .= "DROP TABLE IF EXISTS $table_name;\n"; +		$this->flush($sql_data . $row['Create Table'] . ";\n\n"); + +		$this->db->sql_freeresult($result); +	} + +	/** +	* Extracts database table structure (for MySQL verisons older than 3.23.20) +	* +	* @param	string	$table_name	name of the database table +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	protected function old_write_table($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql_data = '# Table: ' . $table_name . "\n"; +		$sql_data .= "DROP TABLE IF EXISTS $table_name;\n"; +		$sql_data .= "CREATE TABLE $table_name(\n"; +		$rows = array(); + +		$sql = "SHOW FIELDS +			FROM $table_name"; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$line = '   ' . $row['Field'] . ' ' . $row['Type']; + +			if (!is_null($row['Default'])) +			{ +				$line .= " DEFAULT '{$row['Default']}'"; +			} + +			if ($row['Null'] != 'YES') +			{ +				$line .= ' NOT NULL'; +			} + +			if ($row['Extra'] != '') +			{ +				$line .= ' ' . $row['Extra']; +			} + +			$rows[] = $line; +		} +		$this->db->sql_freeresult($result); + +		$sql = "SHOW KEYS +			FROM $table_name"; + +		$result = $this->db->sql_query($sql); + +		$index = array(); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$kname = $row['Key_name']; + +			if ($kname != 'PRIMARY') +			{ +				if ($row['Non_unique'] == 0) +				{ +					$kname = "UNIQUE|$kname"; +				} +			} + +			if ($row['Sub_part']) +			{ +				$row['Column_name'] .= '(' . $row['Sub_part'] . ')'; +			} +			$index[$kname][] = $row['Column_name']; +		} +		$this->db->sql_freeresult($result); + +		foreach ($index as $key => $columns) +		{ +			$line = '   '; + +			if ($key == 'PRIMARY') +			{ +				$line .= 'PRIMARY KEY (' . implode(', ', $columns) . ')'; +			} +			else if (strpos($key, 'UNIQUE') === 0) +			{ +				$line .= 'UNIQUE ' . substr($key, 7) . ' (' . implode(', ', $columns) . ')'; +			} +			else if (strpos($key, 'FULLTEXT') === 0) +			{ +				$line .= 'FULLTEXT ' . substr($key, 9) . ' (' . implode(', ', $columns) . ')'; +			} +			else +			{ +				$line .= "KEY $key (" . implode(', ', $columns) . ')'; +			} + +			$rows[] = $line; +		} + +		$sql_data .= implode(",\n", $rows); +		$sql_data .= "\n);\n\n"; + +		$this->flush($sql_data); +	} +} diff --git a/phpBB/phpbb/db/extractor/oracle_extractor.php b/phpBB/phpbb/db/extractor/oracle_extractor.php new file mode 100644 index 0000000000..79a991889b --- /dev/null +++ b/phpBB/phpbb/db/extractor/oracle_extractor.php @@ -0,0 +1,263 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class oracle_extractor extends base_extractor +{ +	/** +	* {@inheritdoc} +	*/ +	public function write_table($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql_data = '-- Table: ' . $table_name . "\n"; +		$sql_data .= "DROP TABLE $table_name\n/\n"; +		$sql_data .= "\nCREATE TABLE $table_name (\n"; + +		$sql = "SELECT COLUMN_NAME, DATA_TYPE, DATA_PRECISION, DATA_LENGTH, NULLABLE, DATA_DEFAULT +			FROM ALL_TAB_COLS +			WHERE table_name = '{$table_name}'"; +		$result = $this->db->sql_query($sql); + +		$rows = array(); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$line = '  "' . $row['column_name'] . '" ' . $row['data_type']; + +			if ($row['data_type'] !== 'CLOB') +			{ +				if ($row['data_type'] !== 'VARCHAR2' && $row['data_type'] !== 'CHAR') +				{ +					$line .= '(' . $row['data_precision'] . ')'; +				} +				else +				{ +					$line .= '(' . $row['data_length'] . ')'; +				} +			} + +			if (!empty($row['data_default'])) +			{ +				$line .= ' DEFAULT ' . $row['data_default']; +			} + +			if ($row['nullable'] == 'N') +			{ +				$line .= ' NOT NULL'; +			} +			$rows[] = $line; +		} +		$this->db->sql_freeresult($result); + +		$sql = "SELECT A.CONSTRAINT_NAME, A.COLUMN_NAME +			FROM USER_CONS_COLUMNS A, USER_CONSTRAINTS B +			WHERE A.CONSTRAINT_NAME = B.CONSTRAINT_NAME +				AND B.CONSTRAINT_TYPE = 'P' +				AND A.TABLE_NAME = '{$table_name}'"; +		$result = $this->db->sql_query($sql); + +		$primary_key = array(); +		$constraint_name = ''; +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$constraint_name = '"' . $row['constraint_name'] . '"'; +			$primary_key[] = '"' . $row['column_name'] . '"'; +		} +		$this->db->sql_freeresult($result); + +		if (sizeof($primary_key)) +		{ +			$rows[] = "  CONSTRAINT {$constraint_name} PRIMARY KEY (" . implode(', ', $primary_key) . ')'; +		} + +		$sql = "SELECT A.CONSTRAINT_NAME, A.COLUMN_NAME +			FROM USER_CONS_COLUMNS A, USER_CONSTRAINTS B +			WHERE A.CONSTRAINT_NAME = B.CONSTRAINT_NAME +				AND B.CONSTRAINT_TYPE = 'U' +				AND A.TABLE_NAME = '{$table_name}'"; +		$result = $this->db->sql_query($sql); + +		$unique = array(); +		$constraint_name = ''; +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$constraint_name = '"' . $row['constraint_name'] . '"'; +			$unique[] = '"' . $row['column_name'] . '"'; +		} +		$this->db->sql_freeresult($result); + +		if (sizeof($unique)) +		{ +			$rows[] = "  CONSTRAINT {$constraint_name} UNIQUE (" . implode(', ', $unique) . ')'; +		} + +		$sql_data .= implode(",\n", $rows); +		$sql_data .= "\n)\n/\n"; + +		$sql = "SELECT A.REFERENCED_NAME, C.* +			FROM USER_DEPENDENCIES A, USER_TRIGGERS B, USER_SEQUENCES C +			WHERE A.REFERENCED_TYPE = 'SEQUENCE' +				AND A.NAME = B.TRIGGER_NAME +				AND B.TABLE_NAME = '{$table_name}' +				AND C.SEQUENCE_NAME = A.REFERENCED_NAME"; +		$result = $this->db->sql_query($sql); + +		$type = $this->request->variable('type', ''); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$sql_data .= "\nDROP SEQUENCE \"{$row['referenced_name']}\"\n/\n"; +			$sql_data .= "\nCREATE SEQUENCE \"{$row['referenced_name']}\""; + +			if ($type == 'full') +			{ +				$sql_data .= ' START WITH ' . $row['last_number']; +			} + +			$sql_data .= "\n/\n"; +		} +		$this->db->sql_freeresult($result); + +		$sql = "SELECT DESCRIPTION, WHEN_CLAUSE, TRIGGER_BODY +			FROM USER_TRIGGERS +			WHERE TABLE_NAME = '{$table_name}'"; +		$result = $this->db->sql_query($sql); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$sql_data .= "\nCREATE OR REPLACE TRIGGER {$row['description']}WHEN ({$row['when_clause']})\n{$row['trigger_body']}\n/\n"; +		} +		$this->db->sql_freeresult($result); + +		$sql = "SELECT A.INDEX_NAME, B.COLUMN_NAME +			FROM USER_INDEXES A, USER_IND_COLUMNS B +			WHERE A.UNIQUENESS = 'NONUNIQUE' +				AND A.INDEX_NAME = B.INDEX_NAME +				AND B.TABLE_NAME = '{$table_name}'"; +		$result = $this->db->sql_query($sql); + +		$index = array(); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$index[$row['index_name']][] = $row['column_name']; +		} + +		foreach ($index as $index_name => $column_names) +		{ +			$sql_data .= "\nCREATE INDEX $index_name ON $table_name(" . implode(', ', $column_names) . ")\n/\n"; +		} +		$this->db->sql_freeresult($result); +		$this->flush($sql_data); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_data($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$ary_type = $ary_name = array(); + +		// Grab all of the data from current table. +		$sql = "SELECT * +			FROM $table_name"; +		$result = $this->db->sql_query($sql); + +		$i_num_fields = ocinumcols($result); + +		for ($i = 0; $i < $i_num_fields; $i++) +		{ +			$ary_type[$i] = ocicolumntype($result, $i + 1); +			$ary_name[$i] = ocicolumnname($result, $i + 1); +		} + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$schema_vals = $schema_fields = array(); + +			// Build the SQL statement to recreate the data. +			for ($i = 0; $i < $i_num_fields; $i++) +			{ +				// Oracle uses uppercase - we use lowercase +				$str_val = $row[strtolower($ary_name[$i])]; + +				if (preg_match('#char|text|bool|raw|clob#i', $ary_type[$i])) +				{ +					$str_quote = ''; +					$str_empty = "''"; +					$str_val = sanitize_data_oracle($str_val); +				} +				else if (preg_match('#date|timestamp#i', $ary_type[$i])) +				{ +					if (empty($str_val)) +					{ +						$str_quote = ''; +					} +					else +					{ +						$str_quote = "'"; +					} +				} +				else +				{ +					$str_quote = ''; +					$str_empty = 'NULL'; +				} + +				if (empty($str_val) && $str_val !== '0') +				{ +					$str_val = $str_empty; +				} + +				$schema_vals[$i] = $str_quote . $str_val . $str_quote; +				$schema_fields[$i] = '"' . $ary_name[$i] . '"'; +			} + +			// Take the ordered fields and their associated data and build it +			// into a valid sql statement to recreate that field in the data. +			$sql_data = "INSERT INTO $table_name (" . implode(', ', $schema_fields) . ') VALUES (' . implode(', ', $schema_vals) . ")\n/\n"; + +			$this->flush($sql_data); +		} +		$this->db->sql_freeresult($result); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_start($table_prefix) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql_data = "--\n"; +		$sql_data .= "-- phpBB Backup Script\n"; +		$sql_data .= "-- Dump of tables for $table_prefix\n"; +		$sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; +		$sql_data .= "--\n"; +		$this->flush($sql_data); +	} +} diff --git a/phpBB/phpbb/db/extractor/postgres_extractor.php b/phpBB/phpbb/db/extractor/postgres_extractor.php new file mode 100644 index 0000000000..a98e39621c --- /dev/null +++ b/phpBB/phpbb/db/extractor/postgres_extractor.php @@ -0,0 +1,339 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class postgres_extractor extends base_extractor +{ +	/** +	* {@inheritdoc} +	*/ +	public function write_start($table_prefix) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql_data = "--\n"; +		$sql_data .= "-- phpBB Backup Script\n"; +		$sql_data .= "-- Dump of tables for $table_prefix\n"; +		$sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; +		$sql_data .= "--\n"; +		$sql_data .= "BEGIN TRANSACTION;\n"; +		$this->flush($sql_data); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_table($table_name) +	{ +		static $domains_created = array(); + +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql = "SELECT a.domain_name, a.data_type, a.character_maximum_length, a.domain_default +			FROM INFORMATION_SCHEMA.domains a, INFORMATION_SCHEMA.column_domain_usage b +			WHERE a.domain_name = b.domain_name +				AND b.table_name = '{$table_name}'"; +		$result = $this->db->sql_query($sql); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if (empty($domains_created[$row['domain_name']])) +			{ +				$domains_created[$row['domain_name']] = true; +				//$sql_data = "DROP DOMAIN {$row['domain_name']};\n"; +				$sql_data = "CREATE DOMAIN {$row['domain_name']} as {$row['data_type']}"; +				if (!empty($row['character_maximum_length'])) +				{ +					$sql_data .= '(' . $row['character_maximum_length'] . ')'; +				} +				$sql_data .= ' NOT NULL'; +				if (!empty($row['domain_default'])) +				{ +					$sql_data .= ' DEFAULT ' . $row['domain_default']; +				} +				$this->flush($sql_data . ";\n"); +			} +		} +		$this->db->sql_freeresult($result); + +		$sql_data = '-- Table: ' . $table_name . "\n"; +		$sql_data .= "DROP TABLE $table_name;\n"; +		// PGSQL does not "tightly" bind sequences and tables, we must guess... +		$sql = "SELECT relname +			FROM pg_class +			WHERE relkind = 'S' +				AND relname = '{$table_name}_seq'"; +		$result = $this->db->sql_query($sql); +		// We don't even care about storing the results. We already know the answer if we get rows back. +		if ($this->db->sql_fetchrow($result)) +		{ +			$sql_data .= "DROP SEQUENCE {$table_name}_seq;\n"; +			$sql_data .= "CREATE SEQUENCE {$table_name}_seq;\n"; +		} +		$this->db->sql_freeresult($result); + +		$field_query = "SELECT a.attnum, a.attname as field, t.typname as type, a.attlen as length, a.atttypmod as lengthvar, a.attnotnull as notnull +			FROM pg_class c, pg_attribute a, pg_type t +			WHERE c.relname = '" . $this->db->sql_escape($table_name) . "' +				AND a.attnum > 0 +				AND a.attrelid = c.oid +				AND a.atttypid = t.oid +			ORDER BY a.attnum"; +		$result = $this->db->sql_query($field_query); + +		$sql_data .= "CREATE TABLE $table_name(\n"; +		$lines = array(); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			// Get the data from the table +			$sql_get_default = "SELECT pg_get_expr(d.adbin, d.adrelid) as rowdefault +				FROM pg_attrdef d, pg_class c +				WHERE (c.relname = '" . $this->db->sql_escape($table_name) . "') +					AND (c.oid = d.adrelid) +					AND d.adnum = " . $row['attnum']; +			$def_res = $this->db->sql_query($sql_get_default); +			$def_row = $this->db->sql_fetchrow($def_res); +			$this->db->sql_freeresult($def_res); + +			if (empty($def_row)) +			{ +				unset($row['rowdefault']); +			} +			else +			{ +				$row['rowdefault'] = $def_row['rowdefault']; +			} + +			if ($row['type'] == 'bpchar') +			{ +				// Internally stored as bpchar, but isn't accepted in a CREATE TABLE statement. +				$row['type'] = 'char'; +			} + +			$line = '  ' . $row['field'] . ' ' . $row['type']; + +			if (strpos($row['type'], 'char') !== false) +			{ +				if ($row['lengthvar'] > 0) +				{ +					$line .= '(' . ($row['lengthvar'] - 4) . ')'; +				} +			} + +			if (strpos($row['type'], 'numeric') !== false) +			{ +				$line .= '('; +				$line .= sprintf("%s,%s", (($row['lengthvar'] >> 16) & 0xffff), (($row['lengthvar'] - 4) & 0xffff)); +				$line .= ')'; +			} + +			if (isset($row['rowdefault'])) +			{ +				$line .= ' DEFAULT ' . $row['rowdefault']; +			} + +			if ($row['notnull'] == 't') +			{ +				$line .= ' NOT NULL'; +			} + +			$lines[] = $line; +		} +		$this->db->sql_freeresult($result); + +		// Get the listing of primary keys. +		$sql_pri_keys = "SELECT ic.relname as index_name, bc.relname as tab_name, ta.attname as column_name, i.indisunique as unique_key, i.indisprimary as primary_key +			FROM pg_class bc, pg_class ic, pg_index i, pg_attribute ta, pg_attribute ia +			WHERE (bc.oid = i.indrelid) +				AND (ic.oid = i.indexrelid) +				AND (ia.attrelid = i.indexrelid) +				AND	(ta.attrelid = bc.oid) +				AND (bc.relname = '" . $this->db->sql_escape($table_name) . "') +				AND (ta.attrelid = i.indrelid) +				AND (ta.attnum = i.indkey[ia.attnum-1]) +			ORDER BY index_name, tab_name, column_name"; + +		$result = $this->db->sql_query($sql_pri_keys); + +		$index_create = $index_rows = $primary_key = array(); + +		// We do this in two steps. It makes placing the comma easier +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if ($row['primary_key'] == 't') +			{ +				$primary_key[] = $row['column_name']; +				$primary_key_name = $row['index_name']; +			} +			else +			{ +				// We have to store this all this info because it is possible to have a multi-column key... +				// we can loop through it again and build the statement +				$index_rows[$row['index_name']]['table'] = $table_name; +				$index_rows[$row['index_name']]['unique'] = ($row['unique_key'] == 't') ? true : false; +				$index_rows[$row['index_name']]['column_names'][] = $row['column_name']; +			} +		} +		$this->db->sql_freeresult($result); + +		if (!empty($index_rows)) +		{ +			foreach ($index_rows as $idx_name => $props) +			{ +				$index_create[] = 'CREATE ' . ($props['unique'] ? 'UNIQUE ' : '') . "INDEX $idx_name ON $table_name (" . implode(', ', $props['column_names']) . ");"; +			} +		} + +		if (!empty($primary_key)) +		{ +			$lines[] = "  CONSTRAINT $primary_key_name PRIMARY KEY (" . implode(', ', $primary_key) . ")"; +		} + +		// Generate constraint clauses for CHECK constraints +		$sql_checks = "SELECT conname as index_name, consrc +			FROM pg_constraint, pg_class bc +			WHERE conrelid = bc.oid +				AND bc.relname = '" . $this->db->sql_escape($table_name) . "' +				AND NOT EXISTS ( +					SELECT * +						FROM pg_constraint as c, pg_inherits as i +						WHERE i.inhrelid = pg_constraint.conrelid +							AND c.conname = pg_constraint.conname +							AND c.consrc = pg_constraint.consrc +							AND c.conrelid = i.inhparent +				)"; +		$result = $this->db->sql_query($sql_checks); + +		// Add the constraints to the sql file. +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if (!is_null($row['consrc'])) +			{ +				$lines[] = '  CONSTRAINT ' . $row['index_name'] . ' CHECK ' . $row['consrc']; +			} +		} +		$this->db->sql_freeresult($result); + +		$sql_data .= implode(", \n", $lines); +		$sql_data .= "\n);\n"; + +		if (!empty($index_create)) +		{ +			$sql_data .= implode("\n", $index_create) . "\n\n"; +		} +		$this->flush($sql_data); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_data($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		// Grab all of the data from current table. +		$sql = "SELECT * +			FROM $table_name"; +		$result = $this->db->sql_query($sql); + +		$i_num_fields = pg_num_fields($result); +		$seq = ''; + +		for ($i = 0; $i < $i_num_fields; $i++) +		{ +			$ary_type[] = pg_field_type($result, $i); +			$ary_name[] = pg_field_name($result, $i); + +			$sql = "SELECT pg_get_expr(d.adbin, d.adrelid) as rowdefault +				FROM pg_attrdef d, pg_class c +				WHERE (c.relname = '{$table_name}') +					AND (c.oid = d.adrelid) +					AND d.adnum = " . strval($i + 1); +			$result2 = $this->db->sql_query($sql); +			if ($row = $this->db->sql_fetchrow($result2)) +			{ +				// Determine if we must reset the sequences +				if (strpos($row['rowdefault'], "nextval('") === 0) +				{ +					$seq .= "SELECT SETVAL('{$table_name}_seq',(select case when max({$ary_name[$i]})>0 then max({$ary_name[$i]})+1 else 1 end FROM {$table_name}));\n"; +				} +			} +		} + +		$this->flush("COPY $table_name (" . implode(', ', $ary_name) . ') FROM stdin;' . "\n"); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$schema_vals = array(); + +			// Build the SQL statement to recreate the data. +			for ($i = 0; $i < $i_num_fields; $i++) +			{ +				$str_val = $row[$ary_name[$i]]; + +				if (preg_match('#char|text|bool|bytea#i', $ary_type[$i])) +				{ +					$str_val = str_replace(array("\n", "\t", "\r", "\b", "\f", "\v"), array('\n', '\t', '\r', '\b', '\f', '\v'), addslashes($str_val)); +					$str_empty = ''; +				} +				else +				{ +					$str_empty = '\N'; +				} + +				if (empty($str_val) && $str_val !== '0') +				{ +					$str_val = $str_empty; +				} + +				$schema_vals[] = $str_val; +			} + +			// Take the ordered fields and their associated data and build it +			// into a valid sql statement to recreate that field in the data. +			$this->flush(implode("\t", $schema_vals) . "\n"); +		} +		$this->db->sql_freeresult($result); +		$this->flush("\\.\n"); + +		// Write out the sequence statements +		$this->flush($seq); +	} + +	/** +	* Writes closing line(s) to database backup +	* +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	public function write_end() +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$this->flush("COMMIT;\n"); +		parent::write_end(); +	} +} diff --git a/phpBB/phpbb/db/extractor/sqlite3_extractor.php b/phpBB/phpbb/db/extractor/sqlite3_extractor.php new file mode 100644 index 0000000000..ce8da6a652 --- /dev/null +++ b/phpBB/phpbb/db/extractor/sqlite3_extractor.php @@ -0,0 +1,151 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class sqlite3_extractor extends base_extractor +{ +	/** +	* {@inheritdoc} +	*/ +	public function write_start($table_prefix) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql_data = "--\n"; +		$sql_data .= "-- phpBB Backup Script\n"; +		$sql_data .= "-- Dump of tables for $table_prefix\n"; +		$sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; +		$sql_data .= "--\n"; +		$sql_data .= "BEGIN TRANSACTION;\n"; +		$this->flush($sql_data); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_table($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql_data = '-- Table: ' . $table_name . "\n"; +		$sql_data .= "DROP TABLE $table_name;\n"; + +		$sql = "SELECT sql +			FROM sqlite_master +			WHERE type = 'table' +				AND name = '" . $this->db->sql_escape($table_name) . "' +			ORDER BY name ASC;"; +		$result = $this->db->sql_query($sql); +		$row = $this->db->sql_fetchrow($result); +		$this->db->sql_freeresult($result); + +		// Create Table +		$sql_data .= $row['sql'] . ";\n"; + +		$result = $this->db->sql_query("PRAGMA index_list('" . $this->db->sql_escape($table_name) . "');"); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if (strpos($row['name'], 'autoindex') !== false) +			{ +				continue; +			} + +			$result2 = $this->db->sql_query("PRAGMA index_info('" . $this->db->sql_escape($row['name']) . "');"); + +			$fields = array(); +			while ($row2 = $this->db->sql_fetchrow($result2)) +			{ +				$fields[] = $row2['name']; +			} +			$this->db->sql_freeresult($result2); + +			$sql_data .= 'CREATE ' . ($row['unique'] ? 'UNIQUE ' : '') . 'INDEX ' . $row['name'] . ' ON ' . $table_name . ' (' . implode(', ', $fields) . ");\n"; +		} +		$this->db->sql_freeresult($result); + +		$this->flush($sql_data . "\n"); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_data($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$result = $this->db->sql_query("PRAGMA table_info('" . $this->db->sql_escape($table_name) . "');"); + +		$col_types = array(); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$col_types[$row['name']] = $row['type']; +		} +		$this->db->sql_freeresult($result); + +		$sql_insert = 'INSERT INTO ' . $table_name . ' (' . implode(', ', array_keys($col_types)) . ') VALUES ('; + +		$sql = "SELECT * +			FROM $table_name"; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			foreach ($row as $column_name => $column_data) +			{ +				if (is_null($column_data)) +				{ +					$row[$column_name] = 'NULL'; +				} +				else if ($column_data === '') +				{ +					$row[$column_name] = "''"; +				} +				else if (stripos($col_types[$column_name], 'text') !== false || stripos($col_types[$column_name], 'char') !== false || stripos($col_types[$column_name], 'blob') !== false) +				{ +					$row[$column_name] = sanitize_data_generic(str_replace("'", "''", $column_data)); +				} +			} +			$this->flush($sql_insert . implode(', ', $row) . ");\n"); +		} +	} + +	/** +	* Writes closing line(s) to database backup +	* +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	public function write_end() +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$this->flush("COMMIT;\n"); +		parent::write_end(); +	} +} diff --git a/phpBB/phpbb/db/extractor/sqlite_extractor.php b/phpBB/phpbb/db/extractor/sqlite_extractor.php new file mode 100644 index 0000000000..2734e23235 --- /dev/null +++ b/phpBB/phpbb/db/extractor/sqlite_extractor.php @@ -0,0 +1,149 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class sqlite_extractor extends base_extractor +{ +	/** +	* {@inheritdoc} +	*/ +	public function write_start($table_prefix) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql_data = "--\n"; +		$sql_data .= "-- phpBB Backup Script\n"; +		$sql_data .= "-- Dump of tables for $table_prefix\n"; +		$sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; +		$sql_data .= "--\n"; +		$sql_data .= "BEGIN TRANSACTION;\n"; +		$this->flush($sql_data); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_table($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$sql_data = '-- Table: ' . $table_name . "\n"; +		$sql_data .= "DROP TABLE $table_name;\n"; + +		$sql = "SELECT sql +			FROM sqlite_master +			WHERE type = 'table' +				AND name = '" . $this->db->sql_escape($table_name) . "' +			ORDER BY type DESC, name;"; +		$result = $this->db->sql_query($sql); +		$row = $this->db->sql_fetchrow($result); +		$this->db->sql_freeresult($result); + +		// Create Table +		$sql_data .= $row['sql'] . ";\n"; + +		$result = $this->db->sql_query("PRAGMA index_list('" . $this->db->sql_escape($table_name) . "');"); + +		$ar = array(); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$ar[] = $row; +		} +		$this->db->sql_freeresult($result); + +		foreach ($ar as $value) +		{ +			if (strpos($value['name'], 'autoindex') !== false) +			{ +				continue; +			} + +			$result = $this->db->sql_query("PRAGMA index_info('" . $this->db->sql_escape($value['name']) . "');"); + +			$fields = array(); +			while ($row = $this->db->sql_fetchrow($result)) +			{ +				$fields[] = $row['name']; +			} +			$this->db->sql_freeresult($result); + +			$sql_data .= 'CREATE ' . ($value['unique'] ? 'UNIQUE ' : '') . 'INDEX ' . $value['name'] . ' on ' . $table_name . ' (' . implode(', ', $fields) . ");\n"; +		} + +		$this->flush($sql_data . "\n"); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function write_data($table_name) +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$col_types = sqlite_fetch_column_types($this->db->get_db_connect_id(), $table_name); + +		$sql = "SELECT * +			FROM $table_name"; +		$result = sqlite_unbuffered_query($this->db->get_db_connect_id(), $sql); +		$rows = sqlite_fetch_all($result, SQLITE_ASSOC); +		$sql_insert = 'INSERT INTO ' . $table_name . ' (' . implode(', ', array_keys($col_types)) . ') VALUES ('; +		foreach ($rows as $row) +		{ +			foreach ($row as $column_name => $column_data) +			{ +				if (is_null($column_data)) +				{ +					$row[$column_name] = 'NULL'; +				} +				else if ($column_data == '') +				{ +					$row[$column_name] = "''"; +				} +				else if (strpos($col_types[$column_name], 'text') !== false || strpos($col_types[$column_name], 'char') !== false || strpos($col_types[$column_name], 'blob') !== false) +				{ +					$row[$column_name] = sanitize_data_generic(str_replace("'", "''", $column_data)); +				} +			} +			$this->flush($sql_insert . implode(', ', $row) . ");\n"); +		} +	} + +	/** +	* Writes closing line(s) to database backup +	* +	* @return null +	* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() +	*/ +	public function write_end() +	{ +		if (!$this->is_initialized) +		{ +			throw new extractor_not_initialized_exception(); +		} + +		$this->flush("COMMIT;\n"); +		parent::write_end(); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v30x/release_3_0_5_rc1.php b/phpBB/phpbb/db/migration/data/v30x/release_3_0_5_rc1.php index 003ccf8f18..084d00a13a 100644 --- a/phpBB/phpbb/db/migration/data/v30x/release_3_0_5_rc1.php +++ b/phpBB/phpbb/db/migration/data/v30x/release_3_0_5_rc1.php @@ -57,7 +57,9 @@ class release_3_0_5_rc1 extends container_aware_migration  	public function hash_old_passwords()  	{ +		/* @var $passwords_manager \phpbb\passwords\manager */  		$passwords_manager = $this->container->get('passwords.manager'); +  		$sql = 'SELECT user_id, user_password  				FROM ' . $this->table_prefix . 'users  				WHERE user_pass_convert = 1'; @@ -116,7 +118,7 @@ class release_3_0_5_rc1 extends container_aware_migration  				$result = $this->db->sql_query($sql);  				// Skip first row, this is our original auth option we want to preserve -				$row = $this->db->sql_fetchrow($result); +				$this->db->sql_fetchrow($result);  				while ($row = $this->db->sql_fetchrow($result))  				{ diff --git a/phpBB/phpbb/db/migration/data/v30x/release_3_0_9_rc1.php b/phpBB/phpbb/db/migration/data/v30x/release_3_0_9_rc1.php index 06e46d522f..5f928df47c 100644 --- a/phpBB/phpbb/db/migration/data/v30x/release_3_0_9_rc1.php +++ b/phpBB/phpbb/db/migration/data/v30x/release_3_0_9_rc1.php @@ -34,7 +34,7 @@ class release_3_0_9_rc1 extends \phpbb\db\migration\migration  						// this column was removed from the database updater  						// after 3.0.9-RC3 was released. It might still exist  						// in 3.0.9-RCX installations and has to be dropped as -						// soon as the db_tools class is capable of properly +						// soon as the \phpbb\db\tools\tools class is capable of properly  						// removing a primary key.  						// 'attempt_id'			=> array('UINT', NULL, 'auto_increment'),  						'attempt_ip'			=> array('VCHAR:40', ''), diff --git a/phpBB/phpbb/db/migration/data/v310/acp_prune_users_module.php b/phpBB/phpbb/db/migration/data/v310/acp_prune_users_module.php index 0ca4f2f19c..725c57ca86 100644 --- a/phpBB/phpbb/db/migration/data/v310/acp_prune_users_module.php +++ b/phpBB/phpbb/db/migration/data/v310/acp_prune_users_module.php @@ -13,7 +13,7 @@  namespace phpbb\db\migration\data\v310; -class acp_prune_users_module extends \phpbb\db\migration\migration +class acp_prune_users_module extends \phpbb\db\migration\container_aware_migration  {  	public function effectively_installed()  	{ @@ -70,12 +70,7 @@ class acp_prune_users_module extends \phpbb\db\migration\migration  		$acp_cat_users_id = (int) $this->db->sql_fetchfield('module_id');  		$this->db->sql_freeresult($result); -		if (!class_exists('\acp_modules')) -		{ -			include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); -		} -		$module_manager = new \acp_modules(); -		$module_manager->module_class = 'acp'; -		$module_manager->move_module($acp_prune_users_id, $acp_cat_users_id); +		$module_manager = $this->container->get('module.manager'); +		$module_manager->move_module($acp_prune_users_id, $acp_cat_users_id, 'acp');  	}  } diff --git a/phpBB/phpbb/db/migration/data/v310/dev.php b/phpBB/phpbb/db/migration/data/v310/dev.php index f037191c2a..250258eea7 100644 --- a/phpBB/phpbb/db/migration/data/v310/dev.php +++ b/phpBB/phpbb/db/migration/data/v310/dev.php @@ -13,7 +13,7 @@  namespace phpbb\db\migration\data\v310; -class dev extends \phpbb\db\migration\migration +class dev extends \phpbb\db\migration\container_aware_migration  {  	public function effectively_installed()  	{ @@ -204,18 +204,13 @@ class dev extends \phpbb\db\migration\migration  		$language_management_module_id = $this->db->sql_fetchfield('module_id');  		$this->db->sql_freeresult($result); -		if (!class_exists('acp_modules')) -		{ -			include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); -		}  		// acp_modules calls adm_back_link, which is undefined at this point  		if (!function_exists('adm_back_link'))  		{  			include($this->phpbb_root_path . 'includes/functions_acp.' . $this->php_ext);  		} -		$module_manager = new \acp_modules(); -		$module_manager->module_class = 'acp'; -		$module_manager->move_module($language_module_id, $language_management_module_id); +		$module_manager = $this->container->get('module.manager'); +		$module_manager->move_module($language_module_id, $language_management_module_id, 'acp');  	}  	public function update_ucp_pm_basename() diff --git a/phpBB/phpbb/db/migration/data/v31x/update_custom_bbcodes_with_idn.php b/phpBB/phpbb/db/migration/data/v31x/update_custom_bbcodes_with_idn.php index 854ed1f568..14b7b7b0f6 100644 --- a/phpBB/phpbb/db/migration/data/v31x/update_custom_bbcodes_with_idn.php +++ b/phpBB/phpbb/db/migration/data/v31x/update_custom_bbcodes_with_idn.php @@ -45,7 +45,6 @@ class update_custom_bbcodes_with_idn extends \phpbb\db\migration\migration  		$sql_ary = array();  		while ($row = $this->db->sql_fetchrow($result))  		{ -			$data = array();  			if (preg_match('/(URL|LOCAL_URL|RELATIVE_URL)/', $row['bbcode_match']))  			{  				$data = $bbcodes->build_regexp($row['bbcode_match'], $row['bbcode_tpl']); diff --git a/phpBB/phpbb/db/migration/data/v320/allowed_schemes_links.php b/phpBB/phpbb/db/migration/data/v320/allowed_schemes_links.php new file mode 100644 index 0000000000..726822bc71 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/allowed_schemes_links.php @@ -0,0 +1,31 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class allowed_schemes_links extends \phpbb\db\migration\migration +{ +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v320\dev', +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.add', array('allowed_schemes_links', 'http,https,ftp')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/announce_global_permission.php b/phpBB/phpbb/db/migration/data/v320/announce_global_permission.php new file mode 100644 index 0000000000..7afecb884b --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/announce_global_permission.php @@ -0,0 +1,43 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class announce_global_permission extends \phpbb\db\migration\migration +{ +	public function effectively_installed() +	{ +		$sql = 'SELECT auth_option_id +			FROM ' . ACL_OPTIONS_TABLE . " +			WHERE auth_option = 'f_announce_global'"; +		$result = $this->db->sql_query($sql); +		$auth_option_id = $this->db->sql_fetchfield('auth_option_id'); +		$this->db->sql_freeresult($result); + +		return $auth_option_id !== false; +	} + +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v320\dev', +		); +	} + +	public function update_data() +	{ +		return array( +			array('permission.add', array('f_announce_global', false, 'f_announce')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/default_data_type_ids.php b/phpBB/phpbb/db/migration/data/v320/default_data_type_ids.php new file mode 100644 index 0000000000..65e5b3fa73 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/default_data_type_ids.php @@ -0,0 +1,361 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class default_data_type_ids extends \phpbb\db\migration\migration +{ +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v320\v320a2', +			'\phpbb\db\migration\data\v320\oauth_states', +		); +	} + +	public function update_schema() +	{ +		return array( +			'change_columns'	=> array( +				$this->table_prefix . 'acl_users'			=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'attachments'			=> array( +					'attach_id'		=> array('ULINT', null, 'auto_increment'), +					'post_msg_id'	=> array('ULINT', 0), +					'poster_id'		=> array('ULINT', 0), +					'topic_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'banlist'				=> array( +					'ban_id'		=> array('ULINT', null, 'auto_increment'), +					'ban_userid'	=> array('ULINT', 0), +				), +				$this->table_prefix . 'bookmarks'			=> array( +					'topic_id'		=> array('ULINT', 0), +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'bots'				=> array( +					'bot_id'		=> array('ULINT', null, 'auto_increment'), +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'drafts'				=> array( +					'draft_id'		=> array('ULINT', null, 'auto_increment'), +					'user_id'		=> array('ULINT', 0), +					'topic_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'forums'				=> array( +					'forum_last_post_id'	=> array('ULINT', 0), +					'forum_last_poster_id'	=> array('ULINT', 0), +				), +				$this->table_prefix . 'forums_access'		=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'forums_track'		=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'forums_watch'		=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'log'					=> array( +					'log_id'		=> array('ULINT', null, 'auto_increment'), +					'post_id'		=> array('ULINT', 0), +					'reportee_id'	=> array('ULINT', 0), +					'user_id'		=> array('ULINT', 0), +					'topic_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'login_attempts'		=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'moderator_cache'		=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'notifications'		=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'oauth_accounts'		=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'oauth_states'		=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'oauth_tokens'		=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'poll_options'		=> array( +					'topic_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'poll_votes'			=> array( +					'topic_id'		=> array('ULINT', 0), +					'vote_user_id'	=> array('ULINT', 0), +				), +				$this->table_prefix . 'posts'				=> array( +					'post_id'			=> array('ULINT', null, 'auto_increment'), +					'poster_id'			=> array('ULINT', 0), +					'post_delete_user'	=> array('ULINT', 0), +					'post_edit_user'	=> array('ULINT', 0), +					'topic_id'			=> array('ULINT', 0), +				), +				$this->table_prefix . 'privmsgs'			=> array( +					'author_id'			=> array('ULINT', 0), +					'message_edit_user'	=> array('ULINT', 0), +					'msg_id'			=> array('ULINT', null, 'auto_increment'), +				), +				$this->table_prefix . 'privmsgs_folder'		=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'privmsgs_rules'		=> array( +					'rule_user_id'	=> array('ULINT', 0), +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'privmsgs_to'			=> array( +					'author_id'		=> array('ULINT', 0), +					'msg_id'		=> array('ULINT', 0), +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'profile_fields_data'	=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'reports'				=> array( +					'report_id'		=> array('ULINT', 0), +					'pm_id'			=> array('ULINT', 0), +					'post_id'		=> array('ULINT', 0), +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'search_wordlist'		=> array( +					'word_id'		=> array('ULINT', null, 'auto_increment'), +				), +				$this->table_prefix . 'search_wordmatch'	=> array( +					'post_id'		=> array('ULINT', 0), +					'word_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'sessions'			=> array( +					'session_user_id'	=> array('ULINT', 0), +				), +				$this->table_prefix . 'sessions_keys'		=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'topics'				=> array( +					'topic_id'				=> array('ULINT', null, 'auto_increment'), +					'topic_poster'			=> array('ULINT', 0), +					'topic_first_post_id'	=> array('ULINT', 0), +					'topic_last_post_id'	=> array('ULINT', 0), +					'topic_last_poster_id'	=> array('ULINT', 0), +					'topic_moved_id'		=> array('ULINT', 0), +					'topic_delete_user'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'topics_track'		=> array( +					'user_id'		=> array('ULINT', 0), +					'topic_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'topics_posted'		=> array( +					'user_id'		=> array('ULINT', 0), +					'topic_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'topics_watch'		=> array( +					'user_id'		=> array('ULINT', 0), +					'topic_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'user_notifications'	=> array( +					'item_id'		=> array('ULINT', 0), +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'user_group'			=> array( +					'user_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'users'				=> array( +					'user_id'		=> array('ULINT', null, 'auto_increment'), +				), +				$this->table_prefix . 'warnings'			=> array( +					'log_id'		=> array('ULINT', 0), +					'user_id'		=> array('ULINT', 0), +					'post_id'		=> array('ULINT', 0), +				), +				$this->table_prefix . 'words'				=> array( +					'word_id'		=> array('ULINT', null, 'auto_increment'), +				), +				$this->table_prefix . 'zebra'			=> array( +					'user_id'		=> array('ULINT', 0), +					'zebra_id'		=> array('ULINT', 0), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'change_columns'	=> array( +				$this->table_prefix . 'acl_users'			=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'attachments'			=> array( +					'attach_id'		=> array('UINT', null, 'auto_increment'), +					'post_msg_id'	=> array('UINT', 0), +					'poster_id'		=> array('UINT', 0), +					'topic_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'banlist'				=> array( +					'ban_id'		=> array('UINT', null, 'auto_increment'), +					'ban_userid'	=> array('UINT', 0), +				), +				$this->table_prefix . 'bookmarks'			=> array( +					'topic_id'		=> array('UINT', 0), +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'bots'				=> array( +					'bot_id'		=> array('UINT', null, 'auto_increment'), +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'drafts'				=> array( +					'draft_id'		=> array('UINT', null, 'auto_increment'), +					'user_id'		=> array('UINT', 0), +					'topic_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'forums'				=> array( +					'forum_last_post_id'	=> array('UINT', 0), +					'forum_last_poster_id'	=> array('UINT', 0), +				), +				$this->table_prefix . 'forums_access'		=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'forums_track'		=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'forums_watch'		=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'log'					=> array( +					'log_id'		=> array('UINT', null, 'auto_increment'), +					'post_id'		=> array('UINT', 0), +					'reportee_id'	=> array('UINT', 0), +					'user_id'		=> array('UINT', 0), +					'topic_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'login_attempts'		=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'moderator_cache'		=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'notifications'		=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'oauth_accounts'		=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'oauth_states'		=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'oauth_tokens'		=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'poll_options'		=> array( +					'topic_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'poll_votes'			=> array( +					'topic_id'		=> array('UINT', 0), +					'vote_user_id'	=> array('UINT', 0), +				), +				$this->table_prefix . 'posts'				=> array( +					'post_id'			=> array('UINT', null, 'auto_increment'), +					'poster_id'			=> array('UINT', 0), +					'post_delete_user'	=> array('UINT', 0), +					'post_edit_user'	=> array('UINT', 0), +					'topic_id'			=> array('UINT', 0), +				), +				$this->table_prefix . 'privmsgs'			=> array( +					'author_id'			=> array('UINT', 0), +					'message_edit_user'	=> array('UINT', 0), +					'msg_id'			=> array('UINT', null, 'auto_increment'), +				), +				$this->table_prefix . 'privmsgs_folder'		=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'privmsgs_rules'		=> array( +					'rule_user_id'	=> array('UINT', 0), +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'privmsgs_to'			=> array( +					'author_id'		=> array('UINT', 0), +					'msg_id'		=> array('UINT', 0), +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'profile_fields_data'	=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'reports'				=> array( +					'report_id'		=> array('UINT', 0), +					'pm_id'			=> array('UINT', 0), +					'post_id'		=> array('UINT', 0), +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'search_wordlist'		=> array( +					'word_id'		=> array('UINT', null, 'auto_increment'), +				), +				$this->table_prefix . 'search_wordmatch'	=> array( +					'post_id'		=> array('UINT', 0), +					'word_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'sessions'			=> array( +					'session_user_id'	=> array('UINT', 0), +				), +				$this->table_prefix . 'sessions_keys'		=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'topics'				=> array( +					'topic_id'				=> array('UINT', null, 'auto_increment'), +					'topic_poster'			=> array('UINT', 0), +					'topic_first_post_id'	=> array('UINT', 0), +					'topic_last_post_id'	=> array('UINT', 0), +					'topic_last_poster_id'	=> array('UINT', 0), +					'topic_moved_id'		=> array('UINT', 0), +					'topic_delete_user'		=> array('UINT', 0), +				), +				$this->table_prefix . 'topics_track'		=> array( +					'user_id'		=> array('UINT', 0), +					'topic_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'topics_posted'		=> array( +					'user_id'		=> array('UINT', 0), +					'topic_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'topics_watch'		=> array( +					'user_id'		=> array('UINT', 0), +					'topic_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'user_notifications'	=> array( +					'item_id'		=> array('UINT', 0), +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'user_group'			=> array( +					'user_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'users'				=> array( +					'user_id'		=> array('UINT', null, 'auto_increment'), +				), +				$this->table_prefix . 'warnings'			=> array( +					'log_id'		=> array('UINT', 0), +					'user_id'		=> array('UINT', 0), +					'post_id'		=> array('UINT', 0), +				), +				$this->table_prefix . 'words'				=> array( +					'word_id'		=> array('UINT', null, 'auto_increment'), +				), +				$this->table_prefix . 'zebra'			=> array( +					'user_id'		=> array('UINT', 0), +					'zebra_id'		=> array('UINT', 0), +				), +			), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/dev.php b/phpBB/phpbb/db/migration/data/v320/dev.php new file mode 100644 index 0000000000..ad2da3c1f4 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/dev.php @@ -0,0 +1,36 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class dev extends \phpbb\db\migration\container_aware_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.2.0-dev', '>='); +	} + +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v31x\v316', +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.2.0-dev')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/font_awesome_update.php b/phpBB/phpbb/db/migration/data/v320/font_awesome_update.php new file mode 100644 index 0000000000..817b638037 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/font_awesome_update.php @@ -0,0 +1,36 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class font_awesome_update extends \phpbb\db\migration\migration +{ +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v320\dev', +		); +	} + +	public function effectively_installed() +	{ +		return isset($this->config['load_font_awesome_url']); +	} + +	public function update_data() +	{ +		return array( +			array('config.add', array('load_font_awesome_url', 'https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/icons_alt.php b/phpBB/phpbb/db/migration/data/v320/icons_alt.php new file mode 100644 index 0000000000..80132e579e --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/icons_alt.php @@ -0,0 +1,46 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class icons_alt extends \phpbb\db\migration\migration +{ +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v320\dev', +		); +	} + +	public function update_schema() +	{ +		return array( +			'add_columns'        => array( +				$this->table_prefix . 'icons'        => array( +					'icons_alt'    => array('VCHAR', '', 'after' => 'icons_height'), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'drop_columns'        => array( +				$this->table_prefix . 'icons'        => array( +					'icons_alt', +				), +			), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/log_post_id.php b/phpBB/phpbb/db/migration/data/v320/log_post_id.php new file mode 100644 index 0000000000..ead53c8138 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/log_post_id.php @@ -0,0 +1,46 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class log_post_id extends \phpbb\db\migration\migration +{ +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v320\dev', +		); +	} + +	public function update_schema() +	{ +		return array( +			'add_columns'        => array( +				$this->table_prefix . 'log'        => array( +					'post_id'    => array('UINT', 0, 'after' => 'topic_id'), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'drop_columns'        => array( +				$this->table_prefix . 'log'        => array( +					'post_id', +				), +			), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/notifications_board.php b/phpBB/phpbb/db/migration/data/v320/notifications_board.php new file mode 100644 index 0000000000..ac1b3a0f2c --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/notifications_board.php @@ -0,0 +1,75 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class notifications_board extends \phpbb\db\migration\migration +{ +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v320\dev', +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.add', array('allow_board_notifications', 1)), +			array('custom', array(array($this, 'update_user_subscriptions'))), +			array('custom', array(array($this, 'update_module'))), +		); +	} + +	public function update_module() +	{ +		$sql = 'UPDATE ' . MODULES_TABLE . " +			SET module_auth = 'cfg_allow_board_notifications' +			WHERE module_basename = 'ucp_notifications' +				AND module_mode = 'notification_list'"; +		$this->sql_query($sql); +	} + +	public function update_user_subscriptions() +	{ +		$sql = 'UPDATE ' . USER_NOTIFICATIONS_TABLE . " +			SET method = 'notification.method.board' +			WHERE method = ''"; +		$this->sql_query($sql); +	} + +	public function revert_data() +	{ +		return array( +			array('custom', array(array($this, 'revert_user_subscriptions'))), +			array('custom', array(array($this, 'revert_module'))), +		); +	} + +	public function revert_user_subscriptions() +	{ +		$sql = 'UPDATE ' . USER_NOTIFICATIONS_TABLE . " +			SET method = '' +			WHERE method = 'notification.method.board'"; +		$this->sql_query($sql); +	} + +	public function revert_module() +	{ +		$sql = 'UPDATE ' . MODULES_TABLE . " +			SET auth = '' +			WHERE module_basename = 'ucp_notifications' +				AND module_mode = 'notification_list'"; +		$this->sql_query($sql); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/oauth_states.php b/phpBB/phpbb/db/migration/data/v320/oauth_states.php new file mode 100644 index 0000000000..22ab2dabb3 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/oauth_states.php @@ -0,0 +1,56 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class oauth_states extends \phpbb\db\migration\migration +{ +	static public function depends_on() +	{ +		return array('\phpbb\db\migration\data\v310\auth_provider_oauth'); +	} + +	public function effectively_installed() +	{ +		return $this->db_tools->sql_table_exists($this->table_prefix . 'oauth_states'); +	} + +	public function update_schema() +	{ +		return array( +			'add_tables'	=> array( +				$this->table_prefix . 'oauth_states'	=> array( +					'COLUMNS' => array( +						'user_id'			=> array('UINT', 0), +						'session_id'		=> array('CHAR:32', ''), +						'provider'			=> array('VCHAR', ''), +						'oauth_state'		=> array('VCHAR', ''), +					), +					'KEYS' => array( +						'user_id'			=> array('INDEX', 'user_id'), +						'provider'			=> array('INDEX', 'provider'), +					), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'drop_tables'	=> array( +				$this->table_prefix . 'oauth_states', +			), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/remote_upload_validation.php b/phpBB/phpbb/db/migration/data/v320/remote_upload_validation.php new file mode 100644 index 0000000000..d61f6b96fd --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/remote_upload_validation.php @@ -0,0 +1,31 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class remote_upload_validation extends \phpbb\db\migration\migration +{ +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v320\v320a2', +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.add', array('remote_upload_verify', '0')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/remove_outdated_media.php b/phpBB/phpbb/db/migration/data/v320/remove_outdated_media.php new file mode 100644 index 0000000000..c14d31f1c0 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/remove_outdated_media.php @@ -0,0 +1,90 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class remove_outdated_media extends \phpbb\db\migration\migration +{ +	protected $cat_id = array( +			ATTACHMENT_CATEGORY_WM, +			ATTACHMENT_CATEGORY_RM, +			ATTACHMENT_CATEGORY_QUICKTIME, +		); + +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v320\dev', +		); +	} + +	public function update_data() +	{ +		return array( +			array('custom', array(array($this, 'change_extension_group'))), +		); +	} + +	public function change_extension_group() +	{ +		// select group ids of outdated media +		$sql = 'SELECT group_id +			FROM ' . EXTENSION_GROUPS_TABLE . ' +			WHERE ' . $this->db->sql_in_set('cat_id', $this->cat_id); +		$result = $this->db->sql_query($sql); + +		$group_ids = array(); +		while ($group_id = (int) $this->db->sql_fetchfield('group_id')) +		{ +			$group_ids[] = $group_id; +		} +		$this->db->sql_freeresult($result); + +		// nothing to do, admin has removed all the outdated media extension groups +		if (empty($group_ids)) +		{ +			return true; +		} + +		// get the group id of downloadable files +		$sql = 'SELECT group_id +			FROM ' . EXTENSION_GROUPS_TABLE . " +			WHERE group_name = 'DOWNLOADABLE_FILES'"; +		$result = $this->db->sql_query($sql); +		$download_id = (int) $this->db->sql_fetchfield('group_id'); +		$this->db->sql_freeresult($result); + +		if (empty($download_id)) +		{ +			$sql = 'UPDATE ' . EXTENSIONS_TABLE . ' +				SET group_id = 0 +				WHERE ' . $this->db->sql_in_set('group_id', $group_ids); +		} +		else +		{ +			// move outdated media extensions to downloadable files +			$sql = 'UPDATE ' . EXTENSIONS_TABLE . " +				SET group_id = $download_id" . ' +				WHERE ' . $this->db->sql_in_set('group_id', $group_ids); +		} + +		$result = $this->db->sql_query($sql); +		$this->db->sql_freeresult($result); + +		// delete the now empty, outdated media extension groups +		$sql = 'DELETE FROM ' . EXTENSION_GROUPS_TABLE . ' +			WHERE ' . $this->db->sql_in_set('group_id', $group_ids); +		$result = $this->db->sql_query($sql); +		$this->db->sql_freeresult($result); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/remove_profilefield_wlm.php b/phpBB/phpbb/db/migration/data/v320/remove_profilefield_wlm.php new file mode 100644 index 0000000000..1cb9070bf9 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/remove_profilefield_wlm.php @@ -0,0 +1,152 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class remove_profilefield_wlm extends \phpbb\db\migration\migration +{ +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v320\dev', +		); +	} + +	public function update_schema() +	{ +		return array( +			'drop_columns'	=> array( +				$this->table_prefix . 'profile_fields_data'			=> array( +					'pf_phpbb_wlm', +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'add_columns'	=> array( +				$this->table_prefix . 'profile_fields_data'			=> array( +					'pf_phpbb_wlm'		=> array('VCHAR', ''), +				), +			), +		); +	} + +	public function update_data() +	{ +		return array( +			array('custom', array(array($this, 'delete_custom_profile_field_data'))), +		); +	} + +	public function revert_data() +	{ +		return array( +			array('custom', array(array($this, 'create_custom_field'))), +		); +	} + +	public function delete_custom_profile_field_data() +	{ +		$field_id = $this->get_custom_profile_field_id(); + +		$sql = 'DELETE FROM ' . PROFILE_FIELDS_TABLE . ' +			WHERE field_id = ' . (int) $field_id; +		$this->db->sql_query($sql); + +		$sql = 'DELETE FROM ' . PROFILE_LANG_TABLE . ' +			WHERE field_id = ' . (int) $field_id; +		$this->db->sql_query($sql); + +		$sql = 'DELETE FROM ' . PROFILE_FIELDS_LANG_TABLE . ' +			WHERE field_id = ' . (int) $field_id; +		$this->db->sql_query($sql); +	} + +	/** +	 * Get custom profile field id +	 * @return	int	custom profile filed id +	 */ +	public function get_custom_profile_field_id() +	{ +		$sql = 'SELECT field_id +			FROM ' . PROFILE_FIELDS_TABLE . " +			WHERE field_name = 'phpbb_wlm'"; +		$result = $this->db->sql_query($sql); +		$field_id = (int) $this->db->sql_fetchfield('field_id'); +		$this->db->sql_freeresult($result); + +		return $field_id; +	} + +	public function create_custom_field() +	{ +		$sql = 'SELECT MAX(field_order) as max_field_order +			FROM ' . PROFILE_FIELDS_TABLE; +		$result = $this->db->sql_query($sql); +		$max_field_order = (int) $this->db->sql_fetchfield('max_field_order'); +		$this->db->sql_freeresult($result); + +		$sql_ary = array( +			'field_name'			=> 'phpbb_wlm', +			'field_type'			=> 'profilefields.type.string', +			'field_ident'			=> 'phpbb_wlm', +			'field_length'			=> '40', +			'field_minlen'			=> '5', +			'field_maxlen'			=> '255', +			'field_novalue'			=> '', +			'field_default_value'	=> '', +			'field_validation'		=> '.*', +			'field_required'		=> 0, +			'field_show_novalue'	=> 0, +			'field_show_on_reg'		=> 0, +			'field_show_on_pm'		=> 1, +			'field_show_on_vt'		=> 1, +			'field_show_on_ml'		=> 0, +			'field_show_profile'	=> 1, +			'field_hide'			=> 0, +			'field_no_view'			=> 0, +			'field_active'			=> 1, +			'field_is_contact'		=> 1, +			'field_contact_desc'	=> '', +			'field_contact_url'		=> '', +			'field_order'			=> $max_field_order + 1, +		); + +		$sql = 'INSERT INTO ' . PROFILE_FIELDS_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); +		$this->db->sql_query($sql); +		$field_id = (int) $this->db->sql_nextid(); + +		$insert_buffer = new \phpbb\db\sql_insert_buffer($this->db, PROFILE_LANG_TABLE); + +		$sql = 'SELECT lang_id +			FROM ' . LANG_TABLE; +		$result = $this->db->sql_query($sql); +		$lang_name = 'WLM'; +		while ($lang_id = (int) $this->db->sql_fetchfield('lang_id')) +		{ +			$insert_buffer->insert(array( +				'field_id'				=> (int) $field_id, +				'lang_id'				=> (int) $lang_id, +				'lang_name'				=> $lang_name, +				'lang_explain'			=> '', +				'lang_default_value'	=> '', +			)); +		} +		$this->db->sql_freeresult($result); + +		$insert_buffer->flush(); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/report_id_auto_increment.php b/phpBB/phpbb/db/migration/data/v320/report_id_auto_increment.php new file mode 100644 index 0000000000..6e81baefb9 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/report_id_auto_increment.php @@ -0,0 +1,46 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class report_id_auto_increment extends \phpbb\db\migration\migration +{ +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v320\default_data_type_ids', +		); +	} + +	public function update_schema() +	{ +		return array( +			'change_columns'	=> array( +				$this->table_prefix . 'reports'				=> array( +					'report_id'		=> array('ULINT', null, 'auto_increment'), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'change_columns'	=> array( +				$this->table_prefix . 'reports'				=> array( +					'report_id'		=> array('ULINT', 0), +				), +			), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/text_reparser.php b/phpBB/phpbb/db/migration/data/v320/text_reparser.php new file mode 100644 index 0000000000..ea614feb40 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/text_reparser.php @@ -0,0 +1,104 @@ +<?php +/** + * + * This file is part of the phpBB Forum Software package. + * + * @copyright (c) phpBB Limited <https://www.phpbb.com> + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v320; + +class text_reparser extends \phpbb\db\migration\container_aware_migration +{ +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v310\contact_admin_form', +			'\phpbb\db\migration\data\v320\allowed_schemes_links', +		); +	} + +	public function effectively_installed() +	{ +		return isset($this->config['reparse_lock']); +	} + +	public function update_data() +	{ +		return array( +			array('config.add', array('reparse_lock', 0, true)), +			array('config.add', array('text_reparser.pm_text_cron_interval', 10)), +			array('config.add', array('text_reparser.pm_text_last_cron', 0)), +			array('config.add', array('text_reparser.poll_option_cron_interval', 10)), +			array('config.add', array('text_reparser.poll_option_last_cron', 0)), +			array('config.add', array('text_reparser.poll_title_cron_interval', 10)), +			array('config.add', array('text_reparser.poll_title_last_cron', 0)), +			array('config.add', array('text_reparser.post_text_cron_interval', 10)), +			array('config.add', array('text_reparser.post_text_last_cron', 0)), +			array('config.add', array('text_reparser.user_signature_cron_interval', 10)), +			array('config.add', array('text_reparser.user_signature_last_cron', 0)), +			array('custom', array(array($this, 'reparse'))), +		); +	} + +	public function reparse($resume_data) +	{ +		// Somtimes a cron job is too much +		$limit = 100; +		$fast_reparsers = array( +			'text_reparser.contact_admin_info', +			'text_reparser.forum_description', +			'text_reparser.forum_rules', +			'text_reparser.group_description', +		); + +		if (!is_array($resume_data)) +		{ +			$resume_data = array( +				'reparser'	=> 0, +				'current'	=> $this->container->get($fast_reparsers[0])->get_max_id(), +			); +		} + +		$fast_reparsers_size = sizeof($fast_reparsers); +		$processed_records = 0; +		while ($processed_records < $limit && $resume_data['reparser'] < $fast_reparsers_size) +		{ +			$reparser = $this->container->get($fast_reparsers[$resume_data['reparser']]); + +			// New reparser +			if ($resume_data['current'] === 0) +			{ +				$resume_data['current'] = $reparser->get_max_id(); +			} + +			$start = max(1, $resume_data['current'] + 1 - ($limit - $processed_records)); +			$end = max(1, $resume_data['current']); +			$reparser->reparse_range($start, $end); + +			$processed_records += $end - $start + 1; +			$resume_data['current'] = $start - 1; + +			if ($start === 1) +			{ +				// Prevent CLI command from running these reparsers again +				$reparser_manager = $this->container->get('text_reparser.manager'); +				$reparser_manager->update_resume_data($fast_reparsers[$resume_data['reparser']], 1, 0, $limit); + +				$resume_data['reparser']++; +			} +		} + +		if ($resume_data['reparser'] === $fast_reparsers_size) +		{ +			return true; +		} + +		return $resume_data; +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/v320a1.php b/phpBB/phpbb/db/migration/data/v320/v320a1.php new file mode 100644 index 0000000000..d7ecb36f90 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/v320a1.php @@ -0,0 +1,44 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class v320a1 extends \phpbb\db\migration\container_aware_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.2.0-a1', '>='); +	} + +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v320\dev', +			'\phpbb\db\migration\data\v320\allowed_schemes_links', +			'\phpbb\db\migration\data\v320\announce_global_permission', +			'\phpbb\db\migration\data\v320\remove_profilefield_wlm', +			'\phpbb\db\migration\data\v320\font_awesome_update', +			'\phpbb\db\migration\data\v320\icons_alt', +			'\phpbb\db\migration\data\v320\log_post_id', +			'\phpbb\db\migration\data\v320\remove_outdated_media', +			'\phpbb\db\migration\data\v320\notifications_board', +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.2.0-dev')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/v320a2.php b/phpBB/phpbb/db/migration/data/v320/v320a2.php new file mode 100644 index 0000000000..ae53a73210 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/v320a2.php @@ -0,0 +1,38 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class v320a2 extends \phpbb\db\migration\container_aware_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.2.0-a2', '>='); +	} + +	static public function depends_on() +	{ +		return 		array( +			'\phpbb\db\migration\data\v31x\v317rc1', +			'\phpbb\db\migration\data\v320\text_reparser', +			'\phpbb\db\migration\data\v320\v320a1', +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.2.0-a2')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/v320b1.php b/phpBB/phpbb/db/migration/data/v320/v320b1.php new file mode 100644 index 0000000000..5c3a3797cd --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/v320b1.php @@ -0,0 +1,39 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class v320b1 extends \phpbb\db\migration\container_aware_migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.2.0-b1', '>='); +	} + +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v31x\v317pl1', +			'\phpbb\db\migration\data\v320\v320a2', +			'\phpbb\db\migration\data\v31x\increase_size_of_dateformat', +			'\phpbb\db\migration\data\v320\default_data_type_ids', +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.2.0-b1')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/v320b2.php b/phpBB/phpbb/db/migration/data/v320/v320b2.php new file mode 100644 index 0000000000..007f7588e6 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/v320b2.php @@ -0,0 +1,40 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +use phpbb\db\migration\migration; + +class v320b2 extends migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.2.0-b2', '>='); +	} + +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v31x\v318', +			'\phpbb\db\migration\data\v320\v320b1', +			'\phpbb\db\migration\data\v320\remote_upload_validation', +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.2.0-b2')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v320/v320rc1.php b/phpBB/phpbb/db/migration/data/v320/v320rc1.php new file mode 100644 index 0000000000..a04a2abb19 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v320/v320rc1.php @@ -0,0 +1,40 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +use phpbb\db\migration\migration; + +class v320rc1 extends migration +{ +	public function effectively_installed() +	{ +		return version_compare($this->config['version'], '3.2.0-RC1', '>='); +	} + +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v31x\v319', +			'\phpbb\db\migration\data\v320\report_id_auto_increment', +			'\phpbb\db\migration\data\v320\v320b2', +		); +	} + +	public function update_data() +	{ +		return array( +			array('config.update', array('version', '3.2.0-RC1')), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/migration.php b/phpBB/phpbb/db/migration/migration.php index 5f120333e1..4e218344f4 100644 --- a/phpBB/phpbb/db/migration/migration.php +++ b/phpBB/phpbb/db/migration/migration.php @@ -20,7 +20,7 @@ namespace phpbb\db\migration;  * in a subclass. This class provides various utility methods to simplify editing  * a phpBB.  */ -abstract class migration +abstract class migration implements migration_interface  {  	/** @var \phpbb\config\config */  	protected $config; @@ -28,7 +28,7 @@ abstract class migration  	/** @var \phpbb\db\driver\driver_interface */  	protected $db; -	/** @var \phpbb\db\tools */ +	/** @var \phpbb\db\tools\tools_interface */  	protected $db_tools;  	/** @var string */ @@ -51,12 +51,12 @@ abstract class migration  	*  	* @param \phpbb\config\config $config  	* @param \phpbb\db\driver\driver_interface $db -	* @param \phpbb\db\tools $db_tools +	* @param \phpbb\db\tools\tools_interface $db_tools  	* @param string $phpbb_root_path  	* @param string $php_ext  	* @param string $table_prefix  	*/ -	public function __construct(\phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\db\tools $db_tools, $phpbb_root_path, $php_ext, $table_prefix) +	public function __construct(\phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\db\tools\tools_interface $db_tools, $phpbb_root_path, $php_ext, $table_prefix)  	{  		$this->config = $config;  		$this->db = $db; @@ -70,9 +70,7 @@ abstract class migration  	}  	/** -	* Defines other migrations to be applied first -	* -	* @return array An array of migration class names +	* {@inheritdoc}  	*/  	static public function depends_on()  	{ @@ -80,14 +78,7 @@ abstract class migration  	}  	/** -	* Allows you to check if the migration is effectively installed (entirely optional) -	* -	* This is checked when a migration is installed. If true is returned, the migration will be set as -	* installed without performing the database changes. -	* This function is intended to help moving to migrations from a previous database updater, where some -	* migrations may have been installed already even though they are not yet listed in the migrations table. -	* -	* @return bool True if this migration is installed, False if this migration is not installed (checked on install) +	* {@inheritdoc}  	*/  	public function effectively_installed()  	{ @@ -95,9 +86,7 @@ abstract class migration  	}  	/** -	* Updates the database schema by providing a set of change instructions -	* -	* @return array Array of schema changes (compatible with db_tools->perform_schema_changes()) +	* {@inheritdoc}  	*/  	public function update_schema()  	{ @@ -105,9 +94,7 @@ abstract class migration  	}  	/** -	* Reverts the database schema by providing a set of change instructions -	* -	* @return array Array of schema changes (compatible with db_tools->perform_schema_changes()) +	* {@inheritdoc}  	*/  	public function revert_schema()  	{ @@ -115,9 +102,7 @@ abstract class migration  	}  	/** -	* Updates data by returning a list of instructions to be executed -	* -	* @return array Array of data update instructions +	* {@inheritdoc}  	*/  	public function update_data()  	{ @@ -125,12 +110,7 @@ abstract class migration  	}  	/** -	* Reverts data by returning a list of instructions to be executed -	* -	* @return array Array of data instructions that will be performed on revert -	* 	NOTE: calls to tools (such as config.add) are automatically reverted when -	* 		possible, so you should not attempt to revert those, this is mostly for -	* 		otherwise unrevertable calls (custom functions for example) +	* {@inheritdoc}  	*/  	public function revert_data()  	{ diff --git a/phpBB/phpbb/db/migration/migration_interface.php b/phpBB/phpbb/db/migration/migration_interface.php new file mode 100644 index 0000000000..2aba5ec608 --- /dev/null +++ b/phpBB/phpbb/db/migration/migration_interface.php @@ -0,0 +1,70 @@ +<?php +/** + * + * This file is part of the phpBB Forum Software package. + * + * @copyright (c) phpBB Limited <https://www.phpbb.com> + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration; + +/** + * Base class interface for database migrations + */ +interface migration_interface +{ +	/** +	 * Defines other migrations to be applied first +	 * +	 * @return array An array of migration class names +	 */ +	static public function depends_on(); + +	/** +	 * Allows you to check if the migration is effectively installed (entirely optional) +	 * +	 * This is checked when a migration is installed. If true is returned, the migration will be set as +	 * installed without performing the database changes. +	 * This function is intended to help moving to migrations from a previous database updater, where some +	 * migrations may have been installed already even though they are not yet listed in the migrations table. +	 * +	 * @return bool True if this migration is installed, False if this migration is not installed (checked on install) +	 */ +	public function effectively_installed(); + +	/** +	 * Updates the database schema by providing a set of change instructions +	 * +	 * @return array Array of schema changes (compatible with db_tools->perform_schema_changes()) +	 */ +	public function update_schema(); + +	/** +	 * Reverts the database schema by providing a set of change instructions +	 * +	 * @return array Array of schema changes (compatible with db_tools->perform_schema_changes()) +	 */ +	public function revert_schema(); + +	/** +	 * Updates data by returning a list of instructions to be executed +	 * +	 * @return array Array of data update instructions +	 */ +	public function update_data(); + +	/** +	 * Reverts data by returning a list of instructions to be executed +	 * +	 * @return array Array of data instructions that will be performed on revert +	 * 	NOTE: calls to tools (such as config.add) are automatically reverted when +	 * 		possible, so you should not attempt to revert those, this is mostly for +	 * 		otherwise unrevertable calls (custom functions for example) +	 */ +	public function revert_data(); +} diff --git a/phpBB/phpbb/db/migration/profilefield_base_migration.php b/phpBB/phpbb/db/migration/profilefield_base_migration.php index da1a38e2fa..3f26a4998c 100644 --- a/phpBB/phpbb/db/migration/profilefield_base_migration.php +++ b/phpBB/phpbb/db/migration/profilefield_base_migration.php @@ -237,6 +237,7 @@ abstract class profilefield_base_migration extends container_aware_migration  		if ($profile_row === null)  		{ +			/* @var $manager \phpbb\profilefields\manager */  			$manager = $this->container->get('profilefields.manager');  			$profile_row = $manager->build_insert_sql_array(array());  		} diff --git a/phpBB/phpbb/db/migration/schema_generator.php b/phpBB/phpbb/db/migration/schema_generator.php index 91d8307d91..c579e25824 100644 --- a/phpBB/phpbb/db/migration/schema_generator.php +++ b/phpBB/phpbb/db/migration/schema_generator.php @@ -24,7 +24,7 @@ class schema_generator  	/** @var \phpbb\db\driver\driver_interface */  	protected $db; -	/** @var \phpbb\db\tools */ +	/** @var \phpbb\db\tools\tools_interface */  	protected $db_tools;  	/** @var array */ @@ -48,7 +48,7 @@ class schema_generator  	/**  	* Constructor  	*/ -	public function __construct(array $class_names, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\db\tools $db_tools, $phpbb_root_path, $php_ext, $table_prefix) +	public function __construct(array $class_names, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\db\tools\tools_interface $db_tools, $phpbb_root_path, $php_ext, $table_prefix)  	{  		$this->config = $config;  		$this->db = $db; @@ -77,8 +77,15 @@ class schema_generator  		$check_dependencies = true;  		while (!empty($migrations))  		{ -			foreach ($migrations as $migration_class) +			foreach ($migrations as $key => $migration_class)  			{ +				// Unset classes that are not a valid migration +				if (\phpbb\db\migrator::is_migration($migration_class) === false) +				{ +					unset($migrations[$key]); +					continue; +				} +  				$open_dependencies = array_diff($migration_class::depends_on(), $tree);  				if (empty($open_dependencies)) diff --git a/phpBB/phpbb/db/migration/tool/module.php b/phpBB/phpbb/db/migration/tool/module.php index 035625b095..a5ed62fd65 100644 --- a/phpBB/phpbb/db/migration/tool/module.php +++ b/phpBB/phpbb/db/migration/tool/module.php @@ -13,6 +13,8 @@  namespace phpbb\db\migration\tool; +use phpbb\module\exception\module_exception; +  /**  * Migration module management tool  */ @@ -27,6 +29,9 @@ class module implements \phpbb\db\migration\tool\tool_interface  	/** @var \phpbb\user */  	protected $user; +	/** @var \phpbb\module\module_manager */ +	protected $module_manager; +  	/** @var string */  	protected $phpbb_root_path; @@ -42,15 +47,17 @@ class module implements \phpbb\db\migration\tool\tool_interface  	* @param \phpbb\db\driver\driver_interface $db  	* @param \phpbb\cache\service $cache  	* @param \phpbb\user $user +	* @param \phpbb\module\module_manager	$module_manager  	* @param string $phpbb_root_path  	* @param string $php_ext  	* @param string $modules_table  	*/ -	public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\cache\service $cache, \phpbb\user $user, $phpbb_root_path, $php_ext, $modules_table) +	public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\cache\service $cache, \phpbb\user $user, \phpbb\module\module_manager $module_manager, $phpbb_root_path, $php_ext, $modules_table)  	{  		$this->db = $db;  		$this->cache = $cache;  		$this->user = $user; +		$this->module_manager = $module_manager;  		$this->phpbb_root_path = $phpbb_root_path;  		$this->php_ext = $php_ext;  		$this->modules_table = $modules_table; @@ -171,6 +178,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; @@ -186,7 +195,6 @@ class module implements \phpbb\db\migration\tool\tool_interface  			$basename = (isset($data['module_basename'])) ? $data['module_basename'] : '';  			$module = $this->get_module_info($class, $basename); -			$result = '';  			foreach ($module['modes'] as $mode => $module_info)  			{  				if (!isset($data['modes']) || in_array($mode, $data['modes'])) @@ -237,13 +245,6 @@ class module implements \phpbb\db\migration\tool\tool_interface  			return;  		} -		if (!class_exists('acp_modules')) -		{ -			include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); -			$this->user->add_lang('acp/modules'); -		} -		$acp_modules = new \acp_modules(); -  		$module_data = array(  			'module_enabled'	=> (isset($data['module_enabled'])) ? $data['module_enabled'] : 1,  			'module_display'	=> (isset($data['module_display'])) ? $data['module_display'] : 1, @@ -254,19 +255,14 @@ class module implements \phpbb\db\migration\tool\tool_interface  			'module_mode'		=> (isset($data['module_mode'])) ? $data['module_mode'] : '',  			'module_auth'		=> (isset($data['module_auth'])) ? $data['module_auth'] : '',  		); -		$result = $acp_modules->update_module_data($module_data, true); -		// update_module_data can either return a string or an empty array... -		if (is_string($result)) -		{ -			// Error -			throw new \phpbb\db\migration\exception('MODULE_ERROR', $result); -		} -		else +		try  		{ +			$this->module_manager->update_module_data($module_data); +  			// Success  			$module_log_name = ((isset($this->user->lang[$data['module_langname']])) ? $this->user->lang[$data['module_langname']] : $data['module_langname']); -			add_log('admin', 'LOG_MODULE_ADD', $module_log_name); +			$phpbb_log->add('admin', (isset($user->data['user_id'])) ? $user->data['user_id'] : ANONYMOUS, $user->ip, 'LOG_MODULE_ADD', false, array($module_log_name));  			// Move the module if requested above/below an existing one  			if (isset($data['before']) && $data['before']) @@ -316,6 +312,11 @@ class module implements \phpbb\db\migration\tool\tool_interface  				$this->db->sql_query($sql);  			}  		} +		catch (module_exception $e) +		{ +			// Error +			throw new \phpbb\db\migration\exception('MODULE_ERROR', $e->getMessage()); +		}  		// Clear the Modules Cache  		$this->cache->destroy("_modules_$class"); @@ -415,21 +416,9 @@ class module implements \phpbb\db\migration\tool\tool_interface  				$module_ids[] = (int) $module;  			} -			if (!class_exists('acp_modules')) -			{ -				include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); -				$this->user->add_lang('acp/modules'); -			} -			$acp_modules = new \acp_modules(); -			$acp_modules->module_class = $class; -  			foreach ($module_ids as $module_id)  			{ -				$result = $acp_modules->delete_module($module_id); -				if (!empty($result)) -				{ -					return; -				} +				$this->module_manager->delete_module($module_id, $class);  			}  			$this->cache->destroy("_modules_$class"); @@ -472,13 +461,7 @@ class module implements \phpbb\db\migration\tool\tool_interface  	*/  	protected function get_module_info($class, $basename)  	{ -		if (!class_exists('acp_modules')) -		{ -			include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext); -			$this->user->add_lang('acp/modules'); -		} -		$acp_modules = new \acp_modules(); -		$module = $acp_modules->get_module_infos($basename, $class, true); +		$module = $this->module_manager->get_module_infos($class, $basename, true);  		if (empty($module))  		{ diff --git a/phpBB/phpbb/db/migrator.php b/phpBB/phpbb/db/migrator.php index 7fc3e787e2..a1e93942cd 100644 --- a/phpBB/phpbb/db/migrator.php +++ b/phpBB/phpbb/db/migrator.php @@ -13,6 +13,8 @@  namespace phpbb\db; +use phpbb\db\output_handler\migrator_output_handler_interface; +use phpbb\db\output_handler\null_migrator_output_handler;  use Symfony\Component\DependencyInjection\ContainerAwareInterface;  use Symfony\Component\DependencyInjection\ContainerInterface; @@ -32,7 +34,7 @@ class migrator  	/** @var \phpbb\db\driver\driver_interface */  	protected $db; -	/** @var \phpbb\db\tools */ +	/** @var \phpbb\db\tools\tools_interface */  	protected $db_tools;  	/** @var \phpbb\db\migration\helper */ @@ -92,7 +94,7 @@ class migrator  	/**  	* Constructor of the database migrator  	*/ -	public function __construct(ContainerInterface $container, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\db\tools $db_tools, $migrations_table, $phpbb_root_path, $php_ext, $table_prefix, $tools, \phpbb\db\migration\helper $helper) +	public function __construct(ContainerInterface $container, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\db\tools\tools_interface $db_tools, $migrations_table, $phpbb_root_path, $php_ext, $table_prefix, $tools, \phpbb\db\migration\helper $helper)  	{  		$this->container = $container;  		$this->config = $config; @@ -122,7 +124,7 @@ class migrator  	/**  	 * Set the output handler.  	 * -	 * @param migrator_output_handler $handler The output handler +	 * @param migrator_output_handler_interface $handler The output handler  	 */  	public function set_output_handler(migrator_output_handler_interface $handler)  	{ @@ -168,10 +170,28 @@ class migrator  	*/  	public function set_migrations($class_names)  	{ +		foreach ($class_names as $key => $class) +		{ +			if (!self::is_migration($class)) +			{ +				unset($class_names[$key]); +			} +		} +  		$this->migrations = $class_names;  	}  	/** +	 * Get the list of available migration class names +	 * +	 * @return array Array of all migrations available to be run +	 */ +	public function get_migrations() +	{ +		return $this->migrations; +	} + +	/**  	* Runs a single update step from the next migration to be applied.  	*  	* The update step can either be a schema or a (partial) data update. To @@ -416,6 +436,9 @@ class migrator  		if ($state['migration_data_done'])  		{ +			$this->output_handler->write(array('MIGRATION_REVERT_DATA_RUNNING', $name), migrator_output_handler_interface::VERBOSITY_VERBOSE); +			$elapsed_time = microtime(true); +  			if ($state['migration_data_state'] !== 'revert_data')  			{  				$result = $this->process_data_step($migration->update_data(), $state['migration_data_state'], true); @@ -431,9 +454,22 @@ class migrator  			}  			$this->set_migration_state($name, $state); + +			$elapsed_time = microtime(true) - $elapsed_time; +			if ($state['migration_data_done']) +			{ +				$this->output_handler->write(array('MIGRATION_REVERT_DATA_DONE', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_NORMAL); +			} +			else +			{ +				$this->output_handler->write(array('MIGRATION_REVERT_DATA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE); +			}  		}  		else if ($state['migration_schema_done'])  		{ +			$this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_RUNNING', $name), migrator_output_handler_interface::VERBOSITY_VERBOSE); +			$elapsed_time = microtime(true); +  			$steps = $this->helper->get_schema_steps($migration->revert_schema());  			$result = $this->process_data_step($steps, $state['migration_data_state']); @@ -448,6 +484,9 @@ class migrator  				unset($this->migration_state[$name]);  			} + +			$elapsed_time = microtime(true) - $elapsed_time; +			$this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_DONE', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_NORMAL);  		}  		return true; @@ -514,7 +553,7 @@ class migrator  					}  					// Reverse the step that was run -					$result = $this->run_step($reverse_step, false, !$revert); +					$this->run_step($reverse_step, false, !$revert);  				}  				// rethrow the exception @@ -836,4 +875,27 @@ class migrator  			));  		}  	} + +	/** +	 * Check if a class is a migration. +	 * +	 * @param string $migration A migration class name +	 * @return bool Return true if class is a migration, false otherwise +	 */ +	static public function is_migration($migration) +	{ +		if (class_exists($migration)) +		{ +			// Migration classes should extend the abstract class +			// phpbb\db\migration\migration (which implements the +			// migration_interface) and be instantiable. +			$reflector = new \ReflectionClass($migration); +			if ($reflector->implementsInterface('\phpbb\db\migration\migration_interface') && $reflector->isInstantiable()) +			{ +				return true; +			} +		} + +		return false; +	}  } diff --git a/phpBB/phpbb/db/html_migrator_output_handler.php b/phpBB/phpbb/db/output_handler/html_migrator_output_handler.php index e37c667463..67309649c9 100644 --- a/phpBB/phpbb/db/html_migrator_output_handler.php +++ b/phpBB/phpbb/db/output_handler/html_migrator_output_handler.php @@ -11,27 +11,25 @@  *  */ -namespace phpbb\db; - -use phpbb\user; +namespace phpbb\db\output_handler;  class html_migrator_output_handler implements migrator_output_handler_interface  {  	/** -	 * User object. +	 * Language object.  	 * -	 * @var user +	 * @var \phpbb\language\language  	 */ -	private $user; +	private $language;  	/**  	 * Constructor  	 * -	 * @param user $user	User object +	 * @param \phpbb\language\language	$language	Language object  	 */ -	public function __construct(user $user) +	public function __construct(\phpbb\language\language $language)  	{ -		$this->user = $user; +		$this->language = $language;  	}  	/** @@ -41,7 +39,7 @@ class html_migrator_output_handler implements migrator_output_handler_interface  	{  		if ($verbosity <= migrator_output_handler_interface::VERBOSITY_VERBOSE)  		{ -			$final_message = call_user_func_array(array($this->user, 'lang'), $message); +			$final_message = $this->language->lang_array(array_shift($message), $message);  			echo $final_message . "<br />\n";  		}  	} diff --git a/phpBB/phpbb/db/output_handler/installer_migrator_output_handler.php b/phpBB/phpbb/db/output_handler/installer_migrator_output_handler.php new file mode 100644 index 0000000000..56d5cf49a1 --- /dev/null +++ b/phpBB/phpbb/db/output_handler/installer_migrator_output_handler.php @@ -0,0 +1,46 @@ +<?php +/** + * + * This file is part of the phpBB Forum Software package. + * + * @copyright (c) phpBB Limited <https://www.phpbb.com> + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\output_handler; + +use phpbb\install\helper\iohandler\iohandler_interface; + +class installer_migrator_output_handler implements migrator_output_handler_interface +{ +	/** +	 * @var iohandler_interface +	 */ +	protected $iohandler; + +	/** +	 * Constructor +	 * +	 * @param iohandler_interface	$iohandler	Installer's IO-handler +	 */ +	public function __construct(iohandler_interface $iohandler) +	{ +		$this->iohandler = $iohandler; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function write($message, $verbosity) +	{ +		if ($verbosity <= migrator_output_handler_interface::VERBOSITY_VERBOSE) +		{ +			$this->iohandler->add_log_message($message); +			$this->iohandler->send_response(); +		} +	} +} diff --git a/phpBB/phpbb/db/log_wrapper_migrator_output_handler.php b/phpBB/phpbb/db/output_handler/log_wrapper_migrator_output_handler.php index 94c293dc45..20991746ac 100644 --- a/phpBB/phpbb/db/log_wrapper_migrator_output_handler.php +++ b/phpBB/phpbb/db/output_handler/log_wrapper_migrator_output_handler.php @@ -11,18 +11,16 @@  *  */ -namespace phpbb\db; - -use phpbb\user; +namespace phpbb\db\output_handler;  class log_wrapper_migrator_output_handler implements migrator_output_handler_interface  {  	/** -	 * User object. +	 * Language object.  	 * -	 * @var user +	 * @var \phpbb\language\language  	 */ -	protected $user; +	protected $language;  	/**  	 * A migrator output handler @@ -38,16 +36,23 @@ class log_wrapper_migrator_output_handler implements migrator_output_handler_int  	protected $file_handle = false;  	/** +	 * @var \phpbb\filesystem\filesystem_interface +	 */ +	protected $filesystem; + +	/**  	 * Constructor  	 * -	 * @param user $user	User object -	 * @param migrator_output_handler_interface $migrator Migrator output handler -	 * @param string $log_file	File to log to +	 * @param \phpbb\language\language					$language	Language object +	 * @param migrator_output_handler_interface			$migrator	Migrator output handler +	 * @param string									$log_file	File to log to +	 * @param \phpbb\filesystem\filesystem_interface	$filesystem	phpBB filesystem object  	 */ -	public function __construct(user $user, migrator_output_handler_interface $migrator, $log_file) +	public function __construct(\phpbb\language\language $language, migrator_output_handler_interface $migrator, $log_file, \phpbb\filesystem\filesystem_interface $filesystem)  	{ -		$this->user = $user; +		$this->language = $language;  		$this->migrator = $migrator; +		$this->filesystem = $filesystem;  		$this->file_open($log_file);  	} @@ -58,7 +63,7 @@ class log_wrapper_migrator_output_handler implements migrator_output_handler_int  	 */  	protected function file_open($file)  	{ -		if (phpbb_is_writable(dirname($file))) +		if ($this->filesystem->is_writable(dirname($file)))  		{  			$this->file_handle = fopen($file, 'w');  		} @@ -77,7 +82,8 @@ class log_wrapper_migrator_output_handler implements migrator_output_handler_int  		if ($this->file_handle !== false)  		{ -			$translated_message = call_user_func_array(array($this->user, 'lang'), $message) . "\n"; + +			$translated_message = $this->language->lang_array(array_shift($message), $message);  			if ($verbosity <= migrator_output_handler_interface::VERBOSITY_NORMAL)  			{ diff --git a/phpBB/phpbb/db/migrator_output_handler_interface.php b/phpBB/phpbb/db/output_handler/migrator_output_handler_interface.php index a923af99f6..7bb5c73fec 100644 --- a/phpBB/phpbb/db/migrator_output_handler_interface.php +++ b/phpBB/phpbb/db/output_handler/migrator_output_handler_interface.php @@ -11,7 +11,7 @@  *  */ -namespace phpbb\db; +namespace phpbb\db\output_handler;  interface migrator_output_handler_interface  { diff --git a/phpBB/phpbb/db/null_migrator_output_handler.php b/phpBB/phpbb/db/output_handler/null_migrator_output_handler.php index 0e8cfbb049..5fc2a52577 100644 --- a/phpBB/phpbb/db/null_migrator_output_handler.php +++ b/phpBB/phpbb/db/output_handler/null_migrator_output_handler.php @@ -11,7 +11,7 @@  *  */ -namespace phpbb\db; +namespace phpbb\db\output_handler;  class null_migrator_output_handler implements migrator_output_handler_interface  { diff --git a/phpBB/phpbb/db/tools/factory.php b/phpBB/phpbb/db/tools/factory.php new file mode 100644 index 0000000000..d204451a63 --- /dev/null +++ b/phpBB/phpbb/db/tools/factory.php @@ -0,0 +1,43 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\tools; + +/** + * A factory which serves the suitable tools instance for the given dbal + */ +class factory +{ +	/** +	 * @param mixed $db_driver +	 * @param bool $return_statements +	 * @return \phpbb\db\tools\tools_interface +	 */ +	public function get($db_driver, $return_statements = false) +	{ +		if ($db_driver instanceof \phpbb\db\driver\mssql || $db_driver instanceof \phpbb\db\driver\mssql_base) +		{ +			return new \phpbb\db\tools\mssql($db_driver, $return_statements); +		} +		else if ($db_driver instanceof \phpbb\db\driver\postgres) +		{ +			return new \phpbb\db\tools\postgres($db_driver, $return_statements); +		} +		else if ($db_driver instanceof \phpbb\db\driver\driver_interface) +		{ +			return new \phpbb\db\tools\tools($db_driver, $return_statements); +		} + +		throw new \InvalidArgumentException('Invalid database driver given'); +	} +} diff --git a/phpBB/phpbb/db/tools/mssql.php b/phpBB/phpbb/db/tools/mssql.php new file mode 100644 index 0000000000..87719acd21 --- /dev/null +++ b/phpBB/phpbb/db/tools/mssql.php @@ -0,0 +1,795 @@ +<?php +/** + * + * This file is part of the phpBB Forum Software package. + * + * @copyright (c) phpBB Limited <https://www.phpbb.com> + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\tools; + +/** + * Database Tools for handling cross-db actions such as altering columns, etc. + * Currently not supported is returning SQL for creating tables. + */ +class mssql extends tools +{ +	/** +	 * Is the used MS SQL Server a SQL Server 2000? +	 * @var bool +	 */ +	protected $is_sql_server_2000; + +	/** +	 * Get the column types for mssql based databases +	 * +	 * @return array +	 */ +	public static function get_dbms_type_map() +	{ +		return array( +			'mssql'		=> array( +				'INT:'		=> '[int]', +				'BINT'		=> '[float]', +				'ULINT'		=> '[int]', +				'UINT'		=> '[int]', +				'UINT:'		=> '[int]', +				'TINT:'		=> '[int]', +				'USINT'		=> '[int]', +				'BOOL'		=> '[int]', +				'VCHAR'		=> '[varchar] (255)', +				'VCHAR:'	=> '[varchar] (%d)', +				'CHAR:'		=> '[char] (%d)', +				'XSTEXT'	=> '[varchar] (1000)', +				'STEXT'		=> '[varchar] (3000)', +				'TEXT'		=> '[varchar] (8000)', +				'MTEXT'		=> '[text]', +				'XSTEXT_UNI'=> '[varchar] (100)', +				'STEXT_UNI'	=> '[varchar] (255)', +				'TEXT_UNI'	=> '[varchar] (4000)', +				'MTEXT_UNI'	=> '[text]', +				'TIMESTAMP'	=> '[int]', +				'DECIMAL'	=> '[float]', +				'DECIMAL:'	=> '[float]', +				'PDECIMAL'	=> '[float]', +				'PDECIMAL:'	=> '[float]', +				'VCHAR_UNI'	=> '[varchar] (255)', +				'VCHAR_UNI:'=> '[varchar] (%d)', +				'VCHAR_CI'	=> '[varchar] (255)', +				'VARBINARY'	=> '[varchar] (255)', +			), + +			'mssqlnative'	=> array( +				'INT:'		=> '[int]', +				'BINT'		=> '[float]', +				'ULINT'		=> '[int]', +				'UINT'		=> '[int]', +				'UINT:'		=> '[int]', +				'TINT:'		=> '[int]', +				'USINT'		=> '[int]', +				'BOOL'		=> '[int]', +				'VCHAR'		=> '[varchar] (255)', +				'VCHAR:'	=> '[varchar] (%d)', +				'CHAR:'		=> '[char] (%d)', +				'XSTEXT'	=> '[varchar] (1000)', +				'STEXT'		=> '[varchar] (3000)', +				'TEXT'		=> '[varchar] (8000)', +				'MTEXT'		=> '[text]', +				'XSTEXT_UNI'=> '[varchar] (100)', +				'STEXT_UNI'	=> '[varchar] (255)', +				'TEXT_UNI'	=> '[varchar] (4000)', +				'MTEXT_UNI'	=> '[text]', +				'TIMESTAMP'	=> '[int]', +				'DECIMAL'	=> '[float]', +				'DECIMAL:'	=> '[float]', +				'PDECIMAL'	=> '[float]', +				'PDECIMAL:'	=> '[float]', +				'VCHAR_UNI'	=> '[varchar] (255)', +				'VCHAR_UNI:'=> '[varchar] (%d)', +				'VCHAR_CI'	=> '[varchar] (255)', +				'VARBINARY'	=> '[varchar] (255)', +			), +		); +	} + +	/** +	* Constructor. Set DB Object and set {@link $return_statements return_statements}. +	* +	* @param \phpbb\db\driver\driver_interface	$db					Database connection +	* @param bool		$return_statements	True if only statements should be returned and no SQL being executed +	*/ +	public function __construct(\phpbb\db\driver\driver_interface $db, $return_statements = false) +	{ +		parent::__construct($db, $return_statements); + +		// Determine mapping database type +		switch ($this->db->get_sql_layer()) +		{ +			case 'mssql': +			case 'mssql_odbc': +				$this->sql_layer = 'mssql'; +			break; + +			case 'mssqlnative': +				$this->sql_layer = 'mssqlnative'; +			break; +		} + +		$this->dbms_type_map = self::get_dbms_type_map(); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_list_tables() +	{ +		$sql = "SELECT name +			FROM sysobjects +			WHERE type='U'"; +		$result = $this->db->sql_query($sql); + +		$tables = array(); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$name = current($row); +			$tables[$name] = $name; +		} +		$this->db->sql_freeresult($result); + +		return $tables; +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_create_table($table_name, $table_data) +	{ +		// holds the DDL for a column +		$columns = $statements = array(); + +		if ($this->sql_table_exists($table_name)) +		{ +			return $this->_sql_run_sql($statements); +		} + +		// Begin transaction +		$statements[] = 'begin'; + +		// Determine if we have created a PRIMARY KEY in the earliest +		$primary_key_gen = false; + +		// Determine if the table requires a sequence +		$create_sequence = false; + +		// Begin table sql statement +		$table_sql = 'CREATE TABLE [' . $table_name . '] (' . "\n"; + +		if (!isset($table_data['PRIMARY_KEY'])) +		{ +			$table_data['COLUMNS']['mssqlindex'] = array('UINT', null, 'auto_increment'); +			$table_data['PRIMARY_KEY'] = 'mssqlindex'; +		} + +		// Iterate through the columns to create a table +		foreach ($table_data['COLUMNS'] as $column_name => $column_data) +		{ +			// here lies an array, filled with information compiled on the column's data +			$prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + +			if (isset($prepared_column['auto_increment']) && $prepared_column['auto_increment'] && strlen($column_name) > 26) // "${column_name}_gen" +			{ +				trigger_error("Index name '${column_name}_gen' on table '$table_name' is too long. The maximum auto increment column length is 26 characters.", E_USER_ERROR); +			} + +			// here we add the definition of the new column to the list of columns +			$columns[] = "\t [{$column_name}] " . $prepared_column['column_type_sql_default']; + +			// see if we have found a primary key set due to a column definition if we have found it, we can stop looking +			if (!$primary_key_gen) +			{ +				$primary_key_gen = isset($prepared_column['primary_key_set']) && $prepared_column['primary_key_set']; +			} + +			// create sequence DDL based off of the existance of auto incrementing columns +			if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment']) +			{ +				$create_sequence = $column_name; +			} +		} + +		// this makes up all the columns in the create table statement +		$table_sql .= implode(",\n", $columns); + +		// Close the table for two DBMS and add to the statements +		$table_sql .= "\n);"; +		$statements[] = $table_sql; + +		// we have yet to create a primary key for this table, +		// this means that we can add the one we really wanted instead +		if (!$primary_key_gen) +		{ +			// Write primary key +			if (isset($table_data['PRIMARY_KEY'])) +			{ +				if (!is_array($table_data['PRIMARY_KEY'])) +				{ +					$table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']); +				} + +				// We need the data here +				$old_return_statements = $this->return_statements; +				$this->return_statements = true; + +				$primary_key_stmts = $this->sql_create_primary_key($table_name, $table_data['PRIMARY_KEY']); +				foreach ($primary_key_stmts as $pk_stmt) +				{ +					$statements[] = $pk_stmt; +				} + +				$this->return_statements = $old_return_statements; +			} +		} + +		// Write Keys +		if (isset($table_data['KEYS'])) +		{ +			foreach ($table_data['KEYS'] as $key_name => $key_data) +			{ +				if (!is_array($key_data[1])) +				{ +					$key_data[1] = array($key_data[1]); +				} + +				$old_return_statements = $this->return_statements; +				$this->return_statements = true; + +				$key_stmts = ($key_data[0] == 'UNIQUE') ? $this->sql_create_unique_index($table_name, $key_name, $key_data[1]) : $this->sql_create_index($table_name, $key_name, $key_data[1]); + +				foreach ($key_stmts as $key_stmt) +				{ +					$statements[] = $key_stmt; +				} + +				$this->return_statements = $old_return_statements; +			} +		} + +		// Commit Transaction +		$statements[] = 'commit'; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_list_columns($table_name) +	{ +		$columns = array(); + +		$sql = "SELECT c.name +			FROM syscolumns c +			LEFT JOIN sysobjects o ON c.id = o.id +			WHERE o.name = '{$table_name}'"; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$column = strtolower(current($row)); +			$columns[$column] = $column; +		} +		$this->db->sql_freeresult($result); + +		return $columns; +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_index_exists($table_name, $index_name) +	{ +		$sql = "EXEC sp_statistics '$table_name'"; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if ($row['TYPE'] == 3) +			{ +				if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) +				{ +					$this->db->sql_freeresult($result); +					return true; +				} +			} +		} +		$this->db->sql_freeresult($result); + +		return false; +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_unique_index_exists($table_name, $index_name) +	{ +		$sql = "EXEC sp_statistics '$table_name'"; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			// Usually NON_UNIQUE is the column we want to check, but we allow for both +			if ($row['TYPE'] == 3) +			{ +				if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) +				{ +					$this->db->sql_freeresult($result); +					return true; +				} +			} +		} +		$this->db->sql_freeresult($result); + +		return false; +	} + +	/** +	 * {@inheritDoc} +	*/ +	function sql_prepare_column_data($table_name, $column_name, $column_data) +	{ +		if (strlen($column_name) > 30) +		{ +			trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR); +		} + +		// Get type +		list($column_type, ) = $this->get_column_type($column_data[0]); + +		// Adjust default value if db-dependent specified +		if (is_array($column_data[1])) +		{ +			$column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default']; +		} + +		$sql = ''; + +		$return_array = array(); + +		$sql .= " {$column_type} "; +		$sql_default = " {$column_type} "; + +		// For adding columns we need the default definition +		if (!is_null($column_data[1])) +		{ +			// For hexadecimal values do not use single quotes +			if (strpos($column_data[1], '0x') === 0) +			{ +				$return_array['default'] = 'DEFAULT (' . $column_data[1] . ') '; +				$sql_default .= $return_array['default']; +			} +			else +			{ +				$return_array['default'] = 'DEFAULT (' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ') '; +				$sql_default .= $return_array['default']; +			} +		} + +		if (isset($column_data[2]) && $column_data[2] == 'auto_increment') +		{ +			// $sql .= 'IDENTITY (1, 1) '; +			$sql_default .= 'IDENTITY (1, 1) '; +		} + +		$return_array['textimage'] = $column_type === '[text]'; + +		if (!is_null($column_data[1]) || (isset($column_data[2]) && $column_data[2] == 'auto_increment')) +		{ +			$sql .= 'NOT NULL'; +			$sql_default .= 'NOT NULL'; +		} +		else +		{ +			$sql .= 'NULL'; +			$sql_default .= 'NULL'; +		} + +		$return_array['column_type_sql_default'] = $sql_default; + +		$return_array['column_type_sql'] = $sql; + +		return $return_array; +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_column_add($table_name, $column_name, $column_data, $inline = false) +	{ +		$column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); +		$statements = array(); + +		// Does not support AFTER, only through temporary table +		$statements[] = 'ALTER TABLE [' . $table_name . '] ADD [' . $column_name . '] ' . $column_data['column_type_sql_default']; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_column_remove($table_name, $column_name, $inline = false) +	{ +		$statements = array(); + +		// We need the data here +		$old_return_statements = $this->return_statements; +		$this->return_statements = true; + +		$indexes = $this->get_existing_indexes($table_name, $column_name); +		$indexes = array_merge($indexes, $this->get_existing_indexes($table_name, $column_name, true)); + +		// Drop any indexes +		$recreate_indexes = array(); +		if (!empty($indexes)) +		{ +			foreach ($indexes as $index_name => $index_data) +			{ +				$result = $this->sql_index_drop($table_name, $index_name); +				$statements = array_merge($statements, $result); +				if (sizeof($index_data) > 1) +				{ +					// Remove this column from the index and recreate it +					$recreate_indexes[$index_name] = array_diff($index_data, array($column_name)); +				} +			} +		} + +		// Drop default value constraint +		$result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name); +		$statements = array_merge($statements, $result); + +		// Remove the column +		$statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']'; + +		if (!empty($recreate_indexes)) +		{ +			// Recreate indexes after we removed the column +			foreach ($recreate_indexes as $index_name => $index_data) +			{ +				$result = $this->sql_create_index($table_name, $index_name, $index_data); +				$statements = array_merge($statements, $result); +			} +		} + +		$this->return_statements = $old_return_statements; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_index_drop($table_name, $index_name) +	{ +		$statements = array(); + +		$statements[] = 'DROP INDEX ' . $table_name . '.' . $index_name; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_table_drop($table_name) +	{ +		$statements = array(); + +		if (!$this->sql_table_exists($table_name)) +		{ +			return $this->_sql_run_sql($statements); +		} + +		// the most basic operation, get rid of the table +		$statements[] = 'DROP TABLE ' . $table_name; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_create_primary_key($table_name, $column, $inline = false) +	{ +		$statements = array(); + +		$sql = "ALTER TABLE [{$table_name}] WITH NOCHECK ADD "; +		$sql .= "CONSTRAINT [PK_{$table_name}] PRIMARY KEY  CLUSTERED ("; +		$sql .= '[' . implode("],\n\t\t[", $column) . ']'; +		$sql .= ')'; + +		$statements[] = $sql; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_create_unique_index($table_name, $index_name, $column) +	{ +		$statements = array(); + +		$this->check_index_name_length($table_name, $index_name); + +		$statements[] = 'CREATE UNIQUE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])'; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_create_index($table_name, $index_name, $column) +	{ +		$statements = array(); + +		$this->check_index_name_length($table_name, $index_name); + +		// remove index length +		$column = preg_replace('#:.*$#', '', $column); + +		$statements[] = 'CREATE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])'; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_list_index($table_name) +	{ +		$index_array = array(); +		$sql = "EXEC sp_statistics '$table_name'"; +		$result = $this->db->sql_query($sql); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if ($row['TYPE'] == 3) +			{ +				$index_array[] = strtolower($row['INDEX_NAME']); +			} +		} +		$this->db->sql_freeresult($result); + +		return $index_array; +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_column_change($table_name, $column_name, $column_data, $inline = false) +	{ +		$column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); +		$statements = array(); + +		// We need the data here +		$old_return_statements = $this->return_statements; +		$this->return_statements = true; + +		$indexes = $this->get_existing_indexes($table_name, $column_name); +		$unique_indexes = $this->get_existing_indexes($table_name, $column_name, true); + +		// Drop any indexes +		if (!empty($indexes) || !empty($unique_indexes)) +		{ +			$drop_indexes = array_merge(array_keys($indexes), array_keys($unique_indexes)); +			foreach ($drop_indexes as $index_name) +			{ +				$result = $this->sql_index_drop($table_name, $index_name); +				$statements = array_merge($statements, $result); +			} +		} + +		// Drop default value constraint +		$result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name); +		$statements = array_merge($statements, $result); + +		// Change the column +		$statements[] = 'ALTER TABLE [' . $table_name . '] ALTER COLUMN [' . $column_name . '] ' . $column_data['column_type_sql']; + +		if (!empty($column_data['default'])) +		{ +			// Add new default value constraint +			$statements[] = 'ALTER TABLE [' . $table_name . '] ADD CONSTRAINT [DF_' . $table_name . '_' . $column_name . '_1] ' . $column_data['default'] . ' FOR [' . $column_name . ']'; +		} + +		if (!empty($indexes)) +		{ +			// Recreate indexes after we changed the column +			foreach ($indexes as $index_name => $index_data) +			{ +				$result = $this->sql_create_index($table_name, $index_name, $index_data); +				$statements = array_merge($statements, $result); +			} +		} + +		if (!empty($unique_indexes)) +		{ +			// Recreate unique indexes after we changed the column +			foreach ($unique_indexes as $index_name => $index_data) +			{ +				$result = $this->sql_create_unique_index($table_name, $index_name, $index_data); +				$statements = array_merge($statements, $result); +			} +		} + +		$this->return_statements = $old_return_statements; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	* Get queries to drop the default constraints of a column +	* +	* We need to drop the default constraints of a column, +	* before being able to change their type or deleting them. +	* +	* @param string $table_name +	* @param string $column_name +	* @return array		Array with SQL statements +	*/ +	protected function mssql_get_drop_default_constraints_queries($table_name, $column_name) +	{ +		$statements = array(); +		if ($this->mssql_is_sql_server_2000()) +		{ +			// http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx +			// Deprecated in SQL Server 2005 +			$sql = "SELECT so.name AS def_name +				FROM sysobjects so +				JOIN sysconstraints sc ON so.id = sc.constid +				WHERE object_name(so.parent_obj) = '{$table_name}' +					AND so.xtype = 'D' +					AND sc.colid = (SELECT colid FROM syscolumns +						WHERE id = object_id('{$table_name}') +							AND name = '{$column_name}')"; +		} +		else +		{ +			$sql = "SELECT dobj.name AS def_name +				FROM sys.columns col +					LEFT OUTER JOIN sys.objects dobj ON (dobj.object_id = col.default_object_id AND dobj.type = 'D') +				WHERE col.object_id = object_id('{$table_name}') +					AND col.name = '{$column_name}' +					AND dobj.name IS NOT NULL"; +		} + +		$result = $this->db->sql_query($sql); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$statements[] = 'ALTER TABLE [' . $table_name . '] DROP CONSTRAINT [' . $row['def_name'] . ']'; +		} +		$this->db->sql_freeresult($result); + +		return $statements; +	} + +	/** +	* Get a list with existing indexes for the column +	* +	* @param string $table_name +	* @param string $column_name +	* @param bool $unique Should we get unique indexes or normal ones +	* @return array		Array with Index name => columns +	*/ +	public function get_existing_indexes($table_name, $column_name, $unique = false) +	{ +		$existing_indexes = array(); +		if ($this->mssql_is_sql_server_2000()) +		{ +			// http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx +			// Deprecated in SQL Server 2005 +			$sql = "SELECT DISTINCT ix.name AS phpbb_index_name +				FROM sysindexes ix +				INNER JOIN sysindexkeys ixc +					ON ixc.id = ix.id +						AND ixc.indid = ix.indid +				INNER JOIN syscolumns cols +					ON cols.colid = ixc.colid +						AND cols.id = ix.id +				WHERE ix.id = object_id('{$table_name}') +					AND cols.name = '{$column_name}' +					AND INDEXPROPERTY(ix.id, ix.name, 'IsUnique') = " . ($unique ? '1' : '0'); +		} +		else +		{ +			$sql = "SELECT DISTINCT ix.name AS phpbb_index_name +				FROM sys.indexes ix +				INNER JOIN sys.index_columns ixc +					ON ixc.object_id = ix.object_id +						AND ixc.index_id = ix.index_id +				INNER JOIN sys.columns cols +					ON cols.column_id = ixc.column_id +						AND cols.object_id = ix.object_id +				WHERE ix.object_id = object_id('{$table_name}') +					AND cols.name = '{$column_name}' +					AND ix.is_unique = " . ($unique ? '1' : '0'); +		} + +		$result = $this->db->sql_query($sql); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if (!isset($row['is_unique']) || ($unique && $row['is_unique'] == 'UNIQUE') || (!$unique && $row['is_unique'] == 'NONUNIQUE')) +			{ +				$existing_indexes[$row['phpbb_index_name']] = array(); +			} +		} +		$this->db->sql_freeresult($result); + +		if (empty($existing_indexes)) +		{ +			return array(); +		} + +		if ($this->mssql_is_sql_server_2000()) +		{ +			$sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name +				FROM sysindexes ix +				INNER JOIN sysindexkeys ixc +					ON ixc.id = ix.id +						AND ixc.indid = ix.indid +				INNER JOIN syscolumns cols +					ON cols.colid = ixc.colid +						AND cols.id = ix.id +				WHERE ix.id = object_id('{$table_name}') +					AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes)); +		} +		else +		{ +			$sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name +				FROM sys.indexes ix +				INNER JOIN sys.index_columns ixc +					ON ixc.object_id = ix.object_id +						AND ixc.index_id = ix.index_id +				INNER JOIN sys.columns cols +					ON cols.column_id = ixc.column_id +						AND cols.object_id = ix.object_id +				WHERE ix.object_id = object_id('{$table_name}') +					AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes)); +		} + +		$result = $this->db->sql_query($sql); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$existing_indexes[$row['phpbb_index_name']][] = $row['phpbb_column_name']; +		} +		$this->db->sql_freeresult($result); + +		return $existing_indexes; +	} + +	/** +	* Is the used MS SQL Server a SQL Server 2000? +	* +	* @return bool +	*/ +	protected function mssql_is_sql_server_2000() +	{ +		if ($this->is_sql_server_2000 === null) +		{ +			$sql = "SELECT CAST(SERVERPROPERTY('productversion') AS VARCHAR(25)) AS mssql_version"; +			$result = $this->db->sql_query($sql); +			$properties = $this->db->sql_fetchrow($result); +			$this->db->sql_freeresult($result); +			$this->is_sql_server_2000 = $properties['mssql_version'][0] == '8'; +		} + +		return $this->is_sql_server_2000; +	} + +} diff --git a/phpBB/phpbb/db/tools/postgres.php b/phpBB/phpbb/db/tools/postgres.php new file mode 100644 index 0000000000..e2a4e668a6 --- /dev/null +++ b/phpBB/phpbb/db/tools/postgres.php @@ -0,0 +1,614 @@ +<?php +/** + * + * This file is part of the phpBB Forum Software package. + * + * @copyright (c) phpBB Limited <https://www.phpbb.com> + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\tools; + +/** + * Database Tools for handling cross-db actions such as altering columns, etc. + * Currently not supported is returning SQL for creating tables. + */ +class postgres extends tools +{ +	/** +	 * Get the column types for postgres only +	 * +	 * @return array +	 */ +	public static function get_dbms_type_map() +	{ +		return array( +			'postgres'	=> array( +				'INT:'		=> 'INT4', +				'BINT'		=> 'INT8', +				'ULINT'		=> 'INT4', // unsigned +				'UINT'		=> 'INT4', // unsigned +				'UINT:'		=> 'INT4', // unsigned +				'USINT'		=> 'INT2', // unsigned +				'BOOL'		=> 'INT2', // unsigned +				'TINT:'		=> 'INT2', +				'VCHAR'		=> 'varchar(255)', +				'VCHAR:'	=> 'varchar(%d)', +				'CHAR:'		=> 'char(%d)', +				'XSTEXT'	=> 'varchar(1000)', +				'STEXT'		=> 'varchar(3000)', +				'TEXT'		=> 'varchar(8000)', +				'MTEXT'		=> 'TEXT', +				'XSTEXT_UNI'=> 'varchar(100)', +				'STEXT_UNI'	=> 'varchar(255)', +				'TEXT_UNI'	=> 'varchar(4000)', +				'MTEXT_UNI'	=> 'TEXT', +				'TIMESTAMP'	=> 'INT4', // unsigned +				'DECIMAL'	=> 'decimal(5,2)', +				'DECIMAL:'	=> 'decimal(%d,2)', +				'PDECIMAL'	=> 'decimal(6,3)', +				'PDECIMAL:'	=> 'decimal(%d,3)', +				'VCHAR_UNI'	=> 'varchar(255)', +				'VCHAR_UNI:'=> 'varchar(%d)', +				'VCHAR_CI'	=> 'varchar_ci', +				'VARBINARY'	=> 'bytea', +			), +		); +	} + +	/** +	* Constructor. Set DB Object and set {@link $return_statements return_statements}. +	* +	* @param \phpbb\db\driver\driver_interface	$db					Database connection +	* @param bool		$return_statements	True if only statements should be returned and no SQL being executed +	*/ +	public function __construct(\phpbb\db\driver\driver_interface $db, $return_statements = false) +	{ +		parent::__construct($db, $return_statements); + +		// Determine mapping database type +		$this->sql_layer = 'postgres'; + +		$this->dbms_type_map = self::get_dbms_type_map(); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_list_tables() +	{ +		$sql = 'SELECT relname +			FROM pg_stat_user_tables'; +		$result = $this->db->sql_query($sql); + +		$tables = array(); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$name = current($row); +			$tables[$name] = $name; +		} +		$this->db->sql_freeresult($result); + +		return $tables; +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_create_table($table_name, $table_data) +	{ +		// holds the DDL for a column +		$columns = $statements = array(); + +		if ($this->sql_table_exists($table_name)) +		{ +			return $this->_sql_run_sql($statements); +		} + +		// Begin transaction +		$statements[] = 'begin'; + +		// Determine if we have created a PRIMARY KEY in the earliest +		$primary_key_gen = false; + +		// Determine if the table requires a sequence +		$create_sequence = false; + +		// Begin table sql statement +		$table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n"; + +		// Iterate through the columns to create a table +		foreach ($table_data['COLUMNS'] as $column_name => $column_data) +		{ +			// here lies an array, filled with information compiled on the column's data +			$prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + +			if (isset($prepared_column['auto_increment']) && $prepared_column['auto_increment'] && strlen($column_name) > 26) // "${column_name}_gen" +			{ +				trigger_error("Index name '${column_name}_gen' on table '$table_name' is too long. The maximum auto increment column length is 26 characters.", E_USER_ERROR); +			} + +			// here we add the definition of the new column to the list of columns +			$columns[] = "\t {$column_name} " . $prepared_column['column_type_sql']; + +			// see if we have found a primary key set due to a column definition if we have found it, we can stop looking +			if (!$primary_key_gen) +			{ +				$primary_key_gen = isset($prepared_column['primary_key_set']) && $prepared_column['primary_key_set']; +			} + +			// create sequence DDL based off of the existance of auto incrementing columns +			if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment']) +			{ +				$create_sequence = $column_name; +			} +		} + +		// this makes up all the columns in the create table statement +		$table_sql .= implode(",\n", $columns); + +		// we have yet to create a primary key for this table, +		// this means that we can add the one we really wanted instead +		if (!$primary_key_gen) +		{ +			// Write primary key +			if (isset($table_data['PRIMARY_KEY'])) +			{ +				if (!is_array($table_data['PRIMARY_KEY'])) +				{ +					$table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']); +				} + +				$table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; +			} +		} + +		// do we need to add a sequence for auto incrementing columns? +		if ($create_sequence) +		{ +			$statements[] = "CREATE SEQUENCE {$table_name}_seq;"; +		} + +		// close the table +		$table_sql .= "\n);"; +		$statements[] = $table_sql; + +		// Write Keys +		if (isset($table_data['KEYS'])) +		{ +			foreach ($table_data['KEYS'] as $key_name => $key_data) +			{ +				if (!is_array($key_data[1])) +				{ +					$key_data[1] = array($key_data[1]); +				} + +				$old_return_statements = $this->return_statements; +				$this->return_statements = true; + +				$key_stmts = ($key_data[0] == 'UNIQUE') ? $this->sql_create_unique_index($table_name, $key_name, $key_data[1]) : $this->sql_create_index($table_name, $key_name, $key_data[1]); + +				foreach ($key_stmts as $key_stmt) +				{ +					$statements[] = $key_stmt; +				} + +				$this->return_statements = $old_return_statements; +			} +		} + +		// Commit Transaction +		$statements[] = 'commit'; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_list_columns($table_name) +	{ +		$columns = array(); + +		$sql = "SELECT a.attname +			FROM pg_class c, pg_attribute a +			WHERE c.relname = '{$table_name}' +				AND a.attnum > 0 +				AND a.attrelid = c.oid"; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$column = strtolower(current($row)); +			$columns[$column] = $column; +		} +		$this->db->sql_freeresult($result); + +		return $columns; +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_index_exists($table_name, $index_name) +	{ +		$sql = "SELECT ic.relname as index_name +			FROM pg_class bc, pg_class ic, pg_index i +			WHERE (bc.oid = i.indrelid) +				AND (ic.oid = i.indexrelid) +				AND (bc.relname = '" . $table_name . "') +				AND (i.indisunique != 't') +				AND (i.indisprimary != 't')"; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			// This DBMS prefixes index names with the table name +			$row['index_name'] = $this->strip_table_name_from_index_name($table_name, $row['index_name']); + +			if (strtolower($row['index_name']) == strtolower($index_name)) +			{ +				$this->db->sql_freeresult($result); +				return true; +			} +		} +		$this->db->sql_freeresult($result); + +		return false; +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_unique_index_exists($table_name, $index_name) +	{ +		$sql = "SELECT ic.relname as index_name, i.indisunique +			FROM pg_class bc, pg_class ic, pg_index i +			WHERE (bc.oid = i.indrelid) +				AND (ic.oid = i.indexrelid) +				AND (bc.relname = '" . $table_name . "') +				AND (i.indisprimary != 't')"; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if ($row['indisunique'] != 't') +			{ +				continue; +			} + +			// This DBMS prefixes index names with the table name +			$row['index_name'] = $this->strip_table_name_from_index_name($table_name, $row['index_name']); + +			if (strtolower($row['index_name']) == strtolower($index_name)) +			{ +				$this->db->sql_freeresult($result); +				return true; +			} +		} +		$this->db->sql_freeresult($result); + +		return false; +	} + +	/** +	* Function to prepare some column information for better usage +	* @access private +	*/ +	function sql_prepare_column_data($table_name, $column_name, $column_data) +	{ +		if (strlen($column_name) > 30) +		{ +			trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR); +		} + +		// Get type +		list($column_type, $orig_column_type) = $this->get_column_type($column_data[0]); + +		// Adjust default value if db-dependent specified +		if (is_array($column_data[1])) +		{ +			$column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default']; +		} + +		$sql = " {$column_type} "; + +		$return_array = array( +			'column_type'		=> $column_type, +			'auto_increment'	=> false, +		); + +		if (isset($column_data[2]) && $column_data[2] == 'auto_increment') +		{ +			$default_val = "nextval('{$table_name}_seq')"; +			$return_array['auto_increment'] = true; +		} +		else if (!is_null($column_data[1])) +		{ +			$default_val = "'" . $column_data[1] . "'"; +			$return_array['null'] = 'NOT NULL'; +			$sql .= 'NOT NULL '; +		} +		else +		{ +			// Integers need to have 0 instead of empty string as default +			if (strpos($column_type, 'INT') === 0) +			{ +				$default_val = '0'; +			} +			else +			{ +				$default_val = "'" . $column_data[1] . "'"; +			} +			$return_array['null'] = 'NULL'; +			$sql .= 'NULL '; +		} + +		$return_array['default'] = $default_val; + +		$sql .= "DEFAULT {$default_val}"; + +		// Unsigned? Then add a CHECK contraint +		if (in_array($orig_column_type, $this->unsigned_types)) +		{ +			$return_array['constraint'] = "CHECK ({$column_name} >= 0)"; +			$sql .= " CHECK ({$column_name} >= 0)"; +		} + +		$return_array['column_type_sql'] = $sql; + +		return $return_array; +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_column_add($table_name, $column_name, $column_data, $inline = false) +	{ +		$column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); +		$statements = array(); + +		// Does not support AFTER, only through temporary table +		if (version_compare($this->db->sql_server_info(true), '8.0', '>=')) +		{ +			$statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type_sql']; +		} +		else +		{ +			// old versions cannot add columns with default and null information +			$statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type'] . ' ' . $column_data['constraint']; + +			if (isset($column_data['null'])) +			{ +				if ($column_data['null'] == 'NOT NULL') +				{ +					$statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET NOT NULL'; +				} +			} + +			if (isset($column_data['default'])) +			{ +				$statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; +			} +		} + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_column_remove($table_name, $column_name, $inline = false) +	{ +		$statements = array(); + +		$statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN "' . $column_name . '"'; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_index_drop($table_name, $index_name) +	{ +		$statements = array(); + +		$statements[] = 'DROP INDEX ' . $table_name . '_' . $index_name; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_table_drop($table_name) +	{ +		$statements = array(); + +		if (!$this->sql_table_exists($table_name)) +		{ +			return $this->_sql_run_sql($statements); +		} + +		// the most basic operation, get rid of the table +		$statements[] = 'DROP TABLE ' . $table_name; + +		// PGSQL does not "tightly" bind sequences and tables, we must guess... +		$sql = "SELECT relname +			FROM pg_class +			WHERE relkind = 'S' +				AND relname = '{$table_name}_seq'"; +		$result = $this->db->sql_query($sql); + +		// We don't even care about storing the results. We already know the answer if we get rows back. +		if ($this->db->sql_fetchrow($result)) +		{ +			$statements[] =  "DROP SEQUENCE {$table_name}_seq;\n"; +		} +		$this->db->sql_freeresult($result); + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_create_primary_key($table_name, $column, $inline = false) +	{ +		$statements = array(); + +		$statements[] = 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY (' . implode(', ', $column) . ')'; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_create_unique_index($table_name, $index_name, $column) +	{ +		$statements = array(); + +		$this->check_index_name_length($table_name, $index_name); + +		$statements[] = 'CREATE UNIQUE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_create_index($table_name, $index_name, $column) +	{ +		$statements = array(); + +		$this->check_index_name_length($table_name, $index_name); + +		// remove index length +		$column = preg_replace('#:.*$#', '', $column); + +		$statements[] = 'CREATE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; + +		return $this->_sql_run_sql($statements); +	} + + +	/** +	 * {@inheritDoc} +	 */ +	function sql_list_index($table_name) +	{ +		$index_array = array(); + +		$sql = "SELECT ic.relname as index_name +			FROM pg_class bc, pg_class ic, pg_index i +			WHERE (bc.oid = i.indrelid) +				AND (ic.oid = i.indexrelid) +				AND (bc.relname = '" . $table_name . "') +				AND (i.indisunique != 't') +				AND (i.indisprimary != 't')"; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$row['index_name'] = $this->strip_table_name_from_index_name($table_name, $row['index_name']); + +			$index_array[] = $row['index_name']; +		} +		$this->db->sql_freeresult($result); + +		return array_map('strtolower', $index_array); +	} + +	/** +	 * {@inheritDoc} +	 */ +	function sql_column_change($table_name, $column_name, $column_data, $inline = false) +	{ +		$column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); +		$statements = array(); + +		$sql = 'ALTER TABLE ' . $table_name . ' '; + +		$sql_array = array(); +		$sql_array[] = 'ALTER COLUMN ' . $column_name . ' TYPE ' . $column_data['column_type']; + +		if (isset($column_data['null'])) +		{ +			if ($column_data['null'] == 'NOT NULL') +			{ +				$sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET NOT NULL'; +			} +			else if ($column_data['null'] == 'NULL') +			{ +				$sql_array[] = 'ALTER COLUMN ' . $column_name . ' DROP NOT NULL'; +			} +		} + +		if (isset($column_data['default'])) +		{ +			$sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; +		} + +		// we don't want to double up on constraints if we change different number data types +		if (isset($column_data['constraint'])) +		{ +			$constraint_sql = "SELECT consrc as constraint_data +				FROM pg_constraint, pg_class bc +				WHERE conrelid = bc.oid +					AND bc.relname = '{$table_name}' +					AND NOT EXISTS ( +						SELECT * +						FROM pg_constraint as c, pg_inherits as i +						WHERE i.inhrelid = pg_constraint.conrelid +							AND c.conname = pg_constraint.conname +							AND c.consrc = pg_constraint.consrc +							AND c.conrelid = i.inhparent +					)"; + +			$constraint_exists = false; + +			$result = $this->db->sql_query($constraint_sql); +			while ($row = $this->db->sql_fetchrow($result)) +			{ +				if (trim($row['constraint_data']) == trim($column_data['constraint'])) +				{ +					$constraint_exists = true; +					break; +				} +			} +			$this->db->sql_freeresult($result); + +			if (!$constraint_exists) +			{ +				$sql_array[] = 'ADD ' . $column_data['constraint']; +			} +		} + +		$sql .= implode(', ', $sql_array); + +		$statements[] = $sql; + +		return $this->_sql_run_sql($statements); +	} + +	/** +	* Get a list with existing indexes for the column +	* +	* @param string $table_name +	* @param string $column_name +	* @param bool $unique Should we get unique indexes or normal ones +	* @return array		Array with Index name => columns +	*/ +	public function get_existing_indexes($table_name, $column_name, $unique = false) +	{ +		// Not supported +		throw new \Exception('DBMS is not supported'); +	} +} diff --git a/phpBB/phpbb/db/tools.php b/phpBB/phpbb/db/tools/tools.php index 832a0c510c..37ac0d0468 100644 --- a/phpBB/phpbb/db/tools.php +++ b/phpBB/phpbb/db/tools/tools.php @@ -11,13 +11,13 @@  *  */ -namespace phpbb\db; +namespace phpbb\db\tools;  /**  * Database Tools for handling cross-db actions such as altering columns, etc.  * Currently not supported is returning SQL for creating tables.  */ -class tools +class tools implements tools_interface  {  	/**  	* Current sql layer @@ -36,22 +36,17 @@ class tools  	var $dbms_type_map = array();  	/** -	* Is the used MS SQL Server a SQL Server 2000? -	* @var bool -	*/ -	protected $is_sql_server_2000; - -	/**  	* Get the column types for every database we support  	*  	* @return array  	*/ -	public static function get_dbms_type_map() +	static public function get_dbms_type_map()  	{  		return array(  			'mysql_41'	=> array(  				'INT:'		=> 'int(%d)',  				'BINT'		=> 'bigint(20)', +				'ULINT'		=> 'INT(10) UNSIGNED',  				'UINT'		=> 'mediumint(8) UNSIGNED',  				'UINT:'		=> 'int(%d) UNSIGNED',  				'TINT:'		=> 'tinyint(%d)', @@ -82,6 +77,7 @@ class tools  			'mysql_40'	=> array(  				'INT:'		=> 'int(%d)',  				'BINT'		=> 'bigint(20)', +				'ULINT'		=> 'INT(10) UNSIGNED',  				'UINT'		=> 'mediumint(8) UNSIGNED',  				'UINT:'		=> 'int(%d) UNSIGNED',  				'TINT:'		=> 'tinyint(%d)', @@ -109,69 +105,10 @@ class tools  				'VARBINARY'	=> 'varbinary(255)',  			), -			'mssql'		=> array( -				'INT:'		=> '[int]', -				'BINT'		=> '[float]', -				'UINT'		=> '[int]', -				'UINT:'		=> '[int]', -				'TINT:'		=> '[int]', -				'USINT'		=> '[int]', -				'BOOL'		=> '[int]', -				'VCHAR'		=> '[varchar] (255)', -				'VCHAR:'	=> '[varchar] (%d)', -				'CHAR:'		=> '[char] (%d)', -				'XSTEXT'	=> '[varchar] (1000)', -				'STEXT'		=> '[varchar] (3000)', -				'TEXT'		=> '[varchar] (8000)', -				'MTEXT'		=> '[text]', -				'XSTEXT_UNI'=> '[varchar] (100)', -				'STEXT_UNI'	=> '[varchar] (255)', -				'TEXT_UNI'	=> '[varchar] (4000)', -				'MTEXT_UNI'	=> '[text]', -				'TIMESTAMP'	=> '[int]', -				'DECIMAL'	=> '[float]', -				'DECIMAL:'	=> '[float]', -				'PDECIMAL'	=> '[float]', -				'PDECIMAL:'	=> '[float]', -				'VCHAR_UNI'	=> '[varchar] (255)', -				'VCHAR_UNI:'=> '[varchar] (%d)', -				'VCHAR_CI'	=> '[varchar] (255)', -				'VARBINARY'	=> '[varchar] (255)', -			), - -			'mssqlnative'	=> array( -				'INT:'		=> '[int]', -				'BINT'		=> '[float]', -				'UINT'		=> '[int]', -				'UINT:'		=> '[int]', -				'TINT:'		=> '[int]', -				'USINT'		=> '[int]', -				'BOOL'		=> '[int]', -				'VCHAR'		=> '[varchar] (255)', -				'VCHAR:'	=> '[varchar] (%d)', -				'CHAR:'		=> '[char] (%d)', -				'XSTEXT'	=> '[varchar] (1000)', -				'STEXT'		=> '[varchar] (3000)', -				'TEXT'		=> '[varchar] (8000)', -				'MTEXT'		=> '[text]', -				'XSTEXT_UNI'=> '[varchar] (100)', -				'STEXT_UNI'	=> '[varchar] (255)', -				'TEXT_UNI'	=> '[varchar] (4000)', -				'MTEXT_UNI'	=> '[text]', -				'TIMESTAMP'	=> '[int]', -				'DECIMAL'	=> '[float]', -				'DECIMAL:'	=> '[float]', -				'PDECIMAL'	=> '[float]', -				'PDECIMAL:'	=> '[float]', -				'VCHAR_UNI'	=> '[varchar] (255)', -				'VCHAR_UNI:'=> '[varchar] (%d)', -				'VCHAR_CI'	=> '[varchar] (255)', -				'VARBINARY'	=> '[varchar] (255)', -			), -  			'oracle'	=> array(  				'INT:'		=> 'number(%d)',  				'BINT'		=> 'number(20)', +				'ULINT'		=> 'number(10)',  				'UINT'		=> 'number(8)',  				'UINT:'		=> 'number(%d)',  				'TINT:'		=> 'number(%d)', @@ -202,11 +139,12 @@ class tools  			'sqlite'	=> array(  				'INT:'		=> 'int(%d)',  				'BINT'		=> 'bigint(20)', -				'UINT'		=> 'INTEGER UNSIGNED', //'mediumint(8) UNSIGNED', +				'ULINT'		=> 'INTEGER UNSIGNED', // 'int(10) UNSIGNED', +				'UINT'		=> 'INTEGER UNSIGNED', // 'mediumint(8) UNSIGNED',  				'UINT:'		=> 'INTEGER UNSIGNED', // 'int(%d) UNSIGNED',  				'TINT:'		=> 'tinyint(%d)', -				'USINT'		=> 'INTEGER UNSIGNED', //'mediumint(4) UNSIGNED', -				'BOOL'		=> 'INTEGER UNSIGNED', //'tinyint(1) UNSIGNED', +				'USINT'		=> 'INTEGER UNSIGNED', // 'mediumint(4) UNSIGNED', +				'BOOL'		=> 'INTEGER UNSIGNED', // 'tinyint(1) UNSIGNED',  				'VCHAR'		=> 'varchar(255)',  				'VCHAR:'	=> 'varchar(%d)',  				'CHAR:'		=> 'char(%d)', @@ -218,7 +156,7 @@ class tools  				'STEXT_UNI'	=> 'text(65535)',  				'TEXT_UNI'	=> 'text(65535)',  				'MTEXT_UNI'	=> 'mediumtext(16777215)', -				'TIMESTAMP'	=> 'INTEGER UNSIGNED', //'int(11) UNSIGNED', +				'TIMESTAMP'	=> 'INTEGER UNSIGNED', // 'int(11) UNSIGNED',  				'DECIMAL'	=> 'decimal(5,2)',  				'DECIMAL:'	=> 'decimal(%d,2)',  				'PDECIMAL'	=> 'decimal(6,3)', @@ -232,6 +170,7 @@ class tools  			'sqlite3'	=> array(  				'INT:'		=> 'INT(%d)',  				'BINT'		=> 'BIGINT(20)', +				'ULINT'		=> 'INTEGER UNSIGNED',  				'UINT'		=> 'INTEGER UNSIGNED',  				'UINT:'		=> 'INTEGER UNSIGNED',  				'TINT:'		=> 'TINYINT(%d)', @@ -258,36 +197,6 @@ class tools  				'VCHAR_CI'	=> 'VARCHAR(255)',  				'VARBINARY'	=> 'BLOB',  			), - -			'postgres'	=> array( -				'INT:'		=> 'INT4', -				'BINT'		=> 'INT8', -				'UINT'		=> 'INT4', // unsigned -				'UINT:'		=> 'INT4', // unsigned -				'USINT'		=> 'INT2', // unsigned -				'BOOL'		=> 'INT2', // unsigned -				'TINT:'		=> 'INT2', -				'VCHAR'		=> 'varchar(255)', -				'VCHAR:'	=> 'varchar(%d)', -				'CHAR:'		=> 'char(%d)', -				'XSTEXT'	=> 'varchar(1000)', -				'STEXT'		=> 'varchar(3000)', -				'TEXT'		=> 'varchar(8000)', -				'MTEXT'		=> 'TEXT', -				'XSTEXT_UNI'=> 'varchar(100)', -				'STEXT_UNI'	=> 'varchar(255)', -				'TEXT_UNI'	=> 'varchar(4000)', -				'MTEXT_UNI'	=> 'TEXT', -				'TIMESTAMP'	=> 'INT4', // unsigned -				'DECIMAL'	=> 'decimal(5,2)', -				'DECIMAL:'	=> 'decimal(%d,2)', -				'PDECIMAL'	=> 'decimal(6,3)', -				'PDECIMAL:'	=> 'decimal(%d,3)', -				'VCHAR_UNI'	=> 'varchar(255)', -				'VCHAR_UNI:'=> 'varchar(%d)', -				'VCHAR_CI'	=> 'varchar_ci', -				'VARBINARY'	=> 'bytea', -			),  		);  	} @@ -295,13 +204,7 @@ class tools  	* A list of types being unsigned for better reference in some db's  	* @var array  	*/ -	var $unsigned_types = array('UINT', 'UINT:', 'USINT', 'BOOL', 'TIMESTAMP'); - -	/** -	* A list of supported DBMS. We change this class to support more DBMS, the DBMS itself only need to follow some rules. -	* @var array -	*/ -	var $supported_dbms = array('mssql', 'mssqlnative', 'mysql_40', 'mysql_41', 'oracle', 'postgres', 'sqlite', 'sqlite3'); +	var $unsigned_types = array('ULINT', 'UINT', 'UINT:', 'USINT', 'BOOL', 'TIMESTAMP');  	/**  	* This is set to true if user only wants to return the 'to-be-executed' SQL statement(s) (as an array). @@ -344,15 +247,6 @@ class tools  				$this->sql_layer = 'mysql_41';  			break; -			case 'mssql': -			case 'mssql_odbc': -				$this->sql_layer = 'mssql'; -			break; - -			case 'mssqlnative': -				$this->sql_layer = 'mssqlnative'; -			break; -  			default:  				$this->sql_layer = $this->db->get_sql_layer();  			break; @@ -371,10 +265,8 @@ class tools  	}  	/** -	* Gets a list of tables in the database. -	* -	* @return array		Array of table names  (all lower case) -	*/ +	 * {@inheritDoc} +	 */  	function sql_list_tables()  	{  		switch ($this->db->get_sql_layer()) @@ -398,19 +290,6 @@ class tools  						AND name <> "sqlite_sequence"';  			break; -			case 'mssql': -			case 'mssql_odbc': -			case 'mssqlnative': -				$sql = "SELECT name -					FROM sysobjects -					WHERE type='U'"; -			break; - -			case 'postgres': -				$sql = 'SELECT relname -					FROM pg_stat_user_tables'; -			break; -  			case 'oracle':  				$sql = 'SELECT table_name  					FROM USER_TABLES'; @@ -431,12 +310,8 @@ class tools  	}  	/** -	* Check if table exists -	* -	* -	* @param string	$table_name	The table name to check for -	* @return bool true if table exists, else false -	*/ +	 * {@inheritDoc} +	 */  	function sql_table_exists($table_name)  	{  		$this->db->sql_return_on_error(true); @@ -453,12 +328,8 @@ class tools  	}  	/** -	* Create SQL Table -	* -	* @param string	$table_name	The table name to create -	* @param array	$table_data	Array containing table data. -	* @return array	Statements if $return_statements is true. -	*/ +	 * {@inheritDoc} +	 */  	function sql_create_table($table_name, $table_data)  	{  		// holds the DDL for a column @@ -479,26 +350,7 @@ class tools  		$create_sequence = false;  		// Begin table sql statement -		switch ($this->sql_layer) -		{ -			case 'mssql': -			case 'mssqlnative': -				$table_sql = 'CREATE TABLE [' . $table_name . '] (' . "\n"; -			break; - -			default: -				$table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n"; -			break; -		} - -		if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') -		{ -			if (!isset($table_data['PRIMARY_KEY'])) -			{ -				$table_data['COLUMNS']['mssqlindex'] = array('UINT', null, 'auto_increment'); -				$table_data['PRIMARY_KEY'] = 'mssqlindex'; -			} -		} +		$table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n";  		// Iterate through the columns to create a table  		foreach ($table_data['COLUMNS'] as $column_name => $column_data) @@ -512,17 +364,7 @@ class tools  			}  			// here we add the definition of the new column to the list of columns -			switch ($this->sql_layer) -			{ -				case 'mssql': -				case 'mssqlnative': -					$columns[] = "\t [{$column_name}] " . $prepared_column['column_type_sql_default']; -				break; - -				default: -					$columns[] = "\t {$column_name} " . $prepared_column['column_type_sql']; -				break; -			} +			$columns[] = "\t {$column_name} " . $prepared_column['column_type_sql'];  			// see if we have found a primary key set due to a column definition if we have found it, we can stop looking  			if (!$primary_key_gen) @@ -540,16 +382,6 @@ class tools  		// this makes up all the columns in the create table statement  		$table_sql .= implode(",\n", $columns); -		// Close the table for two DBMS and add to the statements -		switch ($this->sql_layer) -		{ -			case 'mssql': -			case 'mssqlnative': -				$table_sql .= "\n);"; -				$statements[] = $table_sql; -			break; -		} -  		// we have yet to create a primary key for this table,  		// this means that we can add the one we really wanted instead  		if (!$primary_key_gen) @@ -566,27 +398,11 @@ class tools  				{  					case 'mysql_40':  					case 'mysql_41': -					case 'postgres':  					case 'sqlite':  					case 'sqlite3':  						$table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')';  					break; -					case 'mssql': -					case 'mssqlnative': -						// We need the data here -						$old_return_statements = $this->return_statements; -						$this->return_statements = true; - -						$primary_key_stmts = $this->sql_create_primary_key($table_name, $table_data['PRIMARY_KEY']); -						foreach ($primary_key_stmts as $pk_stmt) -						{ -							$statements[] = $pk_stmt; -						} - -						$this->return_statements = $old_return_statements; -					break; -  					case 'oracle':  						$table_sql .= ",\n\t CONSTRAINT pk_{$table_name} PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')';  					break; @@ -610,17 +426,6 @@ class tools  				$statements[] = $table_sql;  			break; -			case 'postgres': -				// do we need to add a sequence for auto incrementing columns? -				if ($create_sequence) -				{ -					$statements[] = "CREATE SEQUENCE {$table_name}_seq;"; -				} - -				$table_sql .= "\n);"; -				$statements[] = $table_sql; -			break; -  			case 'oracle':  				$table_sql .= "\n)";  				$statements[] = $table_sql; @@ -679,27 +484,8 @@ class tools  	}  	/** -	* Handle passed database update array. -	* Expected structure... -	* Key being one of the following -	*	drop_tables: Drop tables -	*	add_tables: Add tables -	*	change_columns: Column changes (only type, not name) -	*	add_columns: Add columns to a table -	*	drop_keys: Dropping keys -	*	drop_columns: Removing/Dropping columns -	*	add_primary_keys: adding primary keys -	*	add_unique_index: adding an unique index -	*	add_index: adding an index (can be column:index_size if you need to provide size) -	* -	* The values are in this format: -	*		{TABLE NAME}		=> array( -	*			{COLUMN NAME}		=> array({COLUMN TYPE}, {DEFAULT VALUE}, {OPTIONAL VARIABLES}), -	*			{KEY/INDEX NAME}	=> array({COLUMN NAMES}), -	*		) -	* -	* For more information have a look at /develop/create_schema_files.php (only available through SVN) -	*/ +	 * {@inheritDoc} +	 */  	function perform_schema_changes($schema_changes)  	{  		if (empty($schema_changes)) @@ -1079,13 +865,9 @@ class tools  	}  	/** -	* Gets a list of columns of a table. -	* -	* @param string $table		Table name -	* -	* @return array				Array of column names (all lower case) -	*/ -	function sql_list_columns($table) +	 * {@inheritDoc} +	 */ +	function sql_list_columns($table_name)  	{  		$columns = array(); @@ -1093,33 +875,13 @@ class tools  		{  			case 'mysql_40':  			case 'mysql_41': -				$sql = "SHOW COLUMNS FROM $table"; -			break; - -			// PostgreSQL has a way of doing this in a much simpler way but would -			// not allow us to support all versions of PostgreSQL -			case 'postgres': -				$sql = "SELECT a.attname -					FROM pg_class c, pg_attribute a -					WHERE c.relname = '{$table}' -						AND a.attnum > 0 -						AND a.attrelid = c.oid"; -			break; - -			// same deal with PostgreSQL, we must perform more complex operations than -			// we technically could -			case 'mssql': -			case 'mssqlnative': -				$sql = "SELECT c.name -					FROM syscolumns c -					LEFT JOIN sysobjects o ON c.id = o.id -					WHERE o.name = '{$table}'"; +				$sql = "SHOW COLUMNS FROM $table_name";  			break;  			case 'oracle':  				$sql = "SELECT column_name  					FROM user_tab_columns -					WHERE LOWER(table_name) = '" . strtolower($table) . "'"; +					WHERE LOWER(table_name) = '" . strtolower($table_name) . "'";  			break;  			case 'sqlite': @@ -1127,7 +889,7 @@ class tools  				$sql = "SELECT sql  					FROM sqlite_master  					WHERE type = 'table' -						AND name = '{$table}'"; +						AND name = '{$table_name}'";  				$result = $this->db->sql_query($sql); @@ -1173,64 +935,22 @@ class tools  	}  	/** -	* Check whether a specified column exist in a table -	* -	* @param string	$table			Table to check -	* @param string	$column_name	Column to check -	* -	* @return bool		True if column exists, false otherwise -	*/ -	function sql_column_exists($table, $column_name) +	 * {@inheritDoc} +	 */ +	function sql_column_exists($table_name, $column_name)  	{ -		$columns = $this->sql_list_columns($table); +		$columns = $this->sql_list_columns($table_name);  		return isset($columns[$column_name]);  	}  	/** -	* Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes. -	* -	* @param string	$table_name		Table to check the index at -	* @param string	$index_name		The index name to check -	* -	* @return bool True if index exists, else false -	*/ +	 * {@inheritDoc} +	 */  	function sql_index_exists($table_name, $index_name)  	{ -		if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') -		{ -			$sql = "EXEC sp_statistics '$table_name'"; -			$result = $this->db->sql_query($sql); - -			while ($row = $this->db->sql_fetchrow($result)) -			{ -				if ($row['TYPE'] == 3) -				{ -					if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) -					{ -						$this->db->sql_freeresult($result); -						return true; -					} -				} -			} -			$this->db->sql_freeresult($result); - -			return false; -		} -  		switch ($this->sql_layer)  		{ -			case 'postgres': -				$sql = "SELECT ic.relname as index_name -					FROM pg_class bc, pg_class ic, pg_index i -					WHERE (bc.oid = i.indrelid) -						AND (ic.oid = i.indexrelid) -						AND (bc.relname = '" . $table_name . "') -						AND (i.indisunique != 't') -						AND (i.indisprimary != 't')"; -				$col = 'index_name'; -			break; -  			case 'mysql_40':  			case 'mysql_41':  				$sql = 'SHOW KEYS @@ -1266,7 +986,6 @@ class tools  			switch ($this->sql_layer)  			{  				case 'oracle': -				case 'postgres':  				case 'sqlite':  				case 'sqlite3':  					$row[$col] = substr($row[$col], strlen($table_name) + 1); @@ -1285,48 +1004,12 @@ class tools  	}  	/** -	* Check if a specified index exists in table. Does not return PRIMARY KEY indexes. -	* -	* @param string	$table_name		Table to check the index at -	* @param string	$index_name		The index name to check -	* -	* @return bool True if index exists, else false -	*/ +	 * {@inheritDoc} +	 */  	function sql_unique_index_exists($table_name, $index_name)  	{ -		if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') -		{ -			$sql = "EXEC sp_statistics '$table_name'"; -			$result = $this->db->sql_query($sql); - -			while ($row = $this->db->sql_fetchrow($result)) -			{ -				// Usually NON_UNIQUE is the column we want to check, but we allow for both -				if ($row['TYPE'] == 3) -				{ -					if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) -					{ -						$this->db->sql_freeresult($result); -						return true; -					} -				} -			} -			$this->db->sql_freeresult($result); -			return false; -		} -  		switch ($this->sql_layer)  		{ -			case 'postgres': -				$sql = "SELECT ic.relname as index_name, i.indisunique -					FROM pg_class bc, pg_class ic, pg_index i -					WHERE (bc.oid = i.indrelid) -						AND (ic.oid = i.indexrelid) -						AND (bc.relname = '" . $table_name . "') -						AND (i.indisprimary != 't')"; -				$col = 'index_name'; -			break; -  			case 'mysql_40':  			case 'mysql_41':  				$sql = 'SHOW KEYS @@ -1363,11 +1046,6 @@ class tools  				continue;  			} -			if ($this->sql_layer == 'postgres' && $row['indisunique'] != 't') -			{ -				continue; -			} -  			// These DBMS prefix index name with the table name  			switch ($this->sql_layer)  			{ @@ -1383,7 +1061,6 @@ class tools  					}  				break; -				case 'postgres':  				case 'sqlite':  				case 'sqlite3':  					$row[$col] = substr($row[$col], strlen($table_name) + 1); @@ -1444,7 +1121,7 @@ class tools  		}  		// Get type -		list($column_type, $orig_column_type) = $this->get_column_type($column_data[0]); +		list($column_type) = $this->get_column_type($column_data[0]);  		// Adjust default value if db-dependent specified  		if (is_array($column_data[1])) @@ -1458,50 +1135,6 @@ class tools  		switch ($this->sql_layer)  		{ -			case 'mssql': -			case 'mssqlnative': -				$sql .= " {$column_type} "; -				$sql_default = " {$column_type} "; - -				// For adding columns we need the default definition -				if (!is_null($column_data[1])) -				{ -					// For hexadecimal values do not use single quotes -					if (strpos($column_data[1], '0x') === 0) -					{ -						$return_array['default'] = 'DEFAULT (' . $column_data[1] . ') '; -						$sql_default .= $return_array['default']; -					} -					else -					{ -						$return_array['default'] = 'DEFAULT (' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ') '; -						$sql_default .= $return_array['default']; -					} -				} - -				if (isset($column_data[2]) && $column_data[2] == 'auto_increment') -				{ -//					$sql .= 'IDENTITY (1, 1) '; -					$sql_default .= 'IDENTITY (1, 1) '; -				} - -				$return_array['textimage'] = $column_type === '[text]'; - -				if (!is_null($column_data[1]) || (isset($column_data[2]) && $column_data[2] == 'auto_increment')) -				{ -					$sql .= 'NOT NULL'; -					$sql_default .= 'NOT NULL'; -				} -				else -				{ -					$sql .= 'NULL'; -					$sql_default .= 'NULL'; -				} - -				$return_array['column_type_sql_default'] = $sql_default; - -			break; -  			case 'mysql_40':  			case 'mysql_41':  				$sql .= " {$column_type} "; @@ -1560,51 +1193,6 @@ class tools  			break; -			case 'postgres': -				$return_array['column_type'] = $column_type; - -				$sql .= " {$column_type} "; - -				$return_array['auto_increment'] = false; -				if (isset($column_data[2]) && $column_data[2] == 'auto_increment') -				{ -					$default_val = "nextval('{$table_name}_seq')"; -					$return_array['auto_increment'] = true; -				} -				else if (!is_null($column_data[1])) -				{ -					$default_val = "'" . $column_data[1] . "'"; -					$return_array['null'] = 'NOT NULL'; -					$sql .= 'NOT NULL '; -				} -				else -				{ -					// Integers need to have 0 instead of empty string as default -					if (strpos($column_type, 'INT') === 0) -					{ -						$default_val = '0'; -					} -					else -					{ -						$default_val = "'" . $column_data[1] . "'"; -					} -					$return_array['null'] = 'NULL'; -					$sql .= 'NULL '; -				} - -				$return_array['default'] = $default_val; - -				$sql .= "DEFAULT {$default_val}"; - -				// Unsigned? Then add a CHECK contraint -				if (in_array($orig_column_type, $this->unsigned_types)) -				{ -					$return_array['constraint'] = "CHECK ({$column_name} >= 0)"; -					$sql .= " CHECK ({$column_name} >= 0)"; -				} - -			break; -  			case 'sqlite':  			case 'sqlite3':  				$return_array['primary_key_set'] = false; @@ -1646,6 +1234,7 @@ class tools  	*/  	function get_column_type($column_map_type)  	{ +		$column_type = '';  		if (strpos($column_map_type, ':') !== false)  		{  			list($orig_column_type, $column_length) = explode(':', $column_map_type); @@ -1697,8 +1286,8 @@ class tools  	}  	/** -	* Add new column -	*/ +	 * {@inheritDoc} +	 */  	function sql_column_add($table_name, $column_name, $column_data, $inline = false)  	{  		$column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); @@ -1706,12 +1295,6 @@ class tools  		switch ($this->sql_layer)  		{ -			case 'mssql': -			case 'mssqlnative': -				// Does not support AFTER, only through temporary table -				$statements[] = 'ALTER TABLE [' . $table_name . '] ADD [' . $column_name . '] ' . $column_data['column_type_sql_default']; -			break; -  			case 'mysql_40':  			case 'mysql_41':  				$after = (!empty($column_data['after'])) ? ' AFTER ' . $column_data['after'] : ''; @@ -1723,33 +1306,6 @@ class tools  				$statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' ' . $column_data['column_type_sql'];  			break; -			case 'postgres': -				// Does not support AFTER, only through temporary table -				if (version_compare($this->db->sql_server_info(true), '8.0', '>=')) -				{ -					$statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type_sql']; -				} -				else -				{ -					// old versions cannot add columns with default and null information -					$statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type'] . ' ' . $column_data['constraint']; - -					if (isset($column_data['null'])) -					{ -						if ($column_data['null'] == 'NOT NULL') -						{ -							$statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET NOT NULL'; -						} -					} - -					if (isset($column_data['default'])) -					{ -						$statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; -					} -				} - -			break; -  			case 'sqlite':  				if ($inline && $this->return_statements)  				{ @@ -1815,59 +1371,14 @@ class tools  	}  	/** -	* Drop column -	*/ +	 * {@inheritDoc} +	 */  	function sql_column_remove($table_name, $column_name, $inline = false)  	{  		$statements = array();  		switch ($this->sql_layer)  		{ -			case 'mssql': -			case 'mssqlnative': -				// We need the data here -				$old_return_statements = $this->return_statements; -				$this->return_statements = true; - -				$indexes = $this->get_existing_indexes($table_name, $column_name); -				$indexes = array_merge($indexes, $this->get_existing_indexes($table_name, $column_name, true)); - -				// Drop any indexes -				$recreate_indexes = array(); -				if (!empty($indexes)) -				{ -					foreach ($indexes as $index_name => $index_data) -					{ -						$result = $this->sql_index_drop($table_name, $index_name); -						$statements = array_merge($statements, $result); -						if (sizeof($index_data) > 1) -						{ -							// Remove this column from the index and recreate it -							$recreate_indexes[$index_name] = array_diff($index_data, array($column_name)); -						} -					} -				} - -				// Drop default value constraint -				$result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name); -				$statements = array_merge($statements, $result); - -				// Remove the column -				$statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']'; - -				if (!empty($recreate_indexes)) -				{ -					// Recreate indexes after we removed the column -					foreach ($recreate_indexes as $index_name => $index_data) -					{ -						$result = $this->sql_create_index($table_name, $index_name, $index_data); -						$statements = array_merge($statements, $result); -					} -				} - -				$this->return_statements = $old_return_statements; -			break; -  			case 'mysql_40':  			case 'mysql_41':  				$statements[] = 'ALTER TABLE `' . $table_name . '` DROP COLUMN `' . $column_name . '`'; @@ -1877,10 +1388,6 @@ class tools  				$statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN ' . $column_name;  			break; -			case 'postgres': -				$statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN "' . $column_name . '"'; -			break; -  			case 'sqlite':  			case 'sqlite3': @@ -1944,26 +1451,20 @@ class tools  	}  	/** -	* Drop Index -	*/ +	 * {@inheritDoc} +	 */  	function sql_index_drop($table_name, $index_name)  	{  		$statements = array();  		switch ($this->sql_layer)  		{ -			case 'mssql': -			case 'mssqlnative': -				$statements[] = 'DROP INDEX ' . $table_name . '.' . $index_name; -			break; -  			case 'mysql_40':  			case 'mysql_41':  				$statements[] = 'DROP INDEX ' . $index_name . ' ON ' . $table_name;  			break;  			case 'oracle': -			case 'postgres':  			case 'sqlite':  			case 'sqlite3':  				$statements[] = 'DROP INDEX ' . $table_name . '_' . $index_name; @@ -1974,8 +1475,8 @@ class tools  	}  	/** -	* Drop Table -	*/ +	 * {@inheritDoc} +	 */  	function sql_table_drop($table_name)  	{  		$statements = array(); @@ -2005,52 +1506,25 @@ class tools  				}  				$this->db->sql_freeresult($result);  			break; - -			case 'postgres': -				// PGSQL does not "tightly" bind sequences and tables, we must guess... -				$sql = "SELECT relname -					FROM pg_class -					WHERE relkind = 'S' -						AND relname = '{$table_name}_seq'"; -				$result = $this->db->sql_query($sql); - -				// We don't even care about storing the results. We already know the answer if we get rows back. -				if ($this->db->sql_fetchrow($result)) -				{ -					$statements[] =  "DROP SEQUENCE {$table_name}_seq;\n"; -				} -				$this->db->sql_freeresult($result); -			break;  		}  		return $this->_sql_run_sql($statements);  	}  	/** -	* Add primary key -	*/ +	 * {@inheritDoc} +	 */  	function sql_create_primary_key($table_name, $column, $inline = false)  	{  		$statements = array();  		switch ($this->sql_layer)  		{ -			case 'postgres':  			case 'mysql_40':  			case 'mysql_41':  				$statements[] = 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY (' . implode(', ', $column) . ')';  			break; -			case 'mssql': -			case 'mssqlnative': -				$sql = "ALTER TABLE [{$table_name}] WITH NOCHECK ADD "; -				$sql .= "CONSTRAINT [PK_{$table_name}] PRIMARY KEY  CLUSTERED ("; -				$sql .= '[' . implode("],\n\t\t[", $column) . ']'; -				$sql .= ')'; - -				$statements[] = $sql; -			break; -  			case 'oracle':  				$statements[] = 'ALTER TABLE ' . $table_name . ' add CONSTRAINT pk_' . $table_name . ' PRIMARY KEY (' . implode(', ', $column) . ')';  			break; @@ -2111,22 +1585,16 @@ class tools  	}  	/** -	* Add unique index -	*/ +	 * {@inheritDoc} +	 */  	function sql_create_unique_index($table_name, $index_name, $column)  	{  		$statements = array(); -		$table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) -		if (strlen($table_name . '_' . $index_name) - strlen($table_prefix) > 24) -		{ -			$max_length = strlen($table_prefix) + 24; -			trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); -		} +		$this->check_index_name_length($table_name, $index_name);  		switch ($this->sql_layer)  		{ -			case 'postgres':  			case 'oracle':  			case 'sqlite':  			case 'sqlite3': @@ -2137,29 +1605,19 @@ class tools  			case 'mysql_41':  				$statements[] = 'ALTER TABLE ' . $table_name . ' ADD UNIQUE INDEX ' . $index_name . '(' . implode(', ', $column) . ')';  			break; - -			case 'mssql': -			case 'mssqlnative': -				$statements[] = 'CREATE UNIQUE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])'; -			break;  		}  		return $this->_sql_run_sql($statements);  	}  	/** -	* Add index -	*/ +	 * {@inheritDoc} +	 */  	function sql_create_index($table_name, $index_name, $column)  	{  		$statements = array(); -		$table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) -		if (strlen($table_name . $index_name) - strlen($table_prefix) > 24) -		{ -			$max_length = strlen($table_prefix) + 24; -			trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); -		} +		$this->check_index_name_length($table_name, $index_name);  		// remove index length unless MySQL4  		if ('mysql_40' != $this->sql_layer) @@ -2169,7 +1627,6 @@ class tools  		switch ($this->sql_layer)  		{ -			case 'postgres':  			case 'oracle':  			case 'sqlite':  			case 'sqlite3': @@ -2190,99 +1647,79 @@ class tools  			case 'mysql_41':  				$statements[] = 'ALTER TABLE ' . $table_name . ' ADD INDEX ' . $index_name . ' (' . implode(', ', $column) . ')';  			break; - -			case 'mssql': -			case 'mssqlnative': -				$statements[] = 'CREATE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])'; -			break;  		}  		return $this->_sql_run_sql($statements);  	}  	/** -	* List all of the indices that belong to a table, -	* does not count: -	* * UNIQUE indices -	* * PRIMARY keys -	*/ +	 * Check whether the index name is too long +	 * +	 * @param string $table_name +	 * @param string $index_name +	 */ +	protected function check_index_name_length($table_name, $index_name) +	{ +		$table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) +		if (strlen($table_name . $index_name) - strlen($table_prefix) > 24) +		{ +			$max_length = strlen($table_prefix) + 24; +			trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR); +		} +	} + +	/** +	 * {@inheritDoc} +	 */  	function sql_list_index($table_name)  	{  		$index_array = array(); -		if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative') -		{ -			$sql = "EXEC sp_statistics '$table_name'"; -			$result = $this->db->sql_query($sql); -			while ($row = $this->db->sql_fetchrow($result)) -			{ -				if ($row['TYPE'] == 3) -				{ -					$index_array[] = $row['INDEX_NAME']; -				} -			} -			$this->db->sql_freeresult($result); -		} -		else +		switch ($this->sql_layer)  		{ -			switch ($this->sql_layer) -			{ -				case 'postgres': -					$sql = "SELECT ic.relname as index_name -						FROM pg_class bc, pg_class ic, pg_index i -						WHERE (bc.oid = i.indrelid) -							AND (ic.oid = i.indexrelid) -							AND (bc.relname = '" . $table_name . "') -							AND (i.indisunique != 't') -							AND (i.indisprimary != 't')"; -					$col = 'index_name'; +			case 'mysql_40': +			case 'mysql_41': +				$sql = 'SHOW KEYS +					FROM ' . $table_name; +				$col = 'Key_name';  				break; -				case 'mysql_40': -				case 'mysql_41': -					$sql = 'SHOW KEYS -						FROM ' . $table_name; -					$col = 'Key_name'; +			case 'oracle': +				$sql = "SELECT index_name +					FROM user_indexes +					WHERE table_name = '" . strtoupper($table_name) . "' +						AND generated = 'N' +						AND uniqueness = 'NONUNIQUE'"; +				$col = 'index_name';  				break; -				case 'oracle': -					$sql = "SELECT index_name -						FROM user_indexes -						WHERE table_name = '" . strtoupper($table_name) . "' -							AND generated = 'N' -							AND uniqueness = 'NONUNIQUE'"; -					$col = 'index_name'; +			case 'sqlite': +			case 'sqlite3': +				$sql = "PRAGMA index_info('" . $table_name . "');"; +				$col = 'name';  				break; +		} -				case 'sqlite': -				case 'sqlite3': -					$sql = "PRAGMA index_info('" . $table_name . "');"; -					$col = 'name'; -				break; +		$result = $this->db->sql_query($sql); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique']) +			{ +				continue;  			} -			$result = $this->db->sql_query($sql); -			while ($row = $this->db->sql_fetchrow($result)) +			switch ($this->sql_layer)  			{ -				if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique']) -				{ -					continue; -				} - -				switch ($this->sql_layer) -				{ -					case 'oracle': -					case 'postgres': -					case 'sqlite': -					case 'sqlite3': -						$row[$col] = substr($row[$col], strlen($table_name) + 1); +				case 'oracle': +				case 'sqlite': +				case 'sqlite3': +					$row[$col] = substr($row[$col], strlen($table_name) + 1);  					break; -				} - -				$index_array[] = $row[$col];  			} -			$this->db->sql_freeresult($result); + +			$index_array[] = $row[$col];  		} +		$this->db->sql_freeresult($result);  		return array_map('strtolower', $index_array);  	} @@ -2300,8 +1737,8 @@ class tools  	}  	/** -	* Change column type (not name!) -	*/ +	 * {@inheritDoc} +	 */  	function sql_column_change($table_name, $column_name, $column_data, $inline = false)  	{  		$original_column_data = $column_data; @@ -2310,62 +1747,6 @@ class tools  		switch ($this->sql_layer)  		{ -			case 'mssql': -			case 'mssqlnative': -				// We need the data here -				$old_return_statements = $this->return_statements; -				$this->return_statements = true; - -				$indexes = $this->get_existing_indexes($table_name, $column_name); -				$unique_indexes = $this->get_existing_indexes($table_name, $column_name, true); - -				// Drop any indexes -				if (!empty($indexes) || !empty($unique_indexes)) -				{ -					$drop_indexes = array_merge(array_keys($indexes), array_keys($unique_indexes)); -					foreach ($drop_indexes as $index_name) -					{ -						$result = $this->sql_index_drop($table_name, $index_name); -						$statements = array_merge($statements, $result); -					} -				} - -				// Drop default value constraint -				$result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name); -				$statements = array_merge($statements, $result); - -				// Change the column -				$statements[] = 'ALTER TABLE [' . $table_name . '] ALTER COLUMN [' . $column_name . '] ' . $column_data['column_type_sql']; - -				if (!empty($column_data['default'])) -				{ -					// Add new default value constraint -					$statements[] = 'ALTER TABLE [' . $table_name . '] ADD CONSTRAINT [DF_' . $table_name . '_' . $column_name . '_1] ' . $column_data['default'] . ' FOR [' . $column_name . ']'; -				} - -				if (!empty($indexes)) -				{ -					// Recreate indexes after we changed the column -					foreach ($indexes as $index_name => $index_data) -					{ -						$result = $this->sql_create_index($table_name, $index_name, $index_data); -						$statements = array_merge($statements, $result); -					} -				} - -				if (!empty($unique_indexes)) -				{ -					// Recreate unique indexes after we changed the column -					foreach ($unique_indexes as $index_name => $index_data) -					{ -						$result = $this->sql_create_unique_index($table_name, $index_name, $index_data); -						$statements = array_merge($statements, $result); -					} -				} - -				$this->return_statements = $old_return_statements; -			break; -  			case 'mysql_40':  			case 'mysql_41':  				$statements[] = 'ALTER TABLE `' . $table_name . '` CHANGE `' . $column_name . '` `' . $column_name . '` ' . $column_data['column_type_sql']; @@ -2437,69 +1818,6 @@ class tools  				$this->return_statements = $old_return_statements;  			break; -			case 'postgres': -				$sql = 'ALTER TABLE ' . $table_name . ' '; - -				$sql_array = array(); -				$sql_array[] = 'ALTER COLUMN ' . $column_name . ' TYPE ' . $column_data['column_type']; - -				if (isset($column_data['null'])) -				{ -					if ($column_data['null'] == 'NOT NULL') -					{ -						$sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET NOT NULL'; -					} -					else if ($column_data['null'] == 'NULL') -					{ -						$sql_array[] = 'ALTER COLUMN ' . $column_name . ' DROP NOT NULL'; -					} -				} - -				if (isset($column_data['default'])) -				{ -					$sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; -				} - -				// we don't want to double up on constraints if we change different number data types -				if (isset($column_data['constraint'])) -				{ -					$constraint_sql = "SELECT consrc as constraint_data -								FROM pg_constraint, pg_class bc -								WHERE conrelid = bc.oid -									AND bc.relname = '{$table_name}' -									AND NOT EXISTS ( -										SELECT * -											FROM pg_constraint as c, pg_inherits as i -											WHERE i.inhrelid = pg_constraint.conrelid -												AND c.conname = pg_constraint.conname -												AND c.consrc = pg_constraint.consrc -												AND c.conrelid = i.inhparent -									)"; - -					$constraint_exists = false; - -					$result = $this->db->sql_query($constraint_sql); -					while ($row = $this->db->sql_fetchrow($result)) -					{ -						if (trim($row['constraint_data']) == trim($column_data['constraint'])) -						{ -							$constraint_exists = true; -							break; -						} -					} -					$this->db->sql_freeresult($result); - -					if (!$constraint_exists) -					{ -						$sql_array[] = 'ADD ' . $column_data['constraint']; -					} -				} - -				$sql .= implode(', ', $sql_array); - -				$statements[] = $sql; -			break; -  			case 'sqlite':  			case 'sqlite3': @@ -2568,52 +1886,6 @@ class tools  	}  	/** -	* Get queries to drop the default constraints of a column -	* -	* We need to drop the default constraints of a column, -	* before being able to change their type or deleting them. -	* -	* @param string $table_name -	* @param string $column_name -	* @return array		Array with SQL statements -	*/ -	protected function mssql_get_drop_default_constraints_queries($table_name, $column_name) -	{ -		$statements = array(); -		if ($this->mssql_is_sql_server_2000()) -		{ -			// http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx -			// Deprecated in SQL Server 2005 -			$sql = "SELECT so.name AS def_name -				FROM sysobjects so -				JOIN sysconstraints sc ON so.id = sc.constid -				WHERE object_name(so.parent_obj) = '{$table_name}' -					AND so.xtype = 'D' -					AND sc.colid = (SELECT colid FROM syscolumns -						WHERE id = object_id('{$table_name}') -							AND name = '{$column_name}')"; -		} -		else -		{ -			$sql = "SELECT dobj.name AS def_name -				FROM sys.columns col -					LEFT OUTER JOIN sys.objects dobj ON (dobj.object_id = col.default_object_id AND dobj.type = 'D') -				WHERE col.object_id = object_id('{$table_name}') -					AND col.name = '{$column_name}' -					AND dobj.name IS NOT NULL"; -		} - -		$result = $this->db->sql_query($sql); -		while ($row = $this->db->sql_fetchrow($result)) -		{ -			$statements[] = 'ALTER TABLE [' . $table_name . '] DROP CONSTRAINT [' . $row['def_name'] . ']'; -		} -		$this->db->sql_freeresult($result); - -		return $statements; -	} - -	/**  	* Get a list with existing indexes for the column  	*  	* @param string $table_name @@ -2627,7 +1899,6 @@ class tools  		{  			case 'mysql_40':  			case 'mysql_41': -			case 'postgres':  			case 'sqlite':  			case 'sqlite3':  				// Not supported @@ -2640,40 +1911,6 @@ class tools  		switch ($this->sql_layer)  		{ -			case 'mssql': -			case 'mssqlnative': -				if ($this->mssql_is_sql_server_2000()) -				{ -					// http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx -					// Deprecated in SQL Server 2005 -					$sql = "SELECT DISTINCT ix.name AS phpbb_index_name -					FROM sysindexes ix -					INNER JOIN sysindexkeys ixc -						ON ixc.id = ix.id -							AND ixc.indid = ix.indid -					INNER JOIN syscolumns cols -						ON cols.colid = ixc.colid -							AND cols.id = ix.id -					WHERE ix.id = object_id('{$table_name}') -						AND cols.name = '{$column_name}' -						AND INDEXPROPERTY(ix.id, ix.name, 'IsUnique') = " . ($unique ? '1' : '0'); -				} -				else -				{ -					$sql = "SELECT DISTINCT ix.name AS phpbb_index_name -					FROM sys.indexes ix -					INNER JOIN sys.index_columns ixc -						ON ixc.object_id = ix.object_id -							AND ixc.index_id = ix.index_id -					INNER JOIN sys.columns cols -						ON cols.column_id = ixc.column_id -							AND cols.object_id = ix.object_id -					WHERE ix.object_id = object_id('{$table_name}') -						AND cols.name = '{$column_name}' -						AND ix.is_unique = " . ($unique ? '1' : '0'); -				} -			break; -  			case 'oracle':  				$sql = "SELECT ix.index_name  AS phpbb_index_name, ix.uniqueness AS is_unique  					FROM all_ind_columns ixc, all_indexes ix @@ -2700,36 +1937,6 @@ class tools  		switch ($this->sql_layer)  		{ -			case 'mssql': -			case 'mssqlnative': -				if ($this->mssql_is_sql_server_2000()) -				{ -					$sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name -						FROM sysindexes ix -						INNER JOIN sysindexkeys ixc -							ON ixc.id = ix.id -								AND ixc.indid = ix.indid -						INNER JOIN syscolumns cols -							ON cols.colid = ixc.colid -								AND cols.id = ix.id -						WHERE ix.id = object_id('{$table_name}') -							AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes)); -				} -				else -				{ -					$sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name -						FROM sys.indexes ix -						INNER JOIN sys.index_columns ixc -							ON ixc.object_id = ix.object_id -								AND ixc.index_id = ix.index_id -						INNER JOIN sys.columns cols -							ON cols.column_id = ixc.column_id -								AND cols.object_id = ix.object_id -						WHERE ix.object_id = object_id('{$table_name}') -							AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes)); -				} -			break; -  			case 'oracle':  				$sql = "SELECT index_name AS phpbb_index_name, column_name AS phpbb_column_name  					FROM all_ind_columns @@ -2749,25 +1956,6 @@ class tools  	}  	/** -	* Is the used MS SQL Server a SQL Server 2000? -	* -	* @return bool -	*/ -	protected function mssql_is_sql_server_2000() -	{ -		if ($this->is_sql_server_2000 === null) -		{ -			$sql = "SELECT CAST(SERVERPROPERTY('productversion') AS VARCHAR(25)) AS mssql_version"; -			$result = $this->db->sql_query($sql); -			$properties = $this->db->sql_fetchrow($result); -			$this->db->sql_freeresult($result); -			$this->is_sql_server_2000 = $properties['mssql_version'][0] == '8'; -		} - -		return $this->is_sql_server_2000; -	} - -	/**  	* Returns the Queries which are required to recreate a table including indexes  	*  	* @param string $table_name diff --git a/phpBB/phpbb/db/tools/tools_interface.php b/phpBB/phpbb/db/tools/tools_interface.php new file mode 100644 index 0000000000..f153f73a54 --- /dev/null +++ b/phpBB/phpbb/db/tools/tools_interface.php @@ -0,0 +1,202 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\tools; + +/** + * Interface for a Database Tools for handling cross-db actions such as altering columns, etc. + */ +interface tools_interface +{ +	/** +	 * Handle passed database update array. +	 * Expected structure... +	 * Key being one of the following +	 *	drop_tables: Drop tables +	 *	add_tables: Add tables +	 *	change_columns: Column changes (only type, not name) +	 *	add_columns: Add columns to a table +	 *	drop_keys: Dropping keys +	 *	drop_columns: Removing/Dropping columns +	 *	add_primary_keys: adding primary keys +	 *	add_unique_index: adding an unique index +	 *	add_index: adding an index (can be column:index_size if you need to provide size) +	 * +	 * The values are in this format: +	 *		{TABLE NAME}		=> array( +	 *			{COLUMN NAME}		=> array({COLUMN TYPE}, {DEFAULT VALUE}, {OPTIONAL VARIABLES}), +	 *			{KEY/INDEX NAME}	=> array({COLUMN NAMES}), +	 *		) +	 * +	 * +	 * @param array $schema_changes +	 * @return null +	 */ +	public function perform_schema_changes($schema_changes); + +	/** +	 * Gets a list of tables in the database. +	 * +	 * @return array		Array of table names  (all lower case) +	 */ +	public function sql_list_tables(); + +	/** +	 * Check if table exists +	 * +	 * @param string	$table_name	The table name to check for +	 * @return bool true if table exists, else false +	 */ +	public function sql_table_exists($table_name); + +	/** +	 * Create SQL Table +	 * +	 * @param string	$table_name		The table name to create +	 * @param array		$table_data		Array containing table data. +	 * @return array|true	Statements to run, or true if the statements have been executed +	 */ +	public function sql_create_table($table_name, $table_data); + +	/** +	 * Drop Table +	 * +	 * @param string	$table_name		The table name to drop +	 * @return array|true	Statements to run, or true if the statements have been executed +	 */ +	public function sql_table_drop($table_name); + +	/** +	 * Gets a list of columns of a table. +	 * +	 * @param string $table_name	Table name +	 * @return array		Array of column names (all lower case) +	 */ +	public function sql_list_columns($table_name); + +	/** +	 * Check whether a specified column exist in a table +	 * +	 * @param string	$table_name		Table to check +	 * @param string	$column_name	Column to check +	 * @return bool		True if column exists, false otherwise +	 */ +	public function sql_column_exists($table_name, $column_name); + +	/** +	 * Add new column +	 * +	 * @param string	$table_name		Table to modify +	 * @param string	$column_name	Name of the column to add +	 * @param array		$column_data	Column data +	 * @param bool		$inline			Whether the query should actually be run, +	 *						or return a string for adding the column +	 * @return array|true	Statements to run, or true if the statements have been executed +	 */ +	public function sql_column_add($table_name, $column_name, $column_data, $inline = false); + +	/** +	 * Change column type (not name!) +	 * +	 * @param string	$table_name		Table to modify +	 * @param string	$column_name	Name of the column to modify +	 * @param array		$column_data	Column data +	 * @param bool		$inline			Whether the query should actually be run, +	 *						or return a string for modifying the column +	 * @return array|true	Statements to run, or true if the statements have been executed +	 */ +	public function sql_column_change($table_name, $column_name, $column_data, $inline = false); + +	/** +	 * Drop column +	 * +	 * @param string	$table_name		Table to modify +	 * @param string	$column_name	Name of the column to drop +	 * @param bool		$inline			Whether the query should actually be run, +	 *						or return a string for deleting the column +	 * @return array|true	Statements to run, or true if the statements have been executed +	 */ +	public function sql_column_remove($table_name, $column_name, $inline = false); + +	/** +	 * List all of the indices that belong to a table +	 * +	 * NOTE: does not list +	 * - UNIQUE indices +	 * - PRIMARY keys +	 * +	 * @param string	$table_name		Table to check +	 * @return array		Array with index names +	 */ +	public function sql_list_index($table_name); + +	/** +	 * Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes. +	 * +	 * @param string	$table_name		Table to check the index at +	 * @param string	$index_name		The index name to check +	 * @return bool			True if index exists, else false +	 */ +	public function sql_index_exists($table_name, $index_name); + +	/** +	 * Add index +	 * +	 * @param string	$table_name		Table to modify +	 * @param string	$index_name		Name of the index to create +	 * @param string|array	$column		Either a string with a column name, or an array with columns +	 * @return array|true	Statements to run, or true if the statements have been executed +	 */ +	public function sql_create_index($table_name, $index_name, $column); + +	/** +	 * Drop Index +	 * +	 * @param string	$table_name		Table to modify +	 * @param string	$index_name		Name of the index to delete +	 * @return array|true	Statements to run, or true if the statements have been executed +	 */ +	public function sql_index_drop($table_name, $index_name); + +	/** +	 * Check if a specified index exists in table. +	 * +	 * NOTE: Does not return normal and PRIMARY KEY indexes +	 * +	 * @param string	$table_name		Table to check the index at +	 * @param string	$index_name		The index name to check +	 * @return bool True if index exists, else false +	 */ +	public function sql_unique_index_exists($table_name, $index_name); + +	/** +	 * Add unique index +	 * +	 * @param string	$table_name		Table to modify +	 * @param string	$index_name		Name of the unique index to create +	 * @param string|array	$column		Either a string with a column name, or an array with columns +	 * @return array|true	Statements to run, or true if the statements have been executed +	 */ +	public function sql_create_unique_index($table_name, $index_name, $column); + +	/** +	 * Add primary key +	 * +	 * @param string	$table_name		Table to modify +	 * @param string|array	$column		Either a string with a column name, or an array with columns +	 * @param bool		$inline			Whether the query should actually be run, +	 *						or return a string for creating the key +	 * @return array|true	Statements to run, or true if the statements have been executed +	 */ +	public function sql_create_primary_key($table_name, $column, $inline = false); +} diff --git a/phpBB/phpbb/debug/debug.php b/phpBB/phpbb/debug/debug.php new file mode 100644 index 0000000000..c5ffada2e5 --- /dev/null +++ b/phpBB/phpbb/debug/debug.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\debug; + +use Symfony\Component\Debug\BufferingLogger; +use Symfony\Component\Debug\DebugClassLoader; +use Symfony\Component\Debug\ExceptionHandler; + +/** + * Registers all the debug tools. + + * @see Symfony\Component\Debug\Debug + */ +class debug +{ +	private static $enabled = false; + +	/** +	 * Enables the debug tools. +	 * +	 * This method registers an error handler and an exception handler. +	 * +	 * If the Symfony ClassLoader component is available, a special +	 * class loader is also registered. +	 * +	 * @param int  $errorReportingLevel The level of error reporting you want +	 * @param bool $displayErrors       Whether to display errors (for development) or just log them (for production) +	 */ +	public static function enable($errorReportingLevel = null, $displayErrors = true) +	{ +		if (static::$enabled) +		{ +			return; +		} + +		static::$enabled = true; + +		if ($errorReportingLevel !== null) +		{ +			error_reporting($errorReportingLevel); +		} +		else +		{ +			error_reporting(-1); +		} + +		if ('cli' !== php_sapi_name()) +		{ +			ini_set('display_errors', 0); +			ExceptionHandler::register(); +		} +		else if ($displayErrors && (!ini_get('log_errors') || ini_get('error_log'))) +		{ +			// CLI - display errors only if they're not already logged to STDERR +			ini_set('display_errors', 1); +		} + +		if ($displayErrors) +		{ +			error_handler::register(new error_handler(new BufferingLogger())); +		} +		else +		{ +			error_handler::register()->throwAt(0, true); +		} + +		DebugClassLoader::enable(); +	} +} diff --git a/phpBB/phpbb/debug/error_handler.php b/phpBB/phpbb/debug/error_handler.php new file mode 100644 index 0000000000..246e724f56 --- /dev/null +++ b/phpBB/phpbb/debug/error_handler.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\debug; + +use Symfony\Component\Debug\ErrorHandler; + +class error_handler extends ErrorHandler +{ +	public function handleError($type, $message, $file, $line, array $context, array $backtrace = null) +	{ +		if ($type === E_USER_WARNING || $type === E_USER_NOTICE) +		{ +			$handler = defined('PHPBB_MSG_HANDLER') ? PHPBB_MSG_HANDLER : 'msg_handler'; + +			$handler($type, $message, $file, $line); +		} + +		return parent::handleError($type, $message, $file, $line, $context, $backtrace); +	} +} diff --git a/phpBB/phpbb/di/container_builder.php b/phpBB/phpbb/di/container_builder.php index a214356ac3..b6854673c2 100644 --- a/phpBB/phpbb/di/container_builder.php +++ b/phpBB/phpbb/di/container_builder.php @@ -13,392 +13,617 @@  namespace phpbb\di; +use phpbb\filesystem\filesystem; +use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator; +use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper; +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\Filesystem\Exception\IOException; +use Symfony\Component\Finder\Finder; +use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass;  class container_builder  { -	/** @var string phpBB Root Path */ -	protected $phpbb_root_path; - -	/** @var string php file extension  */ -	protected $php_ext; -  	/** -	* The container under construction -	* -	* @var ContainerBuilder -	*/ -	protected $container; +	 * @var string The environment to use. +	 */ +	protected $environment;  	/** -	* @var \phpbb\db\driver\driver_interface -	*/ -	protected $dbal_connection = null; +	 * @var string phpBB Root Path +	 */ +	protected $phpbb_root_path;  	/** -	* @var array the installed extensions -	*/ -	protected $installed_exts = null; +	 * @var string php file extension +	 */ +	protected $php_ext;  	/** -	* Indicates whether the php config file should be injected into the container (default to true). -	* -	* @var bool -	*/ -	protected $inject_config = true; +	 * The container under construction +	 * +	 * @var ContainerBuilder +	 */ +	protected $container;  	/** -	* Indicates whether extensions should be used (default to true). -	* -	* @var bool -	*/ +	 * Indicates whether extensions should be used (default to true). +	 * +	 * @var bool +	 */  	protected $use_extensions = true;  	/** -	* Defines a custom path to find the configuration of the container (default to $this->phpbb_root_path . 'config') -	* -	* @var string -	*/ +	 * Defines a custom path to find the configuration of the container (default to $this->phpbb_root_path . 'config') +	 * +	 * @var string +	 */  	protected $config_path = null;  	/** -	* Indicates whether the phpBB compile pass should be used (default to true). -	* -	* @var bool -	*/ -	protected $use_custom_pass = true; +	 * Indicates whether the container should be dumped to the filesystem (default to true). +	 * +	 * If DEBUG_CONTAINER is set this option is ignored and a new container is build. +	 * +	 * @var bool +	 */ +	protected $use_cache = true;  	/** -	* Indicates whether the kernel compile pass should be used (default to true). -	* -	* @var bool -	*/ -	protected $use_kernel_pass = true; +	 * Indicates if the container should be compiled automatically (default to true). +	 * +	 * @var bool +	 */ +	protected $compile_container = true;  	/** -	* Indicates whether the container should be dumped to the filesystem (default to true). -	* -	* If DEBUG_CONTAINER is set this option is ignored and a new container is build. -	* -	* @var bool -	*/ -	protected $dump_container = true; +	 * Custom parameters to inject into the container. +	 * +	 * Default to: +	 * 	array( +	 * 		'core.root_path', $this->phpbb_root_path, +	 * 		'core.php_ext', $this->php_ext, +	 * ); +	 * +	 * @var array +	 */ +	protected $custom_parameters = [];  	/** -	* Indicates if the container should be compiled automatically (default to true). -	* -	* @var bool -	*/ -	protected $compile_container = true; +	 * @var \phpbb\config_php_file +	 */ +	protected $config_php_file;  	/** -	* Custom parameters to inject into the container. -	* -	* Default to true: -	* 	array( -	* 		'core.root_path', $this->phpbb_root_path, -	* 		'core.php_ext', $this->php_ext, -	* ); -	* -	* @var array -	*/ -	protected $custom_parameters = null; +	 * @var string +	 */ +	protected $cache_dir;  	/** -	* @var \phpbb\config_php_file -	*/ -	protected $config_php_file; +	 * @var array +	 */ +	private $container_extensions; + +	/** @var \Exception */ +	private $build_exception;  	/** -	* Constructor -	* -	* @param \phpbb\config_php_file $config_php_file -	* @param string $phpbb_root_path Path to the phpbb includes directory. -	* @param string $php_ext php file extension -	*/ -	function __construct(\phpbb\config_php_file $config_php_file, $phpbb_root_path, $php_ext) +	 * Constructor +	 * +	 * @param string $phpbb_root_path Path to the phpbb includes directory. +	 * @param string $php_ext php file extension +	 */ +	public function __construct($phpbb_root_path, $php_ext)  	{ -		$this->config_php_file = $config_php_file;  		$this->phpbb_root_path = $phpbb_root_path;  		$this->php_ext = $php_ext;  	}  	/** -	* Build and return a new Container respecting the current configuration -	* -	* @return \phpbb_cache_container|ContainerBuilder -	*/ +	 * Build and return a new Container respecting the current configuration +	 * +	 * @return \phpbb_cache_container|ContainerBuilder +	 */  	public function get_container()  	{ -		$container_filename = $this->get_container_filename(); -		if (!defined('DEBUG_CONTAINER') && $this->dump_container && file_exists($container_filename)) +		try  		{ -			require($container_filename); -			$this->container = new \phpbb_cache_container(); -		} -		else -		{ -			if ($this->config_path === null) +			$container_filename = $this->get_container_filename(); +			$config_cache = new ConfigCache($container_filename, defined('DEBUG')); +			if ($this->use_cache && $config_cache->isFresh())  			{ -				$this->config_path = $this->phpbb_root_path . 'config'; -			} -			$container_extensions = array(new \phpbb\di\extension\core($this->config_path)); +				if ($this->use_extensions) +				{ +					require($this->get_autoload_filename()); +				} -			if ($this->use_extensions) -			{ -				$installed_exts = $this->get_installed_extensions(); -				$container_extensions[] = new \phpbb\di\extension\ext($installed_exts); +				require($config_cache->getPath()); +				$this->container = new \phpbb_cache_container();  			} - -			if ($this->inject_config) +			else  			{ -				$container_extensions[] = new \phpbb\di\extension\config($this->config_php_file); -			} +				$this->container_extensions = array(new extension\core($this->get_config_path())); -			$this->container = $this->create_container($container_extensions); +				if ($this->use_extensions) +				{ +					$this->load_extensions(); +				} -			if ($this->use_custom_pass) -			{ -				// Symfony Kernel Listeners -				$this->container->addCompilerPass(new \phpbb\di\pass\collection_pass()); +				// Inject the config +				if ($this->config_php_file) +				{ +					$this->container_extensions[] = new extension\config($this->config_php_file); +				} + +				$this->container = $this->create_container($this->container_extensions); + +				// Easy collections through tags +				$this->container->addCompilerPass(new pass\collection_pass()); + +				// Event listeners "phpBB style"  				$this->container->addCompilerPass(new RegisterListenersPass('dispatcher', 'event.listener_listener', 'event.listener')); -				if ($this->use_kernel_pass) +				// Event listeners "Symfony style" +				$this->container->addCompilerPass(new RegisterListenersPass('dispatcher')); + +				if ($this->use_extensions) +				{ +					$this->register_ext_compiler_pass(); +				} + +				$filesystem = new 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)  				{ -					$this->container->addCompilerPass(new RegisterListenersPass('dispatcher')); +					$this->container->compile(); + +					if ($this->use_cache) +					{ +						$this->dump_container($config_cache); +					}  				}  			} -			$this->inject_custom_parameters(); +			if ($this->compile_container && $this->config_php_file) +			{ +				$this->container->set('config.php', $this->config_php_file); +			} -			if ($this->compile_container) +			return $this->container; +		} +		catch (\Exception $e) +		{ +			// Don't try to recover if we are in the development environment +			if ($this->get_environment() === 'development')  			{ -				$this->container->compile(); +				throw $e;  			} -			if ($this->dump_container && !defined('DEBUG_CONTAINER')) +			if ($this->build_exception === null) +			{ +				$this->build_exception = $e; + +				return $this +					->without_extensions() +					->without_cache() +					->with_custom_parameters(array_merge($this->custom_parameters, [ +						'container_exception' => $e, +					])) +					->get_container(); +			} +			else  			{ -				$this->dump_container($container_filename); +				// Rethrow the original exception if it's still failing +				throw $this->build_exception;  			}  		} +	} -		$this->container->set('config.php', $this->config_php_file); +	/** +	 * Enable the extensions. +	 * +	 * @param string $environment The environment to use +	 * @return $this +	 */ +	public function with_environment($environment) +	{ +		$this->environment = $environment; -		if ($this->compile_container) -		{ -			$this->inject_dbal(); -		} +		return $this; +	} + +	/** +	 * Enable the extensions. +	 * +	 * @return $this +	 */ +	public function with_extensions() +	{ +		$this->use_extensions = true; -		return $this->container; +		return $this;  	}  	/** -	* Set if the extensions should be used. -	* -	* @param bool $use_extensions -	*/ -	public function set_use_extensions($use_extensions) +	 * Disable the extensions. +	 * +	 * @return $this +	 */ +	public function without_extensions()  	{ -		$this->use_extensions = $use_extensions; +		$this->use_extensions = false; + +		return $this;  	}  	/** -	* Set if the phpBB compile pass have to be used. -	* -	* @param bool $use_custom_pass -	*/ -	public function set_use_custom_pass($use_custom_pass) +	 * Enable the caching of the container. +	 * +	 * If DEBUG_CONTAINER is set this option is ignored and a new container is build. +	 * +	 * @return $this +	 */ +	public function with_cache()  	{ -		$this->use_custom_pass = $use_custom_pass; +		$this->use_cache = true; + +		return $this;  	}  	/** -	* Set if the kernel compile pass have to be used. -	* -	* @param bool $use_kernel_pass -	*/ -	public function set_use_kernel_pass($use_kernel_pass) +	 * Disable the caching of the container. +	 * +	 * @return $this +	 */ +	public function without_cache()  	{ -		$this->use_kernel_pass = $use_kernel_pass; +		$this->use_cache = false; + +		return $this;  	}  	/** -	* Set if the php config file should be injecting into the container. -	* -	* @param bool $inject_config -	*/ -	public function set_inject_config($inject_config) +	 * Set the cache directory. +	 * +	 * @param string $cache_dir The cache directory. +	 * @return $this +	 */ +	public function with_cache_dir($cache_dir)  	{ -		$this->inject_config = $inject_config; +		$this->cache_dir = $cache_dir; + +		return $this;  	}  	/** -	* Set if a dump container should be used. -	* -	* If DEBUG_CONTAINER is set this option is ignored and a new container is build. -	* -	* @var bool $dump_container -	*/ -	public function set_dump_container($dump_container) +	 * Enable the compilation of the container. +	 * +	 * @return $this +	 */ +	public function with_compiled_container()  	{ -		$this->dump_container = $dump_container; +		$this->compile_container = true; + +		return $this;  	}  	/** -	* Set if the container should be compiled automatically (default to true). -	* -	* @var bool $dump_container -	*/ -	public function set_compile_container($compile_container) +	 * Disable the compilation of the container. +	 * +	 * @return $this +	 */ +	public function without_compiled_container()  	{ -		$this->compile_container = $compile_container; +		$this->compile_container = false; + +		return $this;  	}  	/** -	* Set a custom path to find the configuration of the container -	* -	* @param string $config_path -	*/ -	public function set_config_path($config_path) +	 * Set a custom path to find the configuration of the container. +	 * +	 * @param string $config_path +	 * @return $this +	 */ +	public function with_config_path($config_path)  	{  		$this->config_path = $config_path; + +		return $this;  	}  	/** -	* Set custom parameters to inject into the container. -	* -	* @param array $custom_parameters -	*/ -	public function set_custom_parameters($custom_parameters) +	 * Set custom parameters to inject into the container. +	 * +	 * @param array $custom_parameters +	 * @return $this +	 */ +	public function with_custom_parameters($custom_parameters)  	{  		$this->custom_parameters = $custom_parameters; + +		return $this;  	}  	/** -	* Dump the container to the disk. -	* -	* @param string $container_filename The name of the file. -	*/ -	protected function dump_container($container_filename) +	 * Set custom parameters to inject into the container. +	 * +	 * @param \phpbb\config_php_file $config_php_file +	 * @return $this +	 */ +	public function with_config(\phpbb\config_php_file $config_php_file)  	{ -		$dumper = new PhpDumper($this->container); -		$cached_container_dump = $dumper->dump(array( -			'class'         => 'phpbb_cache_container', -			'base_class'    => 'Symfony\\Component\\DependencyInjection\\ContainerBuilder', -		)); +		$this->config_php_file = $config_php_file; -		file_put_contents($container_filename, $cached_container_dump); +		return $this;  	}  	/** -	* Inject the connection into the container if one was opened. -	*/ -	protected function inject_dbal() +	 * Returns the path to the container configuration (default: root_path/config) +	 * +	 * @return string +	 */ +	protected function get_config_path()  	{ -		if ($this->dbal_connection !== null) -		{ -			$this->container->get('dbal.conn')->set_driver($this->dbal_connection); -		} +		return $this->config_path ?: $this->phpbb_root_path . 'config';  	}  	/** -	* Get DB connection. -	* -	* @return \phpbb\db\driver\driver_interface -	*/ -	protected function get_dbal_connection() +	 * Returns the path to the cache directory (default: root_path/cache/environment). +	 * +	 * @return string Path to the cache directory. +	 */ +	protected function get_cache_dir()  	{ -		if ($this->dbal_connection === null) -		{ -			$dbal_driver_class = $this->config_php_file->convert_30_dbms_to_31($this->config_php_file->get('dbms')); -			$this->dbal_connection = new $dbal_driver_class(); -			$this->dbal_connection->sql_connect( -				$this->config_php_file->get('dbhost'), -				$this->config_php_file->get('dbuser'), -				$this->config_php_file->get('dbpasswd'), -				$this->config_php_file->get('dbname'), -				$this->config_php_file->get('dbport'), -				defined('PHPBB_DB_NEW_LINK') && PHPBB_DB_NEW_LINK -			); -		} - -		return $this->dbal_connection; +		return $this->cache_dir ?: $this->phpbb_root_path . 'cache/' . $this->get_environment() . '/';  	}  	/** -	* Get enabled extensions. -	* -	* @return array enabled extensions -	*/ -	protected function get_installed_extensions() +	 * Load the enabled extensions. +	 */ +	protected function load_extensions()  	{ -		$db = $this->get_dbal_connection(); -		$extension_table = $this->config_php_file->get('table_prefix') . 'ext'; +		if ($this->config_php_file !== null) +		{ +			// Build an intermediate container to load the ext list from the database +			$container_builder = new container_builder($this->phpbb_root_path, $this->php_ext); +			$ext_container = $container_builder +				->without_cache() +				->without_extensions() +				->with_config($this->config_php_file) +				->with_config_path($this->get_config_path()) +				->with_environment('production') +				->without_compiled_container() +				->get_container() +			; + +			$ext_container->register('cache.driver', '\\phpbb\\cache\\driver\\dummy'); +			$ext_container->compile(); + +			$extensions = $ext_container->get('ext.manager')->all_enabled(); + +			// Load each extension found +			$autoloaders = '<?php +/** + * Loads all extensions custom auto-loaders. + * + * This file has been auto-generated + * by phpBB while loading the extensions. + */ + +'; +			foreach ($extensions as $ext_name => $path) +			{ +				$extension_class = '\\' . str_replace('/', '\\', $ext_name) . '\\di\\extension'; -		$sql = 'SELECT * -			FROM ' . $extension_table . ' -			WHERE ext_active = 1'; +				if (!class_exists($extension_class)) +				{ +					$extension_class = '\\phpbb\\extension\\di\\extension_base'; +				} + +				$this->container_extensions[] = new $extension_class($ext_name, $path); + +				// Load extension autoloader +				$filename = $path . 'vendor/autoload.php'; +				if (file_exists($filename)) +				{ +					$autoloaders .= "require('{$filename}');\n"; +				} +			} -		$result = $db->sql_query($sql); -		$rows = $db->sql_fetchrowset($result); -		$db->sql_freeresult($result); +			$configCache = new ConfigCache($this->get_autoload_filename(), false); +			$configCache->write($autoloaders); -		$exts = array(); -		foreach ($rows as $row) +			require($this->get_autoload_filename()); +		} +		else  		{ -			$exts[$row['ext_name']] = $this->phpbb_root_path . 'ext/' . $row['ext_name'] . '/'; +			// To load the extensions we need the database credentials. +			// Automatically disable the extensions if we don't have them. +			$this->use_extensions = false;  		} +	} + +	/** +	 * Dump the container to the disk. +	 * +	 * @param ConfigCache $cache The config cache +	 */ +	protected function dump_container($cache) +	{ +		try +		{ +			$dumper = new PhpDumper($this->container); +			$proxy_dumper = new ProxyDumper(); +			$dumper->setProxyDumper($proxy_dumper); -		return $exts; +			$cached_container_dump = $dumper->dump(array( +				'class'      => 'phpbb_cache_container', +				'base_class' => 'Symfony\\Component\\DependencyInjection\\ContainerBuilder', +			)); + +			$cache->write($cached_container_dump, $this->container->getResources()); +		} +		catch (IOException $e) +		{ +			// Don't fail if the cache isn't writeable +		}  	}  	/** -	* Create the ContainerBuilder object -	* -	* @param array $extensions Array of Container extension objects -	* @return ContainerBuilder object -	*/ +	 * Create the ContainerBuilder object +	 * +	 * @param array $extensions Array of Container extension objects +	 * @return ContainerBuilder object +	 */  	protected function create_container(array $extensions)  	{ -		$container = new ContainerBuilder(); +		$container = new ContainerBuilder(new ParameterBag($this->get_core_parameters())); +		$container->setProxyInstantiator(new RuntimeInstantiator()); + +		$extensions_alias = array();  		foreach ($extensions as $extension)  		{  			$container->registerExtension($extension); -			$container->loadFromExtension($extension->getAlias()); +			$extensions_alias[] = $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) +		foreach ($this->custom_parameters as $key => $value)  		{ -			$this->custom_parameters = array( -				'core.root_path' => $this->phpbb_root_path, -				'core.php_ext' => $this->php_ext, -			); +			$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'         => defined('DEBUG') ? DEBUG : false, +				'core.cache_dir'     => $this->get_cache_dir(), +			), +			$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;  	}  	/** -	* Get the filename under which the dumped container will be stored. -	* -	* @return string Path for dumped container -	*/ +	 * Get the filename under which the dumped container will be stored. +	 * +	 * @return string Path for dumped container +	 */  	protected function get_container_filename()  	{ -		return $this->phpbb_root_path . 'cache/container_' . md5($this->phpbb_root_path) . '.' . $this->php_ext; +		$container_params = [ +			'phpbb_root_path' => $this->phpbb_root_path, +			'use_extensions' => $this->use_extensions, +			'config_path' => $this->config_path, +		]; + +		return $this->get_cache_dir() . 'container_' . md5(implode(',', $container_params)) . '.' . $this->php_ext; +	} + +	/** +	 * Get the filename under which the dumped extensions autoloader will be stored. +	 * +	 * @return string Path for dumped extensions autoloader +	 */ +	protected function get_autoload_filename() +	{ +		$container_params = [ +			'phpbb_root_path' => $this->phpbb_root_path, +			'use_extensions' => $this->use_extensions, +			'config_path' => $this->config_path, +		]; + +		return $this->get_cache_dir() . 'autoload_' . md5(implode(',', $container_params)) . '.' . $this->php_ext; +	} + +	/** +	 * Return the name of the current environment. +	 * +	 * @return string +	 */ +	protected function get_environment() +	{ +		return $this->environment ?: PHPBB_ENVIRONMENT; +	} + +	private function register_ext_compiler_pass() +	{ +		$finder = new Finder(); +		$finder +			->name('*_pass.php') +			->path('di/pass') +			->files() +			->ignoreDotFiles(true) +			->ignoreUnreadableDirs(true) +			->ignoreVCS(true) +			->followLinks() +			->in($this->phpbb_root_path . 'ext') +		; + +		/** @var \SplFileInfo $pass */ +		foreach ($finder as $pass) +		{ +			$filename = $pass->getPathname(); +			$filename = substr($filename, 0, -strlen('.' . $pass->getExtension())); +			$filename = str_replace(DIRECTORY_SEPARATOR, '/', $filename); +			$className = preg_replace('#^.*ext/#', '', $filename); +			$className = '\\' . str_replace('/', '\\', $className); + +			if (class_exists($className) && in_array('Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface', class_implements($className), true)) +			{ +				$this->container->addCompilerPass(new $className()); +			} +		}  	}  } diff --git a/phpBB/phpbb/di/extension/container_configuration.php b/phpBB/phpbb/di/extension/container_configuration.php new file mode 100644 index 0000000000..4585d6509e --- /dev/null +++ b/phpBB/phpbb/di/extension/container_configuration.php @@ -0,0 +1,52 @@ +<?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('debug') +					->addDefaultsIfNotSet() +					->children() +						->booleanNode('exceptions')->defaultValue(false)->end() +					->end() +				->end() +				->arrayNode('twig') +					->addDefaultsIfNotSet() +					->children() +						->booleanNode('debug')->defaultValue(null)->end() +						->booleanNode('auto_reload')->defaultValue(null)->end() +						->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..29c0b0e44e 100644 --- a/phpBB/phpbb/di/extension/core.php +++ b/phpBB/phpbb/di/extension/core.php @@ -13,53 +13,110 @@  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  */  class core extends Extension  { +	const TWIG_OPTIONS_POSITION = 6; +  	/** -	* 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 +				); +			} +		} + +		// Set the Twig options if defined in the environment +		$definition = $container->getDefinition('template.twig.environment'); +		$twig_environment_options = $definition->getArgument(static::TWIG_OPTIONS_POSITION); +		if ($config['twig']['debug']) +		{ +			$twig_environment_options['debug'] = true; +		} +		if ($config['twig']['auto_reload']) +		{ +			$twig_environment_options['auto_reload'] = true; +		} + +		// Replace the 7th argument, the options passed to the environment +		$definition->replaceArgument(static::TWIG_OPTIONS_POSITION, $twig_environment_options); + +		if ($config['twig']['enable_debug_extension']) +		{ +			$definition = $container->getDefinition('template.twig.extensions.debug'); +			$definition->addTag('twig.extension'); +		} + +		// Set the debug options +		foreach ($config['debug'] as $name => $value) +		{ +			$container->setParameter('debug.' . $name, $value); +		} +	} + +	/** +	 * {@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/di/ordered_service_collection.php b/phpBB/phpbb/di/ordered_service_collection.php new file mode 100644 index 0000000000..046012ae5b --- /dev/null +++ b/phpBB/phpbb/di/ordered_service_collection.php @@ -0,0 +1,117 @@ +<?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; + +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Collection of services in a specified order + */ +class ordered_service_collection extends service_collection +{ +	/** +	 * @var bool +	 */ +	protected $is_ordered; + +	/** +	 * @var array +	 */ +	protected $service_ids; + +	/** +	 * Constructor +	 * +	 * @param ContainerInterface $container Container object +	 */ +	public function __construct(ContainerInterface $container) +	{ +		$this->is_ordered = false; +		$this->service_ids = array(); + +		parent::__construct($container); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function getIterator() +	{ +		if (!$this->is_ordered) +		{ +			$this->sort_services(); +		} + +		return new service_collection_iterator($this); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function offsetExists($index) +	{ +		if (!$this->is_ordered) +		{ +			$this->sort_services(); +		} + +		return parent::offsetExists($index); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function offsetGet($index) +	{ +		if (!$this->is_ordered) +		{ +			$this->sort_services(); +		} + +		return parent::offsetGet($index); +	} + +	/** +	 * Adds a service ID to the collection +	 * +	 * @param string	$service_id +	 * @param int		$order +	 */ +	public function add($service_id, $order = 0) +	{ +		$order = (int) $order; +		$this->service_ids[$order][] = $service_id; +		$this->is_ordered = false; +	} + +	protected function sort_services() +	{ +		if ($this->is_ordered) +		{ +			return; +		} + +		$this->exchangeArray(array()); +		ksort($this->service_ids); +		foreach ($this->service_ids as $service_order_group) +		{ +			foreach ($service_order_group as $service_id) +			{ +				$this->offsetSet($service_id, null); +			} +		} + +		$this->is_ordered = true; +	} +} diff --git a/phpBB/phpbb/di/pass/collection_pass.php b/phpBB/phpbb/di/pass/collection_pass.php index a5c054674e..341f88518d 100644 --- a/phpBB/phpbb/di/pass/collection_pass.php +++ b/phpBB/phpbb/di/pass/collection_pass.php @@ -34,10 +34,30 @@ class collection_pass implements CompilerPassInterface  		foreach ($container->findTaggedServiceIds('service_collection') as $id => $data)  		{  			$definition = $container->getDefinition($id); +			$is_ordered_collection = (substr($definition->getClass(), -strlen('ordered_service_collection')) === 'ordered_service_collection'); +			$is_class_name_aware = (isset($data[0]['class_name_aware']) && $data[0]['class_name_aware']);  			foreach ($container->findTaggedServiceIds($data[0]['tag']) as $service_id => $service_data)  			{ -				$definition->addMethodCall('add', array($service_id)); +				if ($is_ordered_collection) +				{ +					$arguments = array($service_id, $service_data[0]['order']); +				} +				else +				{ +					$arguments = array($service_id); +				} + +				if ($is_class_name_aware) +				{ +					$service_definition = $container->getDefinition($service_id); +					$definition->addMethodCall('add_service_class', array( +						$service_id, +						$service_definition->getClass() +					)); +				} + +				$definition->addMethodCall('add', $arguments);  			}  		}  	} diff --git a/phpBB/phpbb/di/service_collection.php b/phpBB/phpbb/di/service_collection.php index 82ca9bf679..8e9175e204 100644 --- a/phpBB/phpbb/di/service_collection.php +++ b/phpBB/phpbb/di/service_collection.php @@ -26,6 +26,11 @@ class service_collection extends \ArrayObject  	protected $container;  	/** +	* @var array +	*/ +	protected $service_classes; + +	/**  	* Constructor  	*  	* @param ContainerInterface $container Container object @@ -33,6 +38,7 @@ class service_collection extends \ArrayObject  	public function __construct(ContainerInterface $container)  	{  		$this->container = $container; +		$this->service_classes = array();  	}  	/** @@ -76,4 +82,25 @@ class service_collection extends \ArrayObject  	{  		$this->offsetSet($name, null);  	} + +	/** +	* Add a service's class to the collection +	* +	* @param string	$service_id +	* @param string	$class +	*/ +	public function add_service_class($service_id, $class) +	{ +		$this->service_classes[$service_id] = $class; +	} + +	/** +	* Get services' classes +	* +	* @return array +	*/ +	public function get_service_classes() +	{ +		return $this->service_classes; +	}  } diff --git a/phpBB/phpbb/di/service_collection_iterator.php b/phpBB/phpbb/di/service_collection_iterator.php index 0d031ab52d..31bc156e99 100644 --- a/phpBB/phpbb/di/service_collection_iterator.php +++ b/phpBB/phpbb/di/service_collection_iterator.php @@ -32,7 +32,7 @@ class service_collection_iterator extends \ArrayIterator  	*/  	public function __construct(service_collection $collection, $flags = 0)  	{ -		parent::__construct($collection, $flags); +		parent::__construct($collection->getArrayCopy(), $flags);  		$this->collection = $collection;  	} diff --git a/phpBB/phpbb/event/kernel_exception_subscriber.php b/phpBB/phpbb/event/kernel_exception_subscriber.php index eb7831ad34..e427abf5e3 100644 --- a/phpBB/phpbb/event/kernel_exception_subscriber.php +++ b/phpBB/phpbb/event/kernel_exception_subscriber.php @@ -24,26 +24,28 @@ class kernel_exception_subscriber implements EventSubscriberInterface  {  	/**  	* Template object +	*  	* @var \phpbb\template\template  	*/  	protected $template;  	/** -	* User object -	* @var \phpbb\user +	* Language object +	* +	* @var \phpbb\language\language  	*/ -	protected $user; +	protected $language;  	/**  	* Construct method  	* -	* @param \phpbb\template\template $template Template object -	* @param \phpbb\user $user User object +	* @param \phpbb\template\template	$template	Template object +	* @param \phpbb\language\language	$language	Language object  	*/ -	public function __construct(\phpbb\template\template $template, \phpbb\user $user) +	public function __construct(\phpbb\template\template $template, \phpbb\language\language $language)  	{  		$this->template = $template; -		$this->user = $user; +		$this->language = $language;  	}  	/** @@ -60,15 +62,15 @@ class kernel_exception_subscriber implements EventSubscriberInterface  		if ($exception instanceof \phpbb\exception\exception_interface)  		{ -			$message = call_user_func_array(array($this->user, 'lang'), array_merge(array($message), $exception->get_parameters())); +			$message = $this->language->lang_array($message, $exception->get_parameters());  		}  		if (!$event->getRequest()->isXmlHttpRequest())  		{ -			page_header($this->user->lang('INFORMATION')); +			page_header($this->language->lang('INFORMATION'));  			$this->template->assign_vars(array( -				'MESSAGE_TITLE' => $this->user->lang('INFORMATION'), +				'MESSAGE_TITLE' => $this->language->lang('INFORMATION'),  				'MESSAGE_TEXT'  => $message,  			)); @@ -106,7 +108,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 02c2a1b9d6..e042d0a5d1 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( @@ -266,7 +266,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) @@ -280,7 +280,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"; @@ -371,7 +371,6 @@ class md_exporter  	{  		$files_list = array(  			'prosilver'		=> array(), -			'subsilver2'	=> array(),  			'adm'			=> array(),  		); @@ -391,10 +390,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/base.php b/phpBB/phpbb/extension/base.php index 5bb530bad4..c7778cfed1 100644 --- a/phpBB/phpbb/extension/base.php +++ b/phpBB/phpbb/extension/base.php @@ -24,7 +24,7 @@ class base implements \phpbb\extension\extension_interface  	protected $container;  	/** @var \phpbb\finder */ -	protected $finder; +	protected $extension_finder;  	/** @var \phpbb\db\migrator */  	protected $migrator; @@ -73,9 +73,7 @@ class base implements \phpbb\extension\extension_interface  	*/  	public function enable_step($old_state)  	{ -		$migrations = $this->get_migration_file_list(); - -		$this->migrator->set_migrations($migrations); +		$this->get_migration_file_list();  		$this->migrator->update(); @@ -103,8 +101,6 @@ class base implements \phpbb\extension\extension_interface  	{  		$migrations = $this->get_migration_file_list(); -		$this->migrator->set_migrations($migrations); -  		foreach ($migrations as $migration)  		{  			while ($this->migrator->migration_state($migration) !== false) @@ -137,6 +133,10 @@ class base implements \phpbb\extension\extension_interface  		$migrations = $this->extension_finder->get_classes_from_files($migrations); +		$this->migrator->set_migrations($migrations); + +		$migrations = $this->migrator->get_migrations(); +  		return $migrations;  	}  } 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/feed/attachments_base.php b/phpBB/phpbb/feed/attachments_base.php index 04812f1570..b14dafe15a 100644 --- a/phpBB/phpbb/feed/attachments_base.php +++ b/phpBB/phpbb/feed/attachments_base.php @@ -16,7 +16,7 @@ namespace phpbb\feed;  /**  * Abstract class for feeds displaying attachments  */ -abstract class attachments_base extends \phpbb\feed\base +abstract class attachments_base extends base  {  	/**  	* Attachments that may be displayed diff --git a/phpBB/phpbb/feed/base.php b/phpBB/phpbb/feed/base.php index eeea0a55df..d4be0dc592 100644 --- a/phpBB/phpbb/feed/base.php +++ b/phpBB/phpbb/feed/base.php @@ -1,27 +1,27 @@  <?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. -* -*/ + * + * 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\feed;  /** -* Base class with some generic functions and settings. -*/ -abstract class base + * Base class with some generic functions and settings. + */ +abstract class base implements feed_interface  {  	/** -	* Feed helper object -	* @var \phpbb\feed\helper -	*/ +	 * Feed helper object +	 * @var \phpbb\feed\helper +	 */  	protected $helper;  	/** @var \phpbb\config\config */ @@ -49,47 +49,47 @@ abstract class base  	protected $phpEx;  	/** -	* SQL Query to be executed to get feed items -	*/ -	var $sql = array(); +	 * SQL Query to be executed to get feed items +	 */ +	protected $sql = array();  	/** -	* Keys specified for retrieval of title, content, etc. -	*/ -	var $keys = array(); +	 * Keys specified for retrieval of title, content, etc. +	 */ +	protected $keys = array();  	/** -	* Number of items to fetch. Usually overwritten by $config['feed_something'] -	*/ -	var $num_items = 15; +	 * Number of items to fetch. Usually overwritten by $config['feed_something'] +	 */ +	protected $num_items = 15;  	/** -	* Separator for title elements to separate items (for example forum / topic) -	*/ -	var $separator = "\xE2\x80\xA2"; // • +	 * Separator for title elements to separate items (for example forum / topic) +	 */ +	protected $separator = "\xE2\x80\xA2"; // •  	/** -	* Separator for the statistics row (Posted by, post date, replies, etc.) -	*/ -	var $separator_stats = "\xE2\x80\x94"; // — +	 * Separator for the statistics row (Posted by, post date, replies, etc.) +	 */ +	protected $separator_stats = "\xE2\x80\x94"; // —  	/** @var mixed Query result handle */  	protected $result;  	/** -	* Constructor -	* -	* @param \phpbb\feed\helper					$helper		Feed helper -	* @param \phpbb\config\config				$config		Config object -	* @param \phpbb\db\driver\driver_interface	$db			Database connection -	* @param \phpbb\cache\driver\driver_interface	$cache	Cache object -	* @param \phpbb\user						$user		User object -	* @param \phpbb\auth\auth					$auth		Auth object -	* @param \phpbb\content_visibility			$content_visibility		Content visibility object -	* @param \phpbb\event\dispatcher_interface	$phpbb_dispatcher		Event dispatcher object -	* @param string								$phpEx		php file extension -	*/ -	function __construct( +	 * Constructor +	 * +	 * @param \phpbb\feed\helper					$helper		Feed helper +	 * @param \phpbb\config\config				$config		Config object +	 * @param \phpbb\db\driver\driver_interface	$db			Database connection +	 * @param \phpbb\cache\driver\driver_interface	$cache	Cache object +	 * @param \phpbb\user						$user		User object +	 * @param \phpbb\auth\auth					$auth		Auth object +	 * @param \phpbb\content_visibility			$content_visibility		Auth object +	 * @param \phpbb\event\dispatcher_interface	$phpbb_dispatcher		Event dispatcher object +	 * @param string								$phpEx		php file extension +	 */ +	public function __construct(  		\phpbb\feed\helper $helper,  		\phpbb\config\config $config,  		\phpbb\db\driver\driver_interface $db, @@ -127,23 +127,23 @@ abstract class base  	}  	/** -	* Set keys. -	*/ -	function set_keys() +	 * {@inheritdoc} +	 */ +	public function set_keys()  	{  	}  	/** -	* Open feed -	*/ -	function open() +	 * {@inheritdoc} +	 */ +	public function open()  	{  	}  	/** -	* Close feed -	*/ -	function close() +	 * {@inheritdoc} +	 */ +	public function close()  	{  		if (!empty($this->result))  		{ @@ -152,28 +152,62 @@ abstract class base  	}  	/** -	* Set key -	* -	* @param string $key Key -	* @param mixed $value Value -	*/ -	function set($key, $value) +	 * {@inheritdoc} +	 */ +	public function set($key, $value)  	{  		$this->keys[$key] = $value;  	}  	/** -	* Get key -	* -	* @param string $key Key -	* @return mixed -	*/ -	function get($key) +	 * {@inheritdoc} +	 */ +	public function get($key)  	{  		return (isset($this->keys[$key])) ? $this->keys[$key] : null;  	} -	function get_readable_forums() +	/** +	 * {@inheritdoc} +	 */ +	public function get_item() +	{ +		if (!isset($this->result)) +		{ +			if (!$this->get_sql()) +			{ +				return false; +			} + +			$sql_ary = $this->sql; + +			/** +			 * Event to modify the feed item sql +			 * +			 * @event core.feed_base_modify_item_sql +			 * @var	array	sql_ary		The SQL array to get the feed item data +			 * +			 * @since 3.1.10-RC1 +			 */ +			$vars = array('sql_ary'); +			extract($this->phpbb_dispatcher->trigger_event('core.feed_base_modify_item_sql', compact($vars))); +			$this->sql = $sql_ary; +			unset($sql_ary); + +			// Query database +			$sql = $this->db->sql_build_query('SELECT', $this->sql); +			$this->result = $this->db->sql_query_limit($sql, $this->num_items); +		} + +		return $this->db->sql_fetchrow($this->result); +	} + +	/** +	 * Returns the ids of the forums readable by the current user. +	 * +	 * @return int[] +	 */ +	protected function get_readable_forums()  	{  		static $forum_ids; @@ -185,7 +219,12 @@ abstract class base  		return $forum_ids;  	} -	function get_moderator_approve_forums() +	/** +	 * Returns the ids of the forum for which the current user can approve the post in the moderation queue. +	 * +	 * @return int[] +	 */ +	protected function get_moderator_approve_forums()  	{  		static $forum_ids; @@ -197,7 +236,13 @@ abstract class base  		return $forum_ids;  	} -	function is_moderator_approve_forum($forum_id) +	/** +	 * Returns true if the current user can approve the post of the given forum +	 * +	 * @param int $forum_id Forum id to check +	 * @return bool +	 */ +	protected function is_moderator_approve_forum($forum_id)  	{  		static $forum_ids; @@ -209,7 +254,12 @@ abstract class base  		return (isset($forum_ids[$forum_id])) ? true : false;  	} -	function get_excluded_forums() +	/** +	 * Returns the ids of the forum excluded from the feeds +	 * +	 * @return int[] +	 */ +	protected function get_excluded_forums()  	{  		static $forum_ids; @@ -236,51 +286,35 @@ abstract class base  		return $forum_ids;  	} -	function is_excluded_forum($forum_id) +	/** +	 * Returns true if the given id is in the excluded forums list. +	 * +	 * @param int $forum_id Id to check +	 * @return bool +	 */ +	protected function is_excluded_forum($forum_id)  	{  		$forum_ids = $this->get_excluded_forums();  		return isset($forum_ids[$forum_id]) ? true : false;  	} -	function get_passworded_forums() +	/** +	 * Returns all password protected forum ids the current user is currently NOT authenticated for. +	 * +	 * @return array     Array of forum ids +	 */ +	protected function get_passworded_forums()  	{  		return $this->user->get_passworded_forums();  	} -	function get_item() -	{ -		if (!isset($this->result)) -		{ -			if (!$this->get_sql()) -			{ -				return false; -			} - -			$sql_ary = $this->sql; - -			/** -			* Event to modify the feed item sql -			* -			* @event core.feed_base_modify_item_sql -			* @var	array	sql_ary		The SQL array to get the feed item data -			* -			* @since 3.1.10-RC1 -			*/ -			$vars = array('sql_ary'); -			extract($this->phpbb_dispatcher->trigger_event('core.feed_base_modify_item_sql', compact($vars))); -			$this->sql = $sql_ary; -			unset($sql_ary); - -			// Query database -			$sql = $this->db->sql_build_query('SELECT', $this->sql); -			$this->result = $this->db->sql_query_limit($sql, $this->num_items); -		} - -		return $this->db->sql_fetchrow($this->result); -	} - -	function user_viewprofile($row) +	/** +	 * Returns the link to the user profile. +	 * +	 * @return string +	 */ +	protected function user_viewprofile($row)  	{  		$author_id = (int) $row[$this->get('author_id')]; @@ -293,4 +327,11 @@ abstract class base  		return '<a href="' . $this->helper->append_sid('memberlist.' . $this->phpEx, 'mode=viewprofile&u=' . $author_id) . '">' . $row[$this->get('creator')] . '</a>';  	} + +	/** +	 * Returns the SQL query used to retrieve the posts of the feed. +	 * +	 * @return string SQL SELECT query +	 */ +	protected abstract function get_sql();  } diff --git a/phpBB/phpbb/feed/controller/feed.php b/phpBB/phpbb/feed/controller/feed.php new file mode 100644 index 0000000000..c0d7bc72ec --- /dev/null +++ b/phpBB/phpbb/feed/controller/feed.php @@ -0,0 +1,411 @@ +<?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\feed\controller; + +use phpbb\auth\auth; +use phpbb\config\config; +use phpbb\db\driver\driver_interface; +use \phpbb\event\dispatcher_interface; +use phpbb\exception\http_exception; +use phpbb\feed\feed_interface; +use phpbb\feed\exception\feed_unavailable_exception; +use phpbb\feed\exception\unauthorized_exception; +use phpbb\feed\helper as feed_helper; +use phpbb\controller\helper as controller_helper; +use phpbb\symfony_request; +use phpbb\user; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +class feed +{ +	/** +	 * @var \Twig_Environment +	 */ +	protected $template; + +	/** +	 * @var symfony_request +	 */ +	protected $request; + +	/** +	 * @var controller_helper +	 */ +	protected $controller_helper; + +	/** +	 * @var config +	 */ +	protected $config; + +	/** +	 * @var driver_interface +	 */ +	protected $db; + +	/** +	 * @var ContainerInterface +	 */ +	protected $container; + +	/** +	 * @var feed_helper +	 */ +	protected $feed_helper; + +	/** +	 * @var user +	 */ +	protected $user; + +	/** +	 * @var auth +	 */ +	protected $auth; + +	/** +	 * @var dispatcher_interface +	 */ +	protected $phpbb_dispatcher; + +	/** +	 * @var string +	 */ +	protected $php_ext; + +	/** +	 * Constructor +	 * +	 * @param \Twig_Environment $twig +	 * @param symfony_request $request +	 * @param controller_helper $controller_helper +	 * @param config $config +	 * @param driver_interface $db +	 * @param ContainerInterface $container +	 * @param feed_helper $feed_helper +	 * @param user $user +	 * @param auth $auth +	 * @param dispatcher_interface $phpbb_dispatcher +	 * @param string $php_ext +	 */ +	public function __construct(\Twig_Environment $twig, symfony_request $request, controller_helper $controller_helper, config $config, driver_interface $db, ContainerInterface $container, feed_helper $feed_helper, user $user, auth $auth, dispatcher_interface $phpbb_dispatcher, $php_ext) +	{ +		$this->request = $request; +		$this->controller_helper = $controller_helper; +		$this->config = $config; +		$this->db = $db; +		$this->container = $container; +		$this->feed_helper = $feed_helper; +		$this->user = $user; +		$this->auth = $auth; +		$this->php_ext = $php_ext; +		$this->template = $twig; +		$this->phpbb_dispatcher = $phpbb_dispatcher; +	} + +	/** +	 * Controller for /feed/forums route +	 * +	 * @return Response +	 * +	 * @throws http_exception when the feed is disabled +	 */ +	public function forums() +	{ +		if (!$this->config['feed_overall_forums']) +		{ +			$this->send_unavailable(); +		} + +		return $this->send_feed($this->container->get('feed.forums')); +	} + +	/** +	 * Controller for /feed/news route +	 * +	 * @return Response +	 * +	 * @throws http_exception when the feed is disabled +	 */ +	public function news() +	{ +		// Get at least one news forum +		$sql = 'SELECT forum_id +					FROM ' . FORUMS_TABLE . ' +					WHERE ' . $this->db->sql_bit_and('forum_options', FORUM_OPTION_FEED_NEWS, '<> 0'); +		$result = $this->db->sql_query_limit($sql, 1, 0, 600); +		$s_feed_news = (int) $this->db->sql_fetchfield('forum_id'); +		$this->db->sql_freeresult($result); + +		if (!$s_feed_news) +		{ +			$this->send_unavailable(); +		} + +		return $this->send_feed($this->container->get('feed.news')); +	} + +	/** +	 * Controller for /feed/topics route +	 * +	 * @return Response +	 * +	 * @throws http_exception when the feed is disabled +	 */ +	public function topics() +	{ +		if (!$this->config['feed_topics_new']) +		{ +			$this->send_unavailable(); +		} + +		return $this->send_feed($this->container->get('feed.topics')); +	} + +	/** +	 * Controller for /feed/topics_new route +	 * +	 * @return Response +	 * +	 * @throws http_exception when the feed is disabled +	 */ +	public function topics_new() +	{ +		return $this->topics(); +	} + +	/** +	 * Controller for /feed/topics_active route +	 * +	 * @return Response +	 * +	 * @throws http_exception when the feed is disabled +	 */ +	public function topics_active() +	{ +		if (!$this->config['feed_topics_active']) +		{ +			$this->send_unavailable(); +		} + +		return $this->send_feed($this->container->get('feed.topics_active')); +	} + +	/** +	 * Controller for /feed/forum/{forum_id} route +	 * +	 * @param int $forum_id +	 * +	 * @return Response +	 * +	 * @throws http_exception when the feed is disabled +	 */ +	public function forum($forum_id) +	{ +		if (!$this->config['feed_forum']) +		{ +			$this->send_unavailable(); +		} + +		return $this->send_feed($this->container->get('feed.forum')->set_forum_id($forum_id)); +	} + +	/** +	 * Controller for /feed/topic/{topic_id} route +	 * +	 * @param int $topic_id +	 * +	 * @return Response +	 * +	 * @throws http_exception when the feed is disabled +	 */ +	public function topic($topic_id) +	{ +		if (!$this->config['feed_topic']) +		{ +			$this->send_unavailable(); +		} + +		return $this->send_feed($this->container->get('feed.topic')->set_topic_id($topic_id)); +	} + +	/** +	 * Controller for /feed/{mode] route +	 * +	 * @return Response +	 * +	 * @throws http_exception when the feed is disabled +	 */ +	public function overall() +	{ +		if (!$this->config['feed_overall']) +		{ +			$this->send_unavailable(); +		} + +		return $this->send_feed($this->container->get('feed.overall')); +	} + +	/** +	 * Display a given feed +	 * +	 * @param feed_interface $feed +	 * +	 * @return Response +	 */ +	protected function send_feed(feed_interface $feed) +	{ +		try +		{ +			return $this->send_feed_do($feed); +		} +		catch (feed_unavailable_exception $e) +		{ +			throw new http_exception(Response::HTTP_NOT_FOUND, $e->getMessage(), $e->get_parameters(), $e); +		} +		catch (unauthorized_exception $e) +		{ +			throw new http_exception(Response::HTTP_FORBIDDEN, $e->getMessage(), $e->get_parameters(), $e); +		} +	} + +	/** +	 * Really send the feed +	 * +	 * @param feed_interface $feed +	 * +	 * @return Response +	 * +	 * @throw exception\feed_exception +	 */ +	protected function send_feed_do(feed_interface $feed) +	{ +		$feed_updated_time = 0; +		$item_vars = array(); + +		$board_url = $this->feed_helper->get_board_url(); + +		// Open Feed +		$feed->open(); + +		// Iterate through items +		while ($row = $feed->get_item()) +		{ +			/** +			 * Event to modify the feed row +			 * +			 * @event core.feed_modify_feed_row +			 * @var	int		forum_id	Forum ID +			 * @var	string	mode		Feeds mode (forums|topics|topics_new|topics_active|news) +			 * @var	array	row			Array with feed data +			 * @var	int		topic_id	Topic ID +			 * +			 * @since 3.1.10-RC1 +			 */ +			$vars = array('forum_id', 'mode', 'row', 'topic_id'); +			extract($this->phpbb_dispatcher->trigger_event('core.feed_modify_feed_row', compact($vars))); + +			// BBCode options to correctly disable urls, smilies, bbcode... +			if ($feed->get('options') === null) +			{ +				// Allow all combinations +				$options = 7; + +				if ($feed->get('enable_bbcode') !== null && $feed->get('enable_smilies') !== null && $feed->get('enable_magic_url') !== null) +				{ +					$options = (($row[$feed->get('enable_bbcode')]) ? OPTION_FLAG_BBCODE : 0) + (($row[$feed->get('enable_smilies')]) ? OPTION_FLAG_SMILIES : 0) + (($row[$feed->get('enable_magic_url')]) ? OPTION_FLAG_LINKS : 0); +				} +			} +			else +			{ +				$options = $row[$feed->get('options')]; +			} + +			$title = (isset($row[$feed->get('title')]) && $row[$feed->get('title')] !== '') ? $row[$feed->get('title')] : ((isset($row[$feed->get('title2')])) ? $row[$feed->get('title2')] : ''); + +			$published = ($feed->get('published') !== null) ? (int) $row[$feed->get('published')] : 0; +			$updated = ($feed->get('updated') !== null) ? (int) $row[$feed->get('updated')] : 0; + +			$display_attachments = ($this->auth->acl_get('u_download') && $this->auth->acl_get('f_download', $row['forum_id']) && isset($row['post_attachment']) && $row['post_attachment']) ? true : false; + +			$item_row = array( +				'author'		=> ($feed->get('creator') !== null) ? $row[$feed->get('creator')] : '', +				'published'		=> ($published > 0) ? $this->feed_helper->format_date($published) : '', +				'updated'		=> ($updated > 0) ? $this->feed_helper->format_date($updated) : '', +				'link'			=> '', +				'title'			=> censor_text($title), +				'category'		=> ($this->config['feed_item_statistics'] && !empty($row['forum_id'])) ? $board_url . '/viewforum.' . $this->php_ext . '?f=' . $row['forum_id'] : '', +				'category_name'	=> ($this->config['feed_item_statistics'] && isset($row['forum_name'])) ? $row['forum_name'] : '', +				'description'	=> censor_text($this->feed_helper->generate_content($row[$feed->get('text')], $row[$feed->get('bbcode_uid')], $row[$feed->get('bitfield')], $options, $row['forum_id'], ($display_attachments ? $feed->get_attachments($row['post_id']) : array()))), +				'statistics'	=> '', +			); + +			// Adjust items, fill link, etc. +			$feed->adjust_item($item_row, $row); + +			$item_vars[] = $item_row; + +			$feed_updated_time = max($feed_updated_time, $published, $updated); +		} + +		// If we do not have any items at all, sending the current time is better than sending no time. +		if (!$feed_updated_time) +		{ +			$feed_updated_time = time(); +		} + +		$feed->close(); + +		$content = $this->template->render('feed.xml.twig', array( +			// Some default assignments +			// FEED_IMAGE is not used (atom) +			'FEED_IMAGE'			=> '', +			'SELF_LINK'				=> $this->controller_helper->route($this->request->attributes->get('_route'), $this->request->attributes->get('_route_params'), true, '', UrlGeneratorInterface::ABSOLUTE_URL), +			'FEED_LINK'				=> $board_url . '/index.' . $this->php_ext, +			'FEED_TITLE'			=> $this->config['sitename'], +			'FEED_SUBTITLE'			=> $this->config['site_desc'], +			'FEED_UPDATED'			=> $this->feed_helper->format_date($feed_updated_time), +			'FEED_LANG'				=> $this->user->lang['USER_LANG'], +			'FEED_AUTHOR'			=> $this->config['sitename'], + +			// Feed entries +			'FEED_ROWS'				=> $item_vars, +		)); + +		$response = new Response($content); +		$response->headers->set('Content-Type', 'application/atom+xml'); +		$response->setCharset('UTF-8'); +		$response->setLastModified(new \DateTime('@' . $feed_updated_time)); + +		if (!empty($this->user->data['is_bot'])) +		{ +			// Let reverse proxies know we detected a bot. +			$response->headers->set('X-PHPBB-IS-BOT', 'yes'); +		} + +		return $response; +	} + +	/** +	 * Throw and exception saying that the feed isn't available +	 * +	 * @throw http_exception +	 */ +	protected function send_unavailable() +	{ +		throw new http_exception(404, 'FEATURE_NOT_AVAILABLE'); +	} +} diff --git a/phpBB/phpbb/feed/exception/feed_exception.php b/phpBB/phpbb/feed/exception/feed_exception.php new file mode 100644 index 0000000000..c9c888211e --- /dev/null +++ b/phpBB/phpbb/feed/exception/feed_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\feed\exception; + +use phpbb\exception\runtime_exception; + +abstract class feed_exception extends runtime_exception +{ + +} diff --git a/phpBB/phpbb/feed/exception/feed_unavailable_exception.php b/phpBB/phpbb/feed/exception/feed_unavailable_exception.php new file mode 100644 index 0000000000..4b6605b47d --- /dev/null +++ b/phpBB/phpbb/feed/exception/feed_unavailable_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\feed\exception; + +abstract class feed_unavailable_exception extends feed_exception +{ + +} diff --git a/phpBB/phpbb/feed/exception/no_feed_exception.php b/phpBB/phpbb/feed/exception/no_feed_exception.php new file mode 100644 index 0000000000..af6357b74c --- /dev/null +++ b/phpBB/phpbb/feed/exception/no_feed_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\feed\exception; + +class no_feed_exception extends feed_unavailable_exception +{ +	public function __construct(\Exception $previous = null, $code = 0) +	{ +		parent::__construct('NO_FEED', array(), $previous, $code); +	} +} diff --git a/phpBB/phpbb/feed/exception/no_forum_exception.php b/phpBB/phpbb/feed/exception/no_forum_exception.php new file mode 100644 index 0000000000..a60832957a --- /dev/null +++ b/phpBB/phpbb/feed/exception/no_forum_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\feed\exception; + +class no_forum_exception extends feed_unavailable_exception +{ +	public function __construct($forum_id, \Exception $previous = null, $code = 0) +	{ +		parent::__construct('NO_FORUM', array($forum_id), $previous, $code); +	} +} diff --git a/phpBB/phpbb/feed/exception/no_topic_exception.php b/phpBB/phpbb/feed/exception/no_topic_exception.php new file mode 100644 index 0000000000..b961a65d1c --- /dev/null +++ b/phpBB/phpbb/feed/exception/no_topic_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\feed\exception; + +class no_topic_exception extends feed_unavailable_exception +{ +	public function __construct($topic_id, \Exception $previous = null, $code = 0) +	{ +		parent::__construct('NO_TOPIC', array($topic_id), $previous, $code); +	} +} diff --git a/phpBB/phpbb/feed/exception/unauthorized_exception.php b/phpBB/phpbb/feed/exception/unauthorized_exception.php new file mode 100644 index 0000000000..7868975779 --- /dev/null +++ b/phpBB/phpbb/feed/exception/unauthorized_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\feed\exception; + +abstract class unauthorized_exception extends feed_exception +{ + +} diff --git a/phpBB/phpbb/feed/exception/unauthorized_forum_exception.php b/phpBB/phpbb/feed/exception/unauthorized_forum_exception.php new file mode 100644 index 0000000000..4384c7b39b --- /dev/null +++ b/phpBB/phpbb/feed/exception/unauthorized_forum_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\feed\exception; + +class unauthorized_forum_exception extends unauthorized_exception +{ +	public function __construct($forum_id, \Exception $previous = null, $code = 0) +	{ +		parent::__construct('SORRY_AUTH_READ', array($forum_id), $previous, $code); +	} +} diff --git a/phpBB/phpbb/feed/exception/unauthorized_topic_exception.php b/phpBB/phpbb/feed/exception/unauthorized_topic_exception.php new file mode 100644 index 0000000000..f49f0a0476 --- /dev/null +++ b/phpBB/phpbb/feed/exception/unauthorized_topic_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\feed\exception; + +class unauthorized_topic_exception extends unauthorized_exception +{ +	public function __construct($topic_id, \Exception $previous = null, $code = 0) +	{ +		parent::__construct('SORRY_AUTH_READ_TOPIC', array($topic_id), $previous, $code); +	} +} diff --git a/phpBB/phpbb/feed/factory.php b/phpBB/phpbb/feed/factory.php deleted file mode 100644 index f364f06d03..0000000000 --- a/phpBB/phpbb/feed/factory.php +++ /dev/null @@ -1,127 +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\feed; - -use Symfony\Component\DependencyInjection\ContainerInterface; - -/** -* Factory class to return correct object -*/ -class factory -{ -	/** -	* Service container object -	* @var ContainerInterface -	*/ -	protected $container; - -	/** @var \phpbb\config\config */ -	protected $config; - -	/** @var \phpbb\db\driver\driver_interface */ -	protected $db; - -	/** -	* Constructor -	* -	* @param ContainerInterface					$container	Container object -	* @param \phpbb\config\config				$config		Config object -	* @param \phpbb\db\driver\driver_interface	$db			Database connection -	*/ -	public function __construct(ContainerInterface $container, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db) -	{ -		$this->container = $container; -		$this->config = $config; -		$this->db = $db; -	} - -	/** -	* Return correct object for specified mode -	* -	* @param string	$mode		The feeds mode. -	* @param int	$forum_id	Forum id specified by the script if forum feed provided. -	* @param int	$topic_id	Topic id specified by the script if topic feed provided. -	* -	* @return object	Returns correct feeds object for specified mode. -	*/ -	function get_feed($mode, $forum_id, $topic_id) -	{ -		switch ($mode) -		{ -			case 'forums': -				if (!$this->config['feed_overall_forums']) -				{ -					return false; -				} - -				return $this->container->get('feed.forums'); -			break; - -			case 'topics': -			case 'topics_new': -				if (!$this->config['feed_topics_new']) -				{ -					return false; -				} - -				return $this->container->get('feed.topics'); -			break; - -			case 'topics_active': -				if (!$this->config['feed_topics_active']) -				{ -					return false; -				} - -				return $this->container->get('feed.topics_active'); -			break; - -			case 'news': -				// Get at least one news forum -				$sql = 'SELECT forum_id -					FROM ' . FORUMS_TABLE . ' -					WHERE ' . $this->db->sql_bit_and('forum_options', FORUM_OPTION_FEED_NEWS, '<> 0'); -				$result = $this->db->sql_query_limit($sql, 1, 0, 600); -				$s_feed_news = (int) $this->db->sql_fetchfield('forum_id'); -				$this->db->sql_freeresult($result); - -				if (!$s_feed_news) -				{ -					return false; -				} - -				return $this->container->get('feed.news'); -			break; - -			default: -				if ($topic_id && $this->config['feed_topic']) -				{ -					return $this->container->get('feed.topic') -								->set_topic_id($topic_id); -				} -				else if ($forum_id && $this->config['feed_forum']) -				{ -					return $this->container->get('feed.forum') -								->set_forum_id($forum_id); -				} -				else if ($this->config['feed_overall']) -				{ -				return $this->container->get('feed.overall'); -				} - -				return false; -			break; -		} -	} -} diff --git a/phpBB/phpbb/feed/feed_interface.php b/phpBB/phpbb/feed/feed_interface.php new file mode 100644 index 0000000000..c185cd249c --- /dev/null +++ b/phpBB/phpbb/feed/feed_interface.php @@ -0,0 +1,67 @@ +<?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\feed; + +/** + * Interface implemented by all feeds types + */ +interface feed_interface +{ +	/** +	 * Set keys. +	 */ +	public function set_keys(); + +	/** +	 * Open feed +	 */ +	public function open(); + +	/** +	 * Close feed +	 */ +	public function close(); + +	/** +	 * Set key +	 * +	 * @param string $key Key +	 * @param mixed $value Value +	 */ +	public function set($key, $value); + +	/** +	 * Get key +	 * +	 * @param string $key Key +	 * @return mixed +	 */ +	public function get($key); + +	/** +	 * Get the next post in the feed +	 * +	 * @return array +	 */ +	public function get_item(); + +	/** +	 * Adjust a feed entry +	 * +	 * @param $item_row +	 * @param $row +	 * @return array +	 */ +	public function adjust_item(&$item_row, &$row); +} diff --git a/phpBB/phpbb/feed/forum.php b/phpBB/phpbb/feed/forum.php index 7a2087c1cd..6701c4d9e7 100644 --- a/phpBB/phpbb/feed/forum.php +++ b/phpBB/phpbb/feed/forum.php @@ -1,35 +1,39 @@  <?php  /** -* -* This file is part of the phpBB Forum Software package. -* -* @copyright (c) phpBB Limited <https://www.phpbb.com> -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ + * + * 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\feed; +use phpbb\feed\exception\no_feed_exception; +use phpbb\feed\exception\no_forum_exception; +use phpbb\feed\exception\unauthorized_forum_exception; +  /** -* Forum feed -* -* This will give you the last {$this->num_items} posts made -* within a specific forum. -*/ -class forum extends \phpbb\feed\post_base + * Forum feed + * + * This will give you the last {$this->num_items} posts made + * within a specific forum. + */ +class forum extends post_base  { -	var $forum_id		= 0; -	var $forum_data		= array(); +	protected $forum_id		= 0; +	protected $forum_data	= array();  	/** -	* Set the Forum ID -	* -	* @param int	$forum_id			Forum ID -	* @return	\phpbb\feed\forum -	*/ +	 * Set the Forum ID +	 * +	 * @param int	$forum_id			Forum ID +	 * @return	\phpbb\feed\forum +	 */  	public function set_forum_id($forum_id)  	{  		$this->forum_id = (int) $forum_id; @@ -37,7 +41,10 @@ class forum extends \phpbb\feed\post_base  		return $this;  	} -	function open() +	/** +	 * {@inheritdoc} +	 */ +	public function open()  	{  		// Check if forum exists  		$sql = 'SELECT forum_id, forum_name, forum_password, forum_type, forum_options @@ -49,25 +56,25 @@ class forum extends \phpbb\feed\post_base  		if (empty($this->forum_data))  		{ -			trigger_error('NO_FORUM'); +			throw new no_forum_exception($this->forum_id);  		}  		// Forum needs to be postable  		if ($this->forum_data['forum_type'] != FORUM_POST)  		{ -			trigger_error('NO_FEED'); +			throw new no_feed_exception();  		}  		// Make sure forum is not excluded from feed  		if (phpbb_optionget(FORUM_OPTION_FEED_EXCLUDE, $this->forum_data['forum_options']))  		{ -			trigger_error('NO_FEED'); +			throw new no_feed_exception();  		}  		// Make sure we can read this forum  		if (!$this->auth->acl_get('f_read', $this->forum_id))  		{ -			trigger_error('SORRY_AUTH_READ'); +			throw new unauthorized_forum_exception($this->forum_id);  		}  		// Make sure forum is not passworded or user is authed @@ -77,7 +84,7 @@ class forum extends \phpbb\feed\post_base  			if (isset($forum_ids_passworded[$this->forum_id]))  			{ -				trigger_error('SORRY_AUTH_READ'); +				throw new unauthorized_forum_exception($this->forum_id);  			}  			unset($forum_ids_passworded); @@ -86,7 +93,10 @@ class forum extends \phpbb\feed\post_base  		parent::open();  	} -	function get_sql() +	/** +	 * {@inheritdoc} +	 */ +	protected function get_sql()  	{  		// Determine topics with recent activity  		$sql = 'SELECT topic_id, topic_last_post_time @@ -114,7 +124,7 @@ class forum extends \phpbb\feed\post_base  		$this->sql = array(  			'SELECT'	=>	'p.post_id, p.topic_id, p.post_time, p.post_edit_time, p.post_visibility, p.post_subject, p.post_text, p.bbcode_bitfield, p.bbcode_uid, p.enable_bbcode, p.enable_smilies, p.enable_magic_url, p.post_attachment, ' . -							'u.username, u.user_id', +				'u.username, u.user_id',  			'FROM'		=> array(  				POSTS_TABLE		=> 'p',  				USERS_TABLE		=> 'u', @@ -129,7 +139,10 @@ class forum extends \phpbb\feed\post_base  		return true;  	} -	function adjust_item(&$item_row, &$row) +	/** +	 * {@inheritdoc} +	 */ +	public function adjust_item(&$item_row, &$row)  	{  		parent::adjust_item($item_row, $row); @@ -137,7 +150,10 @@ class forum extends \phpbb\feed\post_base  		$item_row['forum_id'] = $this->forum_id;  	} -	function get_item() +	/** +	 * {@inheritdoc} +	 */ +	public function get_item()  	{  		return ($row = parent::get_item()) ? array_merge($this->forum_data, $row) : $row;  	} diff --git a/phpBB/phpbb/feed/forums.php b/phpBB/phpbb/feed/forums.php index ee14a5bc76..92f2b2dd4d 100644 --- a/phpBB/phpbb/feed/forums.php +++ b/phpBB/phpbb/feed/forums.php @@ -1,29 +1,32 @@  <?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. -* -*/ + * + * 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\feed;  /** -* 'All Forums' feed -* -* This will give you a list of all postable forums where feeds are enabled -* including forum description, topic stats and post stats -*/ -class forums extends \phpbb\feed\base + * 'All Forums' feed + * + * This will give you a list of all postable forums where feeds are enabled + * including forum description, topic stats and post stats + */ +class forums extends base  { -	var $num_items	= 0; +	protected $num_items	= 0; -	function set_keys() +	/** +	 * {@inheritdoc} +	 */ +	public function set_keys()  	{  		$this->set('title',		'forum_name');  		$this->set('text',		'forum_desc'); @@ -33,7 +36,10 @@ class forums extends \phpbb\feed\base  		$this->set('options',	'forum_desc_options');  	} -	function get_sql() +	/** +	 * {@inheritdoc} +	 */ +	public function get_sql()  	{  		$in_fid_ary = array_diff($this->get_readable_forums(), $this->get_excluded_forums());  		if (empty($in_fid_ary)) @@ -55,7 +61,10 @@ class forums extends \phpbb\feed\base  		return true;  	} -	function adjust_item(&$item_row, &$row) +	/** +	 * {@inheritdoc} +	 */ +	public function adjust_item(&$item_row, &$row)  	{  		$item_row['link'] = $this->helper->append_sid('viewforum.' . $this->phpEx, 'f=' . $row['forum_id']); diff --git a/phpBB/phpbb/feed/helper.php b/phpBB/phpbb/feed/helper.php index f2030f5ced..e15d1e131e 100644 --- a/phpBB/phpbb/feed/helper.php +++ b/phpBB/phpbb/feed/helper.php @@ -1,21 +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. -* -*/ + * + * 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\feed;  /** -* Class with some helpful functions used in feeds -*/ + * Class with some helpful functions used in feeds + */  class helper  {  	/** @var \phpbb\config\config */ @@ -31,13 +31,13 @@ class helper  	protected $phpEx;  	/** -	* Constructor -	* -	* @param	\phpbb\config\config	$config		Config object -	* @param	\phpbb\user		$user		User object -	* @param	string	$phpbb_root_path	Root path -	* @param	string	$phpEx				PHP file extension -	*/ +	 * Constructor +	 * +	 * @param	\phpbb\config\config	$config		Config object +	 * @param	\phpbb\user		$user		User object +	 * @param	string	$phpbb_root_path	Root path +	 * @param	string	$phpEx				PHP file extension +	 */  	public function __construct(\phpbb\config\config $config, \phpbb\user $user, $phpbb_root_path, $phpEx)  	{  		$this->config = $config; @@ -47,8 +47,8 @@ class helper  	}  	/** -	* Run links through append_sid(), prepend generate_board_url() and remove session id -	*/ +	 * Run links through append_sid(), prepend generate_board_url() and remove session id +	 */  	public function get_board_url()  	{  		static $board_url; @@ -62,16 +62,16 @@ class helper  	}  	/** -	* Run links through append_sid(), prepend generate_board_url() and remove session id -	*/ +	 * Run links through append_sid(), prepend generate_board_url() and remove session id +	 */  	public function append_sid($url, $params)  	{  		return append_sid($this->get_board_url() . '/' . $url, $params, true, '');  	}  	/** -	* Generate ISO 8601 date string (RFC 3339) -	*/ +	 * Generate ISO 8601 date string (RFC 3339) +	 */  	public function format_date($time)  	{  		static $zone_offset; @@ -87,16 +87,16 @@ class helper  	}  	/** -	* Generate text content -	* -	* @param string $content is feed text content -	* @param string $uid is bbcode_uid -	* @param string $bitfield is bbcode bitfield -	* @param int $options bbcode flag options -	* @param int $forum_id is the forum id -	* @param array $post_attachments is an array containing the attachments and their respective info -	* @return string the html content to be printed for the feed -	*/ +	 * Generate text content +	 * +	 * @param string $content is feed text content +	 * @param string $uid is bbcode_uid +	 * @param string $bitfield is bbcode bitfield +	 * @param int $options bbcode flag options +	 * @param int $forum_id is the forum id +	 * @param array $post_attachments is an array containing the attachments and their respective info +	 * @return string the html content to be printed for the feed +	 */  	public function generate_content($content, $uid, $bitfield, $options, $forum_id, $post_attachments)  	{  		if (empty($content)) @@ -122,16 +122,16 @@ class helper  		// Firefox does not support CSS for feeds, though  		// Remove font sizes -	//	$content = preg_replace('#<span style="font-size: [0-9]+%; line-height: [0-9]+%;">([^>]+)</span>#iU', '\1', $content); +		//	$content = preg_replace('#<span style="font-size: [0-9]+%; line-height: [0-9]+%;">([^>]+)</span>#iU', '\1', $content);  		// Make text strong :P -	//	$content = preg_replace('#<span style="font-weight: bold?">(.*?)</span>#iU', '<strong>\1</strong>', $content); +		//	$content = preg_replace('#<span style="font-weight: bold?">(.*?)</span>#iU', '<strong>\1</strong>', $content);  		// Italic -	//	$content = preg_replace('#<span style="font-style: italic?">([^<]+)</span>#iU', '<em>\1</em>', $content); +		//	$content = preg_replace('#<span style="font-style: italic?">([^<]+)</span>#iU', '<em>\1</em>', $content);  		// Underline -	//	$content = preg_replace('#<span style="text-decoration: underline?">([^<]+)</span>#iU', '<u>\1</u>', $content); +		//	$content = preg_replace('#<span style="text-decoration: underline?">([^<]+)</span>#iU', '<u>\1</u>', $content);  		// Remove embed Windows Media Streams  		$content	= preg_replace( '#<\!--\[if \!IE\]>-->([^[]+)<\!--<!\[endif\]-->#si', '', $content); diff --git a/phpBB/phpbb/feed/news.php b/phpBB/phpbb/feed/news.php index a02c199d85..fb6fa09278 100644 --- a/phpBB/phpbb/feed/news.php +++ b/phpBB/phpbb/feed/news.php @@ -1,27 +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. -* -*/ + * + * 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\feed;  /** -* News feed -* -* This will give you {$this->num_items} first posts -* of all topics in the selected news forums. -*/ -class news extends \phpbb\feed\topic_base + * News feed + * + * This will give you {$this->num_items} first posts + * of all topics in the selected news forums. + */ +class news extends topic_base  { -	function get_news_forums() +	/** +	 * Returns the ids of the 'news forums' +	 * @return int[] +	 */ +	private function get_news_forums()  	{  		static $forum_ids; @@ -48,7 +52,10 @@ class news extends \phpbb\feed\topic_base  		return $forum_ids;  	} -	function get_sql() +	/** +	 * {@inheritdoc} +	 */ +	protected function get_sql()  	{  		// Determine forum ids  		$in_fid_ary = array_intersect($this->get_news_forums(), $this->get_readable_forums()); diff --git a/phpBB/phpbb/feed/overall.php b/phpBB/phpbb/feed/overall.php index ab452f5386..40cf94ace0 100644 --- a/phpBB/phpbb/feed/overall.php +++ b/phpBB/phpbb/feed/overall.php @@ -1,27 +1,30 @@  <?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. -* -*/ + * + * 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\feed;  /** -* Board wide feed (aka overall feed) -* -* This will give you the newest {$this->num_items} posts -* from the whole board. -*/ -class overall extends \phpbb\feed\post_base + * Board wide feed (aka overall feed) + * + * This will give you the newest {$this->num_items} posts + * from the whole board. + */ +class overall extends post_base  { -	function get_sql() +	/** +	 * {@inheritdoc} +	 */ +	protected function get_sql()  	{  		$forum_ids = array_diff($this->get_readable_forums(), $this->get_excluded_forums(), $this->get_passworded_forums());  		if (empty($forum_ids)) @@ -55,8 +58,8 @@ class overall extends \phpbb\feed\post_base  		// Get the actual data  		$this->sql = array(  			'SELECT'	=>	'f.forum_id, f.forum_name, ' . -							'p.post_id, p.topic_id, p.post_time, p.post_edit_time, p.post_visibility, p.post_subject, p.post_text, p.bbcode_bitfield, p.bbcode_uid, p.enable_bbcode, p.enable_smilies, p.enable_magic_url, p.post_attachment, ' . -							'u.username, u.user_id', +				'p.post_id, p.topic_id, p.post_time, p.post_edit_time, p.post_visibility, p.post_subject, p.post_text, p.bbcode_bitfield, p.bbcode_uid, p.enable_bbcode, p.enable_smilies, p.enable_magic_url, p.post_attachment, ' . +				'u.username, u.user_id',  			'FROM'		=> array(  				USERS_TABLE		=> 'u',  				POSTS_TABLE		=> 'p', @@ -77,7 +80,10 @@ class overall extends \phpbb\feed\post_base  		return true;  	} -	function adjust_item(&$item_row, &$row) +	/** +	 * {@inheritdoc} +	 */ +	public function adjust_item(&$item_row, &$row)  	{  		parent::adjust_item($item_row, $row); diff --git a/phpBB/phpbb/feed/post_base.php b/phpBB/phpbb/feed/post_base.php index 011775b6af..f6dc39cbec 100644 --- a/phpBB/phpbb/feed/post_base.php +++ b/phpBB/phpbb/feed/post_base.php @@ -1,27 +1,29 @@  <?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. -* -*/ + * + * 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\feed;  /** -* Abstract class for post based feeds -*/ -abstract class post_base extends \phpbb\feed\attachments_base + * Abstract class for post based feeds + */ +abstract class post_base extends attachments_base  { -	var $num_items = 'feed_limit_post'; -	var $attachments = array(); +	protected $num_items = 'feed_limit_post'; -	function set_keys() +	/** +	 * {@inheritdoc} +	 */ +	public function set_keys()  	{  		$this->set('title',		'post_subject');  		$this->set('title2',	'topic_title'); @@ -40,7 +42,10 @@ abstract class post_base extends \phpbb\feed\attachments_base  		$this->set('enable_magic_url',	'enable_magic_url');  	} -	function adjust_item(&$item_row, &$row) +	/** +	 * {@inheritdoc} +	 */ +	public function adjust_item(&$item_row, &$row)  	{  		$item_row['link'] = $this->helper->append_sid('viewtopic.' . $this->phpEx, "t={$row['topic_id']}&p={$row['post_id']}#p{$row['post_id']}"); diff --git a/phpBB/phpbb/feed/topic.php b/phpBB/phpbb/feed/topic.php index 66c49e55cf..f029c2b00e 100644 --- a/phpBB/phpbb/feed/topic.php +++ b/phpBB/phpbb/feed/topic.php @@ -1,35 +1,40 @@  <?php  /** -* -* This file is part of the phpBB Forum Software package. -* -* @copyright (c) phpBB Limited <https://www.phpbb.com> -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ + * + * 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\feed; +use phpbb\feed\exception\no_feed_exception; +use phpbb\feed\exception\no_topic_exception; +use phpbb\feed\exception\unauthorized_forum_exception; +use phpbb\feed\exception\unauthorized_topic_exception; +  /** -* Topic feed for a specific topic -* -* This will give you the last {$this->num_items} posts made within this topic. -*/ -class topic extends \phpbb\feed\post_base + * Topic feed for a specific topic + * + * This will give you the last {$this->num_items} posts made within this topic. + */ +class topic extends post_base  { -	var $topic_id		= 0; -	var $forum_id		= 0; -	var $topic_data		= array(); +	protected $topic_id		= 0; +	protected $forum_id		= 0; +	protected $topic_data	= array();  	/** -	* Set the Topic ID -	* -	* @param int	$topic_id			Topic ID -	* @return	\phpbb\feed\topic -	*/ +	 * Set the Topic ID +	 * +	 * @param int	$topic_id			Topic ID +	 * @return	\phpbb\feed\topic +	 */  	public function set_topic_id($topic_id)  	{  		$this->topic_id = (int) $topic_id; @@ -37,7 +42,10 @@ class topic extends \phpbb\feed\post_base  		return $this;  	} -	function open() +	/** +	 * {@inheritdoc} +	 */ +	public function open()  	{  		$sql = 'SELECT f.forum_options, f.forum_password, t.topic_id, t.forum_id, t.topic_visibility, t.topic_title, t.topic_time, t.topic_views, t.topic_posts_approved, t.topic_type  			FROM ' . TOPICS_TABLE . ' t @@ -50,7 +58,7 @@ class topic extends \phpbb\feed\post_base  		if (empty($this->topic_data))  		{ -			trigger_error('NO_TOPIC'); +			throw new no_topic_exception($this->topic_id);  		}  		$this->forum_id = (int) $this->topic_data['forum_id']; @@ -58,19 +66,19 @@ class topic extends \phpbb\feed\post_base  		// Make sure topic is either approved or user authed  		if ($this->topic_data['topic_visibility'] != ITEM_APPROVED && !$this->auth->acl_get('m_approve', $this->forum_id))  		{ -			trigger_error('SORRY_AUTH_READ'); +			throw new unauthorized_topic_exception($this->topic_id);  		}  		// Make sure forum is not excluded from feed  		if (phpbb_optionget(FORUM_OPTION_FEED_EXCLUDE, $this->topic_data['forum_options']))  		{ -			trigger_error('NO_FEED'); +			throw new no_feed_exception();  		}  		// Make sure we can read this forum  		if (!$this->auth->acl_get('f_read', $this->forum_id))  		{ -			trigger_error('SORRY_AUTH_READ'); +			throw new unauthorized_forum_exception($this->forum_id);  		}  		// Make sure forum is not passworded or user is authed @@ -80,7 +88,7 @@ class topic extends \phpbb\feed\post_base  			if (isset($forum_ids_passworded[$this->forum_id]))  			{ -				trigger_error('SORRY_AUTH_READ'); +				throw new unauthorized_forum_exception($this->forum_id);  			}  			unset($forum_ids_passworded); @@ -89,11 +97,14 @@ class topic extends \phpbb\feed\post_base  		parent::open();  	} -	function get_sql() +	/** +	 * {@inheritdoc} +	 */ +	protected function get_sql()  	{  		$this->sql = array(  			'SELECT'	=>	'p.post_id, p.post_time, p.post_edit_time, p.post_visibility, p.post_subject, p.post_text, p.bbcode_bitfield, p.bbcode_uid, p.enable_bbcode, p.enable_smilies, p.enable_magic_url, p.post_attachment, ' . -							'u.username, u.user_id', +				'u.username, u.user_id',  			'FROM'		=> array(  				POSTS_TABLE		=> 'p',  				USERS_TABLE		=> 'u', @@ -107,14 +118,20 @@ class topic extends \phpbb\feed\post_base  		return true;  	} -	function adjust_item(&$item_row, &$row) +	/** +	 * {@inheritdoc} +	 */ +	public function adjust_item(&$item_row, &$row)  	{  		parent::adjust_item($item_row, $row);  		$item_row['forum_id'] = $this->forum_id;  	} -	function get_item() +	/** +	 * {@inheritdoc} +	 */ +	public function get_item()  	{  		return ($row = parent::get_item()) ? array_merge($this->topic_data, $row) : $row;  	} diff --git a/phpBB/phpbb/feed/topic_base.php b/phpBB/phpbb/feed/topic_base.php index f9ff368cba..0f1a9ccb70 100644 --- a/phpBB/phpbb/feed/topic_base.php +++ b/phpBB/phpbb/feed/topic_base.php @@ -1,26 +1,29 @@  <?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. -* -*/ + * + * 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\feed;  /** -* Abstract class for topic based feeds -*/ -abstract class topic_base extends \phpbb\feed\attachments_base + * Abstract class for topic based feeds + */ +abstract class topic_base extends attachments_base  { -	var $num_items = 'feed_limit_topic'; +	protected $num_items = 'feed_limit_topic'; -	function set_keys() +	/** +	 * {@inheritdoc} +	 */ +	public function set_keys()  	{  		$this->set('title',		'topic_title');  		$this->set('title2',	'forum_name'); @@ -39,7 +42,10 @@ abstract class topic_base extends \phpbb\feed\attachments_base  		$this->set('enable_magic_url',	'enable_magic_url');  	} -	function adjust_item(&$item_row, &$row) +	/** +	 * {@inheritdoc} +	 */ +	public function adjust_item(&$item_row, &$row)  	{  		$item_row['link'] = $this->helper->append_sid('viewtopic.' . $this->phpEx, 't=' . $row['topic_id'] . '&p=' . $row['post_id'] . '#p' . $row['post_id']); diff --git a/phpBB/phpbb/feed/topics.php b/phpBB/phpbb/feed/topics.php index 2b9cb3501a..cf4a2e579e 100644 --- a/phpBB/phpbb/feed/topics.php +++ b/phpBB/phpbb/feed/topics.php @@ -1,27 +1,30 @@  <?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. -* -*/ + * + * 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\feed;  /** -* New Topics feed -* -* This will give you the last {$this->num_items} created topics -* including the first post. -*/ -class topics extends \phpbb\feed\topic_base + * New Topics feed + * + * This will give you the last {$this->num_items} created topics + * including the first post. + */ +class topics extends topic_base  { -	function get_sql() +	/** +	 * {@inheritdoc} +	 */ +	protected function get_sql()  	{  		$forum_ids_read = $this->get_readable_forums();  		if (empty($forum_ids_read)) @@ -77,7 +80,10 @@ class topics extends \phpbb\feed\topic_base  		return true;  	} -	function adjust_item(&$item_row, &$row) +	/** +	 * {@inheritdoc} +	 */ +	public function adjust_item(&$item_row, &$row)  	{  		parent::adjust_item($item_row, $row); diff --git a/phpBB/phpbb/feed/topics_active.php b/phpBB/phpbb/feed/topics_active.php index 6d5eddfc16..52340dc2d5 100644 --- a/phpBB/phpbb/feed/topics_active.php +++ b/phpBB/phpbb/feed/topics_active.php @@ -1,30 +1,33 @@  <?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. -* -*/ + * + * 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\feed;  /** -* Active Topics feed -* -* This will give you the last {$this->num_items} topics -* with replies made withing the last {$this->sort_days} days -* including the last post. -*/ -class topics_active extends \phpbb\feed\topic_base + * Active Topics feed + * + * This will give you the last {$this->num_items} topics + * with replies made withing the last {$this->sort_days} days + * including the last post. + */ +class topics_active extends topic_base  { -	var $sort_days = 7; +	protected $sort_days = 7; -	function set_keys() +	/** +	 * {@inheritdoc} +	 */ +	public function set_keys()  	{  		parent::set_keys(); @@ -32,7 +35,10 @@ class topics_active extends \phpbb\feed\topic_base  		$this->set('creator',	'topic_last_poster_name');  	} -	function get_sql() +	/** +	 * {@inheritdoc} +	 */ +	protected function get_sql()  	{  		$forum_ids_read = $this->get_readable_forums();  		if (empty($forum_ids_read)) @@ -94,7 +100,12 @@ class topics_active extends \phpbb\feed\topic_base  		return true;  	} -	function get_forum_ids() +	/** +	 * Returns the ids of the forums not excluded from the active list +	 * +	 * @return int[] +	 */ +	private function get_forum_ids()  	{  		static $forum_ids; @@ -122,7 +133,10 @@ class topics_active extends \phpbb\feed\topic_base  		return $forum_ids;  	} -	function adjust_item(&$item_row, &$row) +	/** +	 * {@inheritdoc} +	 */ +	public function adjust_item(&$item_row, &$row)  	{  		parent::adjust_item($item_row, $row); diff --git a/phpBB/phpbb/files/factory.php b/phpBB/phpbb/files/factory.php new file mode 100644 index 0000000000..84b7cc9449 --- /dev/null +++ b/phpBB/phpbb/files/factory.php @@ -0,0 +1,58 @@ +<?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\files; + +class factory +{ +	/** +	 * @var \Symfony\Component\DependencyInjection\ContainerInterface +	 */ +	private $container; + +	/** +	 * Constructor +	 * +	 * @param \Symfony\Component\DependencyInjection\ContainerInterface $container +	 */ +	public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container) +	{ +		$this->container = $container; +	} + +	/** +	 * Get files service +	 * +	 * @param string $name Service name +	 * +	 * @return object|bool Requested service or false if service could not be +	 *				found by the container +	 */ +	public function get($name) +	{ +		$service = false; + +		$name = (strpos($name, '.') === false) ? 'files.' . $name : $name; + +		try +		{ +			$service = $this->container->get($name); +		} +		catch (\Exception $e) +		{ +			// do nothing +		} + +		return $service; +	} +} diff --git a/phpBB/phpbb/files/filespec.php b/phpBB/phpbb/files/filespec.php new file mode 100644 index 0000000000..2ff2a92c83 --- /dev/null +++ b/phpBB/phpbb/files/filespec.php @@ -0,0 +1,584 @@ +<?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\files; + +use phpbb\language\language; + +/** + * Responsible for holding all file relevant information, as well as doing file-specific operations. + * The {@link fileupload fileupload class} can be used to upload several files, each of them being this object to operate further on. + */ +class filespec +{ +	/** @var string File name */ +	protected $filename = ''; + +	/** @var string Real name of file */ +	protected $realname = ''; + +	/** @var string Upload name of file */ +	protected $uploadname = ''; + +	/** @var string Mimetype of file */ +	protected $mimetype = ''; + +	/** @var string File extension */ +	protected $extension = ''; + +	/** @var int File size */ +	protected $filesize = 0; + +	/** @var int Width of file */ +	protected $width = 0; + +	/** @var int Height of file */ +	protected $height = 0; + +	/** @var array Image info including type and size */ +	protected $image_info = array(); + +	/** @var string Destination file name */ +	protected $destination_file = ''; + +	/** @var string Destination file path */ +	protected $destination_path = ''; + +	/** @var bool Whether file was moved */ +	protected $file_moved = false; + +	/** @var bool Whether file is local */ +	protected $local = false; + +	/** @var bool Class initialization flag */ +	protected $class_initialized = false; + +	/** @var array Error array */ +	public $error = array(); + +	/** @var upload Instance of upload class  */ +	public $upload; + +	/** @var \phpbb\filesystem\filesystem_interface */ +	protected $filesystem; + +	/** @var \bantu\IniGetWrapper\IniGetWrapper ini_get() wrapper class */ +	protected $php_ini; + +	/** @var \FastImageSize\FastImageSize */ +	protected $imagesize; + +	/** @var language Language class */ +	protected $language; + +	/** @var string phpBB root path */ +	protected $phpbb_root_path; + +	/** @var \phpbb\plupload\plupload The plupload object */ +	protected $plupload; + +	/** @var \phpbb\mimetype\guesser phpBB Mimetype guesser */ +	protected $mimetype_guesser; + +	/** +	 * File upload class +	 * +	 * @param \phpbb\filesystem\filesystem_interface	$phpbb_filesystem Filesystem +	 * @param language					$language Language +	 * @param \bantu\IniGetWrapper\IniGetWrapper			$php_ini ini_get() wrapper +	 * @param \FastImageSize\FastImageSize $imagesize Imagesize class +	 * @param string					$phpbb_root_path phpBB root path +	 * @param \phpbb\mimetype\guesser	$mimetype_guesser Mime type guesser +	 * @param \phpbb\plupload\plupload	$plupload Plupload +	 */ +	public function __construct(\phpbb\filesystem\filesystem_interface $phpbb_filesystem, language $language, \bantu\IniGetWrapper\IniGetWrapper $php_ini, \FastImageSize\FastImageSize $imagesize, $phpbb_root_path, \phpbb\mimetype\guesser $mimetype_guesser = null, \phpbb\plupload\plupload $plupload = null) +	{ +		$this->filesystem = $phpbb_filesystem; +		$this->language = $language; +		$this->php_ini = $php_ini; +		$this->imagesize = $imagesize; +		$this->phpbb_root_path = $phpbb_root_path; +		$this->plupload = $plupload; +		$this->mimetype_guesser = $mimetype_guesser; +	} + +	/** +	 * Set upload ary +	 * +	 * @param array $upload_ary Upload ary +	 * +	 * @return filespec This instance of the filespec class +	 */ +	public function set_upload_ary($upload_ary) +	{ +		if (!isset($upload_ary) || !sizeof($upload_ary)) +		{ +			return $this; +		} + +		$this->class_initialized = true; +		$this->filename = $upload_ary['tmp_name']; +		$this->filesize = $upload_ary['size']; +		$name = (STRIP) ? stripslashes($upload_ary['name']) : $upload_ary['name']; +		$name = trim(utf8_basename($name)); +		$this->realname = $this->uploadname = $name; +		$this->mimetype = $upload_ary['type']; + +		// Opera adds the name to the mime type +		$this->mimetype	= (strpos($this->mimetype, '; name') !== false) ? str_replace(strstr($this->mimetype, '; name'), '', $this->mimetype) : $this->mimetype; + +		if (!$this->mimetype) +		{ +			$this->mimetype = 'application/octet-stream'; +		} + +		$this->extension = strtolower(self::get_extension($this->realname)); + +		// Try to get real filesize from temporary folder (not always working) ;) +		$this->filesize = ($this->get_filesize($this->filename)) ?: $this->filesize; + +		$this->width = $this->height = 0; +		$this->file_moved = false; + +		$this->local = (isset($upload_ary['local_mode'])) ? true : false; + +		return $this; +	} + +	/** +	 * Set the upload namespace +	 * +	 * @param upload $namespace Instance of upload class +	 * +	 * @return filespec This instance of the filespec class +	 */ +	public function set_upload_namespace($namespace) +	{ +		$this->upload = $namespace; + +		return $this; +	} + +	/** +	 * Check if class members were not properly initialised yet +	 * +	 * @return bool True if there was an init error, false if not +	 */ +	public function init_error() +	{ +		return !$this->class_initialized; +	} + +	/** +	 * Set error in error array +	 * +	 * @param mixed $error Content for error array +	 * +	 * @return \phpbb\files\filespec This instance of the filespec class +	 */ +	public function set_error($error) +	{ +		$this->error[] = $error; + +		return $this; +	} + +	/** +	 * Cleans destination filename +	 * +	 * @param string $mode Either real, unique, or unique_ext. Real creates a +	 *				realname, filtering some characters, lowering every +	 *				character. Unique creates a unique filename. +	 * @param string $prefix Prefix applied to filename +	 * @param string $user_id The user_id is only needed for when cleaning a user's avatar +	 */ +	public function clean_filename($mode = 'unique', $prefix = '', $user_id = '') +	{ +		if ($this->init_error()) +		{ +			return; +		} + +		switch ($mode) +		{ +			case 'real': +				// Remove every extension from filename (to not let the mime bug being exposed) +				if (strpos($this->realname, '.') !== false) +				{ +					$this->realname = substr($this->realname, 0, strpos($this->realname, '.')); +				} + +				// Replace any chars which may cause us problems with _ +				$bad_chars = array("'", "\\", ' ', '/', ':', '*', '?', '"', '<', '>', '|'); + +				$this->realname = rawurlencode(str_replace($bad_chars, '_', strtolower($this->realname))); +				$this->realname = preg_replace("/%(\w{2})/", '_', $this->realname); + +				$this->realname = $prefix . $this->realname . '.' . $this->extension; +			break; + +			case 'unique': +				$this->realname = $prefix . md5(unique_id()); +			break; + +			case 'avatar': +				$this->extension = strtolower($this->extension); +				$this->realname = $prefix . $user_id . '.' . $this->extension; + +			break; + +			case 'unique_ext': +			default: +				$this->realname = $prefix . md5(unique_id()) . '.' . $this->extension; +		} +	} + +	/** +	 * Get property from file object +	 * +	 * @param string $property Name of property +	 * +	 * @return mixed Content of property +	 */ +	public function get($property) +	{ +		if ($this->init_error() || !isset($this->$property)) +		{ +			return false; +		} + +		return $this->$property; +	} + +	/** +	 * Check if file is an image (mime type) +	 * +	 * @return bool true if it is an image, false if not +	 */ +	public function is_image() +	{ +		return (strpos($this->mimetype, 'image/') === 0); +	} + +	/** +	 * Check if the file got correctly uploaded +	 * +	 * @return bool true if it is a valid upload, false if not +	 */ +	public function is_uploaded() +	{ +		$is_plupload = $this->plupload && $this->plupload->is_active(); + +		if (!$this->local && !$is_plupload && !is_uploaded_file($this->filename)) +		{ +			return false; +		} + +		if (($this->local || $is_plupload) && !file_exists($this->filename)) +		{ +			return false; +		} + +		return true; +	} + +	/** +	 * Remove file +	 */ +	public function remove() +	{ +		if ($this->file_moved) +		{ +			@unlink($this->destination_file); +		} +	} + +	/** +	 * Get file extension +	 * +	 * @param string $filename Filename that needs to be checked +	 * +	 * @return string Extension of the supplied filename +	 */ +	static public function get_extension($filename) +	{ +		$filename = utf8_basename($filename); + +		if (strpos($filename, '.') === false) +		{ +			return ''; +		} + +		$filename = explode('.', $filename); +		return array_pop($filename); +	} + +	/** +	 * Get mime type +	 * +	 * @param string $filename Filename that needs to be checked +	 * @return string Mime type of supplied filename +	 */ +	public function get_mimetype($filename) +	{ +		if ($this->mimetype_guesser !== null) +		{ +			$mimetype = $this->mimetype_guesser->guess($filename, $this->uploadname); + +			if ($mimetype !== 'application/octet-stream') +			{ +				$this->mimetype = $mimetype; +			} +		} + +		return $this->mimetype; +	} + +	/** +	 * Get file size +	 * +	 * @param string $filename File name of file to check +	 * +	 * @return int File size +	 */ +	public function get_filesize($filename) +	{ +		return @filesize($filename); +	} + + +	/** +	 * Check the first 256 bytes for forbidden content +	 * +	 * @param array $disallowed_content Array containg disallowed content +	 * +	 * @return bool False if disallowed content found, true if not +	 */ +	public function check_content($disallowed_content) +	{ +		if (empty($disallowed_content)) +		{ +			return true; +		} + +		$fp = @fopen($this->filename, 'rb'); + +		if ($fp !== false) +		{ +			$ie_mime_relevant = fread($fp, 256); +			fclose($fp); +			foreach ($disallowed_content as $forbidden) +			{ +				if (stripos($ie_mime_relevant, '<' . $forbidden) !== false) +				{ +					return false; +				} +			} +		} +		return true; +	} + +	/** +	 * Move file to destination folder +	 * The phpbb_root_path variable will be applied to the destination path +	 * +	 * @param string $destination Destination path, for example $config['avatar_path'] +	 * @param bool $overwrite If set to true, an already existing file will be overwritten +	 * @param bool $skip_image_check If set to true, the check for the file to be a valid image is skipped +	 * @param string|bool $chmod Permission mask for chmodding the file after a successful move. +	 *				The mode entered here reflects the mode defined by {@link phpbb_chmod()} +	 * +	 * @return bool True if file was moved, false if not +	 * @access public +	 */ +	public function move_file($destination, $overwrite = false, $skip_image_check = false, $chmod = false) +	{ +		if (sizeof($this->error)) +		{ +			return false; +		} + +		$chmod = ($chmod === false) ? CHMOD_READ | CHMOD_WRITE : $chmod; + +		// We need to trust the admin in specifying valid upload directories and an attacker not being able to overwrite it... +		$this->destination_path = $this->phpbb_root_path . $destination; + +		// Check if the destination path exist... +		if (!file_exists($this->destination_path)) +		{ +			@unlink($this->filename); +			return false; +		} + +		$upload_mode = ($this->php_ini->getBool('open_basedir') || $this->php_ini->getBool('safe_mode')) ? 'move' : 'copy'; +		$upload_mode = ($this->local) ? 'local' : $upload_mode; +		$this->destination_file = $this->destination_path . '/' . utf8_basename($this->realname); + +		// Check if the file already exist, else there is something wrong... +		if (file_exists($this->destination_file) && !$overwrite) +		{ +			@unlink($this->filename); +			$this->error[] = $this->language->lang($this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR', $this->destination_file); +			$this->file_moved = false; +			return false; +		} +		else +		{ +			if (file_exists($this->destination_file)) +			{ +				@unlink($this->destination_file); +			} + +			switch ($upload_mode) +			{ +				case 'copy': + +					if (!@copy($this->filename, $this->destination_file)) +					{ +						if (!@move_uploaded_file($this->filename, $this->destination_file)) +						{ +							$this->error[] = $this->language->lang($this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR', $this->destination_file); +						} +					} + +				break; + +				case 'move': + +					if (!@move_uploaded_file($this->filename, $this->destination_file)) +					{ +						if (!@copy($this->filename, $this->destination_file)) +						{ +							$this->error[] = $this->language->lang($this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR', $this->destination_file); +						} +					} + +				break; + +				case 'local': + +					if (!@copy($this->filename, $this->destination_file)) +					{ +						$this->error[] = $this->language->lang($this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR', $this->destination_file); +					} + +				break; +			} + +			// Remove temporary filename +			@unlink($this->filename); + +			if (sizeof($this->error)) +			{ +				return false; +			} + +			try +			{ +				$this->filesystem->phpbb_chmod($this->destination_file, $chmod); +			} +			catch (\phpbb\filesystem\exception\filesystem_exception $e) +			{ +				// Do nothing +			} +		} + +		// Try to get real filesize from destination folder +		$this->filesize = ($this->get_filesize($this->destination_file)) ?: $this->filesize; + +		// Get mimetype of supplied file +		$this->mimetype = $this->get_mimetype($this->destination_file); + +		if ($this->is_image() && !$skip_image_check) +		{ +			$this->width = $this->height = 0; + +			$this->image_info = $this->imagesize->getImageSize($this->destination_file, $this->mimetype); + +			if ($this->image_info !== false) +			{ +				$this->width = $this->image_info['width']; +				$this->height = $this->image_info['height']; + +				// Check image type +				$types = upload::image_types(); + +				if (!isset($types[$this->image_info['type']]) || !in_array($this->extension, $types[$this->image_info['type']])) +				{ +					if (!isset($types[$this->image_info['type']])) +					{ +						$this->error[] = $this->language->lang('IMAGE_FILETYPE_INVALID', $this->image_info['type'], $this->mimetype); +					} +					else +					{ +						$this->error[] = $this->language->lang('IMAGE_FILETYPE_MISMATCH', $types[$this->image_info['type']][0], $this->extension); +					} +				} + +				// Make sure the dimensions match a valid image +				if (empty($this->width) || empty($this->height)) +				{ +					$this->error[] = $this->language->lang('ATTACHED_IMAGE_NOT_IMAGE'); +				} +			} +			else +			{ +				$this->error[] = $this->language->lang('UNABLE_GET_IMAGE_SIZE'); +			} +		} + +		$this->file_moved = true; +		$this->additional_checks(); +		unset($this->upload); + +		return true; +	} + +	/** +	 * Performing additional checks +	 * +	 * @return bool False if issue was found, true if not +	 */ +	public function additional_checks() +	{ +		if (!$this->file_moved) +		{ +			return false; +		} + +		// Filesize is too big or it's 0 if it was larger than the maxsize in the upload form +		if ($this->upload->max_filesize && ($this->get('filesize') > $this->upload->max_filesize || $this->filesize == 0)) +		{ +			$max_filesize = get_formatted_filesize($this->upload->max_filesize, false); + +			$this->error[] = $this->language->lang($this->upload->error_prefix . 'WRONG_FILESIZE', $max_filesize['value'], $max_filesize['unit']); + +			return false; +		} + +		if (!$this->upload->valid_dimensions($this)) +		{ +			$this->error[] = $this->language->lang($this->upload->error_prefix . 'WRONG_SIZE', +				$this->language->lang('PIXELS', (int) $this->upload->min_width), +				$this->language->lang('PIXELS', (int) $this->upload->min_height), +				$this->language->lang('PIXELS', (int) $this->upload->max_width), +				$this->language->lang('PIXELS', (int) $this->upload->max_height), +				$this->language->lang('PIXELS', (int) $this->width), +				$this->language->lang('PIXELS', (int) $this->height)); + +			return false; +		} + +		return true; +	} +} diff --git a/phpBB/phpbb/files/types/base.php b/phpBB/phpbb/files/types/base.php new file mode 100644 index 0000000000..3313ad040b --- /dev/null +++ b/phpBB/phpbb/files/types/base.php @@ -0,0 +1,65 @@ +<?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\files\types; + +abstract class base implements type_interface +{ +	/** @var \phpbb\language\language */ +	protected $language; + +	/** @var \bantu\IniGetWrapper\IniGetWrapper */ +	protected $php_ini; + +	/** @var \phpbb\files\upload */ +	protected $upload; + +	/** +	 * Check if upload exceeds maximum file size +	 * +	 * @param \phpbb\files\filespec $file Filespec object +	 * +	 * @return \phpbb\files\filespec Returns same filespec instance +	 */ +	public function check_upload_size($file) +	{ +		// PHP Upload filesize exceeded +		if ($file->get('filename') == 'none') +		{ +			$max_filesize = $this->php_ini->getString('upload_max_filesize'); +			$unit = 'MB'; + +			if (!empty($max_filesize)) +			{ +				$unit = strtolower(substr($max_filesize, -1, 1)); +				$max_filesize = (int) $max_filesize; + +				$unit = ($unit == 'k') ? 'KB' : (($unit == 'g') ? 'GB' : 'MB'); +			} + +			$file->error[] = (empty($max_filesize)) ? $this->language->lang($this->upload->error_prefix . 'PHP_SIZE_NA') : $this->language->lang($this->upload->error_prefix . 'PHP_SIZE_OVERRUN', $max_filesize, $this->language->lang($unit)); +		} + +		return $file; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function set_upload(\phpbb\files\upload $upload) +	{ +		$this->upload = $upload; + +		return $this; +	} +} diff --git a/phpBB/phpbb/files/types/form.php b/phpBB/phpbb/files/types/form.php new file mode 100644 index 0000000000..832f090c47 --- /dev/null +++ b/phpBB/phpbb/files/types/form.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\files\types; + +use bantu\IniGetWrapper\IniGetWrapper; +use phpbb\files\factory; +use phpbb\files\filespec; +use phpbb\language\language; +use phpbb\plupload\plupload; +use phpbb\request\request_interface; + +class form extends base +{ +	/** @var factory Files factory */ +	protected $factory; + +	/** @var language */ +	protected $language; + +	/** @var IniGetWrapper */ +	protected $php_ini; + +	/** @var plupload */ +	protected $plupload; + +	/** @var request_interface */ +	protected $request; + +	/** @var \phpbb\files\upload */ +	protected $upload; + +	/** +	 * Construct a form upload type +	 * +	 * @param factory			$factory	Files factory +	 * @param language			$language	Language class +	 * @param IniGetWrapper		$php_ini	ini_get() wrapper +	 * @param plupload			$plupload	Plupload +	 * @param request_interface	$request	Request object +	 */ +	public function __construct(factory $factory, language $language, IniGetWrapper $php_ini, plupload $plupload, request_interface $request) +	{ +		$this->factory = $factory; +		$this->language = $language; +		$this->php_ini = $php_ini; +		$this->plupload = $plupload; +		$this->request = $request; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function upload() +	{ +		$args = func_get_args(); +		return $this->form_upload($args[0]); +	} + +	/** +	 * Form upload method +	 * Upload file from users harddisk +	 * +	 * @param string $form_name Form name assigned to the file input field (if it is an array, the key has to be specified) +	 * +	 * @return filespec $file Object "filespec" is returned, all further operations can be done with this object +	 * @access public +	 */ +	protected function form_upload($form_name) +	{ +		$upload = $this->request->file($form_name); +		unset($upload['local_mode']); + +		$result = $this->plupload->handle_upload($form_name); +		if (is_array($result)) +		{ +			$upload = array_merge($upload, $result); +		} + +		/** @var filespec $file */ +		$file = $this->factory->get('filespec') +			->set_upload_ary($upload) +			->set_upload_namespace($this->upload); + +		if ($file->init_error()) +		{ +			$file->error[] = ''; +			return $file; +		} + +		// Error array filled? +		if (isset($upload['error'])) +		{ +			$error = $this->upload->assign_internal_error($upload['error']); + +			if ($error !== false) +			{ +				$file->error[] = $error; +				return $file; +			} +		} + +		// Check if empty file got uploaded (not catched by is_uploaded_file) +		if (isset($upload['size']) && $upload['size'] == 0) +		{ +			$file->error[] = $this->language->lang($this->upload->error_prefix . 'EMPTY_FILEUPLOAD'); +			return $file; +		} + +		// PHP Upload file size check +		$file = $this->check_upload_size($file); +		if (sizeof($file->error)) +		{ +			return $file; +		} + +		// Not correctly uploaded +		if (!$file->is_uploaded()) +		{ +			$file->error[] = $this->language->lang($this->upload->error_prefix . 'NOT_UPLOADED'); +			return $file; +		} + +		$this->upload->common_checks($file); + +		return $file; +	} +} diff --git a/phpBB/phpbb/files/types/local.php b/phpBB/phpbb/files/types/local.php new file mode 100644 index 0000000000..7e9210b196 --- /dev/null +++ b/phpBB/phpbb/files/types/local.php @@ -0,0 +1,136 @@ +<?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\files\types; + +use bantu\IniGetWrapper\IniGetWrapper; +use phpbb\files\factory; +use phpbb\files\filespec; +use phpbb\language\language; +use phpbb\request\request_interface; + +class local extends base +{ +	/** @var factory Files factory */ +	protected $factory; + +	/** @var language */ +	protected $language; + +	/** @var IniGetWrapper */ +	protected $php_ini; + +	/** @var request_interface */ +	protected $request; + +	/** @var \phpbb\files\upload */ +	protected $upload; + +	/** +	 * Construct a form upload type +	 * +	 * @param factory $factory Files factory +	 * @param language $language Language class +	 * @param IniGetWrapper $php_ini ini_get() wrapper +	 * @param request_interface $request Request object +	 */ +	public function __construct(factory $factory, language $language, IniGetWrapper $php_ini, request_interface $request) +	{ +		$this->factory = $factory; +		$this->language = $language; +		$this->php_ini = $php_ini; +		$this->request = $request; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function upload() +	{ +		$args = func_get_args(); +		return $this->local_upload($args[0], isset($args[1]) ? $args[1] : false); +	} + +	/** +	 * Move file from another location to phpBB +	 * +	 * @param string $source_file Filename of source file +	 * @param array|bool $filedata Array with filedata or false +	 * +	 * @return filespec Object "filespec" is returned, all further operations can be done with this object +	 */ +	protected function local_upload($source_file, $filedata = false) +	{ +		$upload = $this->get_upload_ary($source_file, $filedata); + +		/** @var filespec $file */ +		$file = $this->factory->get('filespec') +			->set_upload_ary($upload) +			->set_upload_namespace($this->upload); + +		if ($file->init_error()) +		{ +			$file->error[] = ''; +			return $file; +		} + +		// PHP Upload file size check +		$file = $this->check_upload_size($file); +		if (sizeof($file->error)) +		{ +			return $file; +		} + +		// Not correctly uploaded +		if (!$file->is_uploaded()) +		{ +			$file->error[] = $this->language->lang($this->upload->error_prefix . 'NOT_UPLOADED'); +			return $file; +		} + +		$this->upload->common_checks($file); +		$this->request->overwrite('local', $upload, request_interface::FILES); + +		return $file; +	} + +	/** +	 * Retrieve upload array +	 * +	 * @param string $source_file Source file name +	 * @param array $filedata File data array +	 * +	 * @return array Upload array +	 */ +	protected function get_upload_ary($source_file, $filedata) +	{ +		$upload = array(); + +		$upload['local_mode'] = true; +		$upload['tmp_name'] = $source_file; + +		if ($filedata === false) +		{ +			$upload['name'] = utf8_basename($source_file); +			$upload['size'] = 0; +		} +		else +		{ +			$upload['name'] = $filedata['realname']; +			$upload['size'] = $filedata['size']; +			$upload['type'] = $filedata['type']; +		} + +		return $upload; +	} +} diff --git a/phpBB/phpbb/files/types/remote.php b/phpBB/phpbb/files/types/remote.php new file mode 100644 index 0000000000..1fdba0ca32 --- /dev/null +++ b/phpBB/phpbb/files/types/remote.php @@ -0,0 +1,207 @@ +<?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\files\types; + +use bantu\IniGetWrapper\IniGetWrapper; +use phpbb\config\config; +use phpbb\files\factory; +use phpbb\files\filespec; +use phpbb\language\language; +use phpbb\request\request_interface; + +class remote extends base +{ +	/** @var config phpBB config */ +	protected $config; + +	/** @var factory Files factory */ +	protected $factory; + +	/** @var language */ +	protected $language; + +	/** @var IniGetWrapper */ +	protected $php_ini; + +	/** @var request_interface */ +	protected $request; + +	/** @var \phpbb\files\upload */ +	protected $upload; + +	/** @var string phpBB root path */ +	protected $phpbb_root_path; + +	/** +	 * Construct a form upload type +	 * +	 * @param config $config phpBB config +	 * @param factory $factory Files factory +	 * @param language $language Language class +	 * @param IniGetWrapper $php_ini ini_get() wrapper +	 * @param request_interface $request Request object +	 * @param string $phpbb_root_path phpBB root path +	 */ +	public function __construct(config $config, factory $factory, language $language, IniGetWrapper $php_ini, request_interface $request, $phpbb_root_path) +	{ +		$this->config = $config; +		$this->factory = $factory; +		$this->language = $language; +		$this->php_ini = $php_ini; +		$this->request = $request; +		$this->phpbb_root_path = $phpbb_root_path; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function upload() +	{ +		$args = func_get_args(); +		return $this->remote_upload($args[0]); +	} + +	/** +	 * Remote upload method +	 * Uploads file from given url +	 * +	 * @param string $upload_url URL pointing to file to upload, for example http://www.foobar.com/example.gif +	 * @return filespec $file Object "filespec" is returned, all further operations can be done with this object +	 * @access public +	 */ +	protected function remote_upload($upload_url) +	{ +		$upload_ary = array(); +		$upload_ary['local_mode'] = true; + +		if (!preg_match('#^(https?://).*?\.(' . implode('|', $this->upload->allowed_extensions) . ')$#i', $upload_url, $match)) +		{ +			return $this->factory->get('filespec')->set_error($this->language->lang($this->upload->error_prefix . 'URL_INVALID')); +		} + +		$url = parse_url($upload_url); + +		$upload_ary['type'] = 'application/octet-stream'; + +		$url['path'] = explode('.', $url['path']); +		$ext = array_pop($url['path']); + +		$url['path'] = implode('', $url['path']); +		$upload_ary['name'] = utf8_basename($url['path']) . (($ext) ? '.' . $ext : ''); + +		$remote_max_filesize = $this->get_max_file_size(); + +		$guzzle_options = [ +			'timeout'			=> $this->upload->upload_timeout, +			'connect_timeout'	=> $this->upload->upload_timeout, +			'verify'			=> !empty($this->config['remote_upload_verify']) ? (bool) $this->config['remote_upload_verify'] : false, +		]; +		$client = new \GuzzleHttp\Client($guzzle_options); + +		try +		{ +			$response = $client->get($upload_url, $guzzle_options); +		} +		catch (\GuzzleHttp\Exception\ClientException $clientException) +		{ +			return $this->factory->get('filespec')->set_error($this->upload->error_prefix . 'URL_NOT_FOUND'); +		} +		catch (\GuzzleHttp\Exception\RequestException $requestException) +		{ +			if (strpos($requestException->getMessage(), 'cURL error 28') !== false || preg_match('/408|504/', $requestException->getCode())) +			{ +				return $this->factory->get('filespec')->set_error($this->upload->error_prefix . 'REMOTE_UPLOAD_TIMEOUT'); +			} +			else +			{ +				return $this->factory->get('filespec')->set_error($this->language->lang($this->upload->error_prefix . 'NOT_UPLOADED')); +			} +		} +		catch (\Exception $e) +		{ +			return $this->factory->get('filespec')->set_error($this->language->lang($this->upload->error_prefix . 'NOT_UPLOADED')); +		} + +		$content_length = $response->getBody()->getSize(); +		if ($remote_max_filesize && $content_length > $remote_max_filesize) +		{ +			$max_filesize = get_formatted_filesize($remote_max_filesize, false); + +			return $this->factory->get('filespec')->set_error($this->language->lang($this->upload->error_prefix . 'WRONG_FILESIZE', $max_filesize['value'], $max_filesize['unit'])); +		} + +		if ($content_length == 0) +		{ +			return $this->factory->get('filespec')->set_error($this->upload->error_prefix . 'EMPTY_REMOTE_DATA'); +		} + +		$data = $response->getBody(); + +		$filename = tempnam(sys_get_temp_dir(), unique_id() . '-'); + +		if (!($fp = @fopen($filename, 'wb'))) +		{ +			return $this->factory->get('filespec')->set_error($this->upload->error_prefix . 'NOT_UPLOADED'); +		} + +		$upload_ary['size'] = fwrite($fp, $data); +		fclose($fp); +		unset($data); + +		$upload_ary['tmp_name'] = $filename; + +		/** @var filespec $file */ +		$file = $this->factory->get('filespec') +			->set_upload_ary($upload_ary) +			->set_upload_namespace($this->upload); +		$this->upload->common_checks($file); + +		return $file; +	} + +	/** +	 * Get maximum file size for remote uploads +	 * +	 * @return int Maximum file size +	 */ +	protected function get_max_file_size() +	{ +		$max_file_size = $this->upload->max_filesize; +		if (!$max_file_size) +		{ +			$max_file_size = $this->php_ini->getString('upload_max_filesize'); + +			if (!empty($max_file_size)) +			{ +				$unit = strtolower(substr($max_file_size, -1, 1)); +				$max_file_size = (int) $max_file_size; + +				switch ($unit) +				{ +					case 'g': +						$max_file_size *= 1024; +					// no break +					case 'm': +						$max_file_size *= 1024; +					// no break +					case 'k': +						$max_file_size *= 1024; +					// no break +				} +			} +		} + +		return $max_file_size; +	} +} diff --git a/phpBB/phpbb/files/types/type_interface.php b/phpBB/phpbb/files/types/type_interface.php new file mode 100644 index 0000000000..e07078349a --- /dev/null +++ b/phpBB/phpbb/files/types/type_interface.php @@ -0,0 +1,38 @@ +<?php +/** + * + * This file is part of the phpBB Forum Software package. + * + * @copyright (c) phpBB Limited <https://www.phpbb.com> + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\files\types; + +use phpbb\files\upload; + +interface type_interface +{ +	/** +	 * Handle upload for upload types. Arguments passed to this method will be +	 * handled by the upload type classes themselves. +	 * +	 * @return \phpbb\files\filespec|bool Filespec instance if upload is +	 *                                    successful or false if not +	 */ +	public function upload(); + +	/** +	 * Set upload instance +	 * Needs to be executed before every upload. +	 * +	 * @param upload $upload Upload instance +	 * +	 * @return type_interface Returns itself +	 */ +	public function set_upload(upload $upload); +} diff --git a/phpBB/phpbb/files/upload.php b/phpBB/phpbb/files/upload.php new file mode 100644 index 0000000000..a9bf74094d --- /dev/null +++ b/phpBB/phpbb/files/upload.php @@ -0,0 +1,390 @@ +<?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\files; + +use phpbb\filesystem\filesystem_interface; +use phpbb\language\language; +use phpbb\request\request_interface; + +/** + * File upload class + * Init class (all parameters optional and able to be set/overwritten separately) - scope is global and valid for all uploads + */ +class upload +{ +	/** @var array Allowed file extensions */ +	public $allowed_extensions = array(); + +	/** @var array Disallowed content */ +	protected $disallowed_content = array('body', 'head', 'html', 'img', 'plaintext', 'a href', 'pre', 'script', 'table', 'title'); + +	/** @var int Maximum filesize */ +	public $max_filesize = 0; + +	/** @var int Minimum width of images */ +	public $min_width = 0; + +	/** @var int Minimum height of images */ +	public $min_height = 0; + +	/** @var int Maximum width of images */ +	public $max_width = 0; + +	/** @var int Maximum height of images */ +	public $max_height = 0; + +	/** @var string Prefix for language variables of errors */ +	public $error_prefix = ''; + +	/** @var int Timeout for remote upload */ +	public $upload_timeout = 6; + +	/** @var filesystem_interface */ +	protected $filesystem; + +	/** @var \phpbb\files\factory Files factory */ +	protected $factory; + +	/** @var \bantu\IniGetWrapper\IniGetWrapper ini_get() wrapper */ +	protected $php_ini; + +	/** @var \phpbb\language\language Language class */ +	protected $language; + +	/** @var request_interface Request class */ +	protected $request; + +	/** +	 * Init file upload class. +	 * +	 * @param filesystem_interface $filesystem +	 * @param factory $factory Files factory +	 * @param language $language Language class +	 * @param \bantu\IniGetWrapper\IniGetWrapper $php_ini ini_get() wrapper +	 * @param request_interface $request Request class +	 */ +	public function __construct(filesystem_interface $filesystem, factory $factory, language $language, \bantu\IniGetWrapper\IniGetWrapper $php_ini, request_interface $request) +	{ +		$this->filesystem = $filesystem; +		$this->factory = $factory; +		$this->language = $language; +		$this->php_ini = $php_ini; +		$this->request = $request; +	} + +	/** +	 * Reset vars +	 */ +	public function reset_vars() +	{ +		$this->max_filesize = 0; +		$this->min_width = $this->min_height = $this->max_width = $this->max_height = 0; +		$this->error_prefix = ''; +		$this->allowed_extensions = array(); +		$this->disallowed_content = array(); +	} + +	/** +	 * Set allowed extensions +	 * +	 * @param array $allowed_extensions Allowed file extensions +	 * +	 * @return \phpbb\files\upload This instance of upload +	 */ +	public function set_allowed_extensions($allowed_extensions) +	{ +		if ($allowed_extensions !== false && is_array($allowed_extensions)) +		{ +			$this->allowed_extensions = $allowed_extensions; +		} + +		return $this; +	} + +	/** +	 * Set allowed dimensions +	 * +	 * @param int $min_width Minimum image width +	 * @param int $min_height Minimum image height +	 * @param int $max_width Maximum image width +	 * @param int $max_height Maximum image height +	 * +	 * @return \phpbb\files\upload This instance of upload +	 */ +	public function set_allowed_dimensions($min_width, $min_height, $max_width, $max_height) +	{ +		$this->min_width = (int) $min_width; +		$this->min_height = (int) $min_height; +		$this->max_width = (int) $max_width; +		$this->max_height = (int) $max_height; + +		return $this; +	} + +	/** +	 * Set maximum allowed file size +	 * +	 * @param int $max_filesize Maximum file size +	 * +	 * @return \phpbb\files\upload This instance of upload +	 */ +	public function set_max_filesize($max_filesize) +	{ +		if ($max_filesize !== false && (int) $max_filesize) +		{ +			$this->max_filesize = (int) $max_filesize; +		} + +		return $this; +	} + +	/** +	 * Set disallowed strings +	 * +	 * @param array $disallowed_content Disallowed content +	 * +	 * @return \phpbb\files\upload This instance of upload +	 */ +	public function set_disallowed_content($disallowed_content) +	{ +		if ($disallowed_content !== false && is_array($disallowed_content)) +		{ +			$this->disallowed_content = array_diff($disallowed_content, array('')); +		} + +		return $this; +	} + +	/** +	 * Set error prefix +	 * +	 * @param string $error_prefix Prefix for language variables of errors +	 * +	 * @return \phpbb\files\upload This instance of upload +	 */ +	public function set_error_prefix($error_prefix) +	{ +		$this->error_prefix = $error_prefix; + +		return $this; +	} + +	/** +	 * Handle upload based on type +	 * +	 * @param string $type Upload type +	 * +	 * @return \phpbb\files\filespec|bool A filespec instance if upload was +	 *		successful, false if there were issues or the type is not supported +	 */ +	public function handle_upload($type) +	{ +		$args = func_get_args(); +		array_shift($args); +		$type_class = $this->factory->get($type) +			->set_upload($this); + +		return (is_object($type_class)) ? call_user_func_array(array($type_class, 'upload'), $args) : false; +	} + +	/** +	 * Assign internal error +	 * +	 * @param string $errorcode Error code to assign +	 * +	 * @return string Error string +	 * @access public +	 */ +	public function assign_internal_error($errorcode) +	{ +		switch ($errorcode) +		{ +			case UPLOAD_ERR_INI_SIZE: +				$max_filesize = $this->php_ini->getString('upload_max_filesize'); +				$unit = 'MB'; + +				if (!empty($max_filesize)) +				{ +					$unit = strtolower(substr($max_filesize, -1, 1)); +					$max_filesize = (int) $max_filesize; + +					$unit = ($unit == 'k') ? 'KB' : (($unit == 'g') ? 'GB' : 'MB'); +				} + +				$error = (empty($max_filesize)) ? $this->language->lang($this->error_prefix . 'PHP_SIZE_NA') : $this->language->lang($this->error_prefix . 'PHP_SIZE_OVERRUN', $max_filesize, $this->language->lang($unit)); +			break; + +			case UPLOAD_ERR_FORM_SIZE: +				$max_filesize = get_formatted_filesize($this->max_filesize, false); + +				$error = $this->language->lang($this->error_prefix . 'WRONG_FILESIZE', $max_filesize['value'], $max_filesize['unit']); +			break; + +			case UPLOAD_ERR_PARTIAL: +				$error = $this->language->lang($this->error_prefix . 'PARTIAL_UPLOAD'); +			break; + +			case UPLOAD_ERR_NO_FILE: +				$error = $this->language->lang($this->error_prefix . 'NOT_UPLOADED'); +			break; + +			case UPLOAD_ERR_NO_TMP_DIR: +			case UPLOAD_ERR_CANT_WRITE: +				$error = $this->language->lang($this->error_prefix . 'NO_TEMP_DIR'); +			break; + +			case UPLOAD_ERR_EXTENSION: +				$error = $this->language->lang($this->error_prefix . 'PHP_UPLOAD_STOPPED'); +			break; + +			default: +				$error = false; +			break; +		} + +		return $error; +	} + +	/** +	 * Perform common file checks +	 * +	 * @param filespec $file Instance of filespec class +	 */ +	public function common_checks(&$file) +	{ +		// Filesize is too big or it's 0 if it was larger than the maxsize in the upload form +		if ($this->max_filesize && ($file->get('filesize') > $this->max_filesize || $file->get('filesize') == 0)) +		{ +			$max_filesize = get_formatted_filesize($this->max_filesize, false); + +			$file->error[] = $this->language->lang($this->error_prefix . 'WRONG_FILESIZE', $max_filesize['value'], $max_filesize['unit']); +		} + +		// check Filename +		if (preg_match("#[\\/:*?\"<>|]#i", $file->get('realname'))) +		{ +			$file->error[] = $this->language->lang($this->error_prefix . 'INVALID_FILENAME', $file->get('realname')); +		} + +		// Invalid Extension +		if (!$this->valid_extension($file)) +		{ +			$file->error[] = $this->language->lang($this->error_prefix . 'DISALLOWED_EXTENSION', $file->get('extension')); +		} + +		// MIME Sniffing +		if (!$this->valid_content($file)) +		{ +			$file->error[] = $this->language->lang($this->error_prefix . 'DISALLOWED_CONTENT'); +		} +	} + +	/** +	 * Check for allowed extension +	 * +	 * @param filespec $file Instance of filespec class +	 * +	 * @return bool True if extension is allowed, false if not +	 */ +	public function valid_extension(&$file) +	{ +		return (in_array($file->get('extension'), $this->allowed_extensions)) ? true : false; +	} + +	/** +	 * Check for allowed dimension +	 * +	 * @param filespec $file Instance of filespec class +	 * +	 * @return bool True if dimensions are valid or no constraints set, false +	 *			if not +	 */ +	public function valid_dimensions(&$file) +	{ +		if (!$this->max_width && !$this->max_height && !$this->min_width && !$this->min_height) +		{ +			return true; +		} + +		if (($file->get('width') > $this->max_width && $this->max_width) || +			($file->get('height') > $this->max_height && $this->max_height) || +			($file->get('width') < $this->min_width && $this->min_width) || +			($file->get('height') < $this->min_height && $this->min_height)) +		{ +			return false; +		} + +		return true; +	} + +	/** +	 * Check if form upload is valid +	 * +	 * @param string $form_name Name of form +	 * +	 * @return bool True if form upload is valid, false if not +	 */ +	public function is_valid($form_name) +	{ +		$upload = $this->request->file($form_name); + +		return (!empty($upload) && $upload['name'] !== 'none'); +	} + + +	/** +	 * Check for bad content (IE mime-sniffing) +	 * +	 * @param filespec $file Instance of filespec class +	 * +	 * @return bool True if content is valid, false if not +	 */ +	public function valid_content(&$file) +	{ +		return ($file->check_content($this->disallowed_content)); +	} + +	/** +	 * Get image type/extension mapping +	 * +	 * @return array Array containing the image types and their extensions +	 */ +	static public function image_types() +	{ +		$result = array( +			IMAGETYPE_GIF		=> array('gif'), +			IMAGETYPE_JPEG		=> array('jpg', 'jpeg'), +			IMAGETYPE_PNG		=> array('png'), +			IMAGETYPE_SWF		=> array('swf'), +			IMAGETYPE_PSD		=> array('psd'), +			IMAGETYPE_BMP		=> array('bmp'), +			IMAGETYPE_TIFF_II	=> array('tif', 'tiff'), +			IMAGETYPE_TIFF_MM	=> array('tif', 'tiff'), +			IMAGETYPE_JPC		=> array('jpg', 'jpeg'), +			IMAGETYPE_JP2		=> array('jpg', 'jpeg'), +			IMAGETYPE_JPX		=> array('jpg', 'jpeg'), +			IMAGETYPE_JB2		=> array('jpg', 'jpeg'), +			IMAGETYPE_IFF		=> array('iff'), +			IMAGETYPE_WBMP		=> array('wbmp'), +			IMAGETYPE_XBM		=> array('xbm'), +		); + +		if (defined('IMAGETYPE_SWC')) +		{ +			$result[IMAGETYPE_SWC] = array('swc'); +		} + +		return $result; +	} +} 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..1093be2499 --- /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	$files				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/group/helper.php b/phpBB/phpbb/group/helper.php new file mode 100644 index 0000000000..5befddfc53 --- /dev/null +++ b/phpBB/phpbb/group/helper.php @@ -0,0 +1,40 @@ +<?php +/** + * + * This file is part of the phpBB Forum Software package. + * + * @copyright (c) phpBB Limited <https://www.phpbb.com> + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\group; + +class helper +{ +	/** @var  \phpbb\language\language */ +	protected $language; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\language\language $language	Language object +	 */ +	public function __construct(\phpbb\language\language $language) +	{ +		$this->language = $language; +	} + +	/** +	 * @param $group_name string	The stored group name +	 * +	 * @return string		Group name or translated group name if it exists +	 */ +	public function get_name($group_name) +	{ +		return $this->language->is_set('G_' . utf8_strtoupper($group_name)) ? $this->language->lang('G_' . utf8_strtoupper($group_name)) : $group_name; +	} +} diff --git a/phpBB/phpbb/help/controller/bbcode.php b/phpBB/phpbb/help/controller/bbcode.php new file mode 100644 index 0000000000..e16f99023d --- /dev/null +++ b/phpBB/phpbb/help/controller/bbcode.php @@ -0,0 +1,85 @@ +<?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; + +/** + * BBCode help page + */ +class bbcode extends controller +{ +	/** +	 * @return string The title of the page +	 */ +	public function display() +	{ +		$this->language->add_lang('help/bbcode'); + +		$this->manager->add_block( +			'HELP_BBCODE_BLOCK_INTRO', +			false, +			array( +				'HELP_BBCODE_INTRO_BBCODE_QUESTION' => 'HELP_BBCODE_INTRO_BBCODE_ANSWER', +			) +		); +		$this->manager->add_block( +			'HELP_BBCODE_BLOCK_TEXT', +			false, +			array( +				'HELP_BBCODE_TEXT_BASIC_QUESTION' => 'HELP_BBCODE_TEXT_BASIC_ANSWER', +				'HELP_BBCODE_TEXT_COLOR_QUESTION' => 'HELP_BBCODE_TEXT_COLOR_ANSWER', +				'HELP_BBCODE_TEXT_COMBINE_QUESTION' => 'HELP_BBCODE_TEXT_COMBINE_ANSWER', +			) +		); +		$this->manager->add_block( +			'HELP_BBCODE_BLOCK_QUOTES', +			false, +			array( +				'HELP_BBCODE_QUOTES_TEXT_QUESTION' => 'HELP_BBCODE_QUOTES_TEXT_ANSWER', +				'HELP_BBCODE_QUOTES_CODE_QUESTION' => 'HELP_BBCODE_QUOTES_CODE_ANSWER', +			) +		); +		$this->manager->add_block( +			'HELP_BBCODE_BLOCK_LISTS', +			false, +			array( +				'HELP_BBCODE_LISTS_UNORDERER_QUESTION' => 'HELP_BBCODE_LISTS_UNORDERER_ANSWER', +				'HELP_BBCODE_LISTS_ORDERER_QUESTION' => 'HELP_BBCODE_LISTS_ORDERER_ANSWER', +			) +		); +		$this->manager->add_block( +			'HELP_BBCODE_BLOCK_LINKS', +			true, +			array( +				'HELP_BBCODE_LINKS_BASIC_QUESTION' => 'HELP_BBCODE_LINKS_BASIC_ANSWER', +			) +		); +		$this->manager->add_block( +			'HELP_BBCODE_BLOCK_IMAGES', +			false, +			array( +				'HELP_BBCODE_IMAGES_BASIC_QUESTION' => 'HELP_BBCODE_IMAGES_BASIC_ANSWER', +				'HELP_BBCODE_IMAGES_ATTACHMENT_QUESTION' => 'HELP_BBCODE_IMAGES_ATTACHMENT_ANSWER', +			) +		); +		$this->manager->add_block( +			'HELP_BBCODE_BLOCK_OTHERS', +			false, +			array( +				'HELP_BBCODE_OTHERS_CUSTOM_QUESTION' => 'HELP_BBCODE_OTHERS_CUSTOM_ANSWER', +			) +		); + +		return $this->language->lang('BBCODE_GUIDE'); +	} +} diff --git a/phpBB/phpbb/help/controller/controller.php b/phpBB/phpbb/help/controller/controller.php new file mode 100644 index 0000000000..29494205a9 --- /dev/null +++ b/phpBB/phpbb/help/controller/controller.php @@ -0,0 +1,76 @@ +<?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; + +/** + * BBCode help page + */ +abstract class controller +{ +	/** @var \phpbb\controller\helper */ +	protected $helper; + +	/** @var \phpbb\help\manager */ +	protected $manager; + +	/** @var \phpbb\template\template */ +	protected $template; + +	/** @var \phpbb\language\language */ +	protected $language; + +	/** @var string */ +	protected $root_path; + +	/** @var string */ +	protected $php_ext; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\controller\helper	$helper +	 * @param \phpbb\help\manager		$manager +	 * @param \phpbb\template\template	$template +	 * @param \phpbb\language\language	$language +	 * @param string					$root_path +	 * @param string					$php_ext +	 */ +	public function __construct(\phpbb\controller\helper $helper, \phpbb\help\manager $manager, \phpbb\template\template $template, \phpbb\language\language $language, $root_path, $php_ext) +	{ +		$this->helper = $helper; +		$this->manager = $manager; +		$this->template = $template; +		$this->language = $language; +		$this->root_path = $root_path; +		$this->php_ext = $php_ext; +	} + +	/** +	 * @return string +	 */ +	abstract protected function display(); + +	public function handle() +	{ +		$title = $this->display(); + +		$this->template->assign_vars(array( +			'L_FAQ_TITLE'	=> $title, +			'S_IN_FAQ'		=> true, +		)); + +		make_jumpbox(append_sid("{$this->root_path}viewforum.{$this->php_ext}")); +		return $this->helper->render('faq_body.html', $title); +	} +} diff --git a/phpBB/phpbb/help/controller/faq.php b/phpBB/phpbb/help/controller/faq.php new file mode 100644 index 0000000000..5e45cfe667 --- /dev/null +++ b/phpBB/phpbb/help/controller/faq.php @@ -0,0 +1,165 @@ +<?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; + +/** + * FAQ help page + */ +class faq extends controller +{ +	/** +	 * @return string The title of the page +	 */ +	public function display() +	{ +		$this->language->add_lang('help/faq'); + +		$this->manager->add_block( +			'HELP_FAQ_BLOCK_LOGIN', +			false, +			array( +				'HELP_FAQ_LOGIN_REGISTER_QUESTION' => 'HELP_FAQ_LOGIN_REGISTER_ANSWER', +				'HELP_FAQ_LOGIN_COPPA_QUESTION' => 'HELP_FAQ_LOGIN_COPPA_ANSWER', +				'HELP_FAQ_LOGIN_CANNOT_REGISTER_QUESTION' => 'HELP_FAQ_LOGIN_CANNOT_REGISTER_ANSWER', +				'HELP_FAQ_LOGIN_REGISTER_CONFIRM_QUESTION' => 'HELP_FAQ_LOGIN_REGISTER_CONFIRM_ANSWER', +				'HELP_FAQ_LOGIN_CANNOT_LOGIN_QUESTION' => 'HELP_FAQ_LOGIN_CANNOT_LOGIN_ANSWER', +				'HELP_FAQ_LOGIN_CANNOT_LOGIN_ANYMORE_QUESTION' => 'HELP_FAQ_LOGIN_CANNOT_LOGIN_ANYMORE_ANSWER', +				'HELP_FAQ_LOGIN_LOST_PASSWORD_QUESTION' => 'HELP_FAQ_LOGIN_LOST_PASSWORD_ANSWER', +				'HELP_FAQ_LOGIN_AUTO_LOGOUT_QUESTION' => 'HELP_FAQ_LOGIN_AUTO_LOGOUT_ANSWER', +				'HELP_FAQ_LOGIN_DELETE_COOKIES_QUESTION' => 'HELP_FAQ_LOGIN_DELETE_COOKIES_ANSWER', +			) +		); +		$this->manager->add_block( +			'HELP_FAQ_BLOCK_USERSETTINGS', +			false, +			array( +				'HELP_FAQ_USERSETTINGS_CHANGE_SETTINGS_QUESTION' => 'HELP_FAQ_USERSETTINGS_CHANGE_SETTINGS_ANSWER', +				'HELP_FAQ_USERSETTINGS_HIDE_ONLINE_QUESTION' => 'HELP_FAQ_USERSETTINGS_HIDE_ONLINE_ANSWER', +				'HELP_FAQ_USERSETTINGS_TIMEZONE_QUESTION' => 'HELP_FAQ_USERSETTINGS_TIMEZONE_ANSWER', +				'HELP_FAQ_USERSETTINGS_SERVERTIME_QUESTION' => 'HELP_FAQ_USERSETTINGS_SERVERTIME_ANSWER', +				'HELP_FAQ_USERSETTINGS_LANGUAGE_QUESTION' => 'HELP_FAQ_USERSETTINGS_LANGUAGE_ANSWER', +				'HELP_FAQ_USERSETTINGS_AVATAR_QUESTION' => 'HELP_FAQ_USERSETTINGS_AVATAR_ANSWER', +				'HELP_FAQ_USERSETTINGS_AVATAR_DISPLAY_QUESTION' => 'HELP_FAQ_USERSETTINGS_AVATAR_DISPLAY_ANSWER', +				'HELP_FAQ_USERSETTINGS_RANK_QUESTION' => 'HELP_FAQ_USERSETTINGS_RANK_ANSWER', +				'HELP_FAQ_USERSETTINGS_EMAIL_LOGIN_QUESTION' => 'HELP_FAQ_USERSETTINGS_EMAIL_LOGIN_ANSWER', +			) +		); +		$this->manager->add_block( +			'HELP_FAQ_BLOCK_POSTING', +			false, +			array( +				'HELP_FAQ_POSTING_CREATE_QUESTION' => 'HELP_FAQ_POSTING_CREATE_ANSWER', +				'HELP_FAQ_POSTING_EDIT_DELETE_QUESTION' => 'HELP_FAQ_POSTING_EDIT_DELETE_ANSWER', +				'HELP_FAQ_POSTING_SIGNATURE_QUESTION' => 'HELP_FAQ_POSTING_SIGNATURE_ANSWER', +				'HELP_FAQ_POSTING_POLL_CREATE_QUESTION' => 'HELP_FAQ_POSTING_POLL_CREATE_ANSWER', +				'HELP_FAQ_POSTING_POLL_ADD_QUESTION' => 'HELP_FAQ_POSTING_POLL_ADD_ANSWER', +				'HELP_FAQ_POSTING_POLL_EDIT_QUESTION' => 'HELP_FAQ_POSTING_POLL_EDIT_ANSWER', +				'HELP_FAQ_POSTING_FORUM_RESTRICTED_QUESTION' => 'HELP_FAQ_POSTING_FORUM_RESTRICTED_ANSWER', +				'HELP_FAQ_POSTING_NO_ATTACHMENTS_QUESTION' => 'HELP_FAQ_POSTING_NO_ATTACHMENTS_ANSWER', +				'HELP_FAQ_POSTING_WARNING_QUESTION' => 'HELP_FAQ_POSTING_WARNING_ANSWER', +				'HELP_FAQ_POSTING_REPORT_QUESTION' => 'HELP_FAQ_POSTING_REPORT_ANSWER', +				'HELP_FAQ_POSTING_DRAFT_QUESTION' => 'HELP_FAQ_POSTING_DRAFT_ANSWER', +				'HELP_FAQ_POSTING_QUEUE_QUESTION' => 'HELP_FAQ_POSTING_QUEUE_ANSWER', +				'HELP_FAQ_POSTING_BUMP_QUESTION' => 'HELP_FAQ_POSTING_BUMP_ANSWER', +			) +		); +		$this->manager->add_block( +			'HELP_FAQ_BLOCK_FORMATTING', +			false, +			array( +				'HELP_FAQ_FORMATTING_BBOCDE_QUESTION' => 'HELP_FAQ_FORMATTING_BBOCDE_ANSWER', +				'HELP_FAQ_FORMATTING_HTML_QUESTION' => 'HELP_FAQ_FORMATTING_HTML_ANSWER', +				'HELP_FAQ_FORMATTING_SMILIES_QUESTION' => 'HELP_FAQ_FORMATTING_SMILIES_ANSWER', +				'HELP_FAQ_FORMATTING_IMAGES_QUESTION' => 'HELP_FAQ_FORMATTING_IMAGES_ANSWER', +				'HELP_FAQ_FORMATTING_GLOBAL_ANNOUNCE_QUESTION' => 'HELP_FAQ_FORMATTING_GLOBAL_ANNOUNCE_ANSWER', +				'HELP_FAQ_FORMATTING_ANNOUNCEMENT_QUESTION' => 'HELP_FAQ_FORMATTING_ANNOUNCEMENT_ANSWER', +				'HELP_FAQ_FORMATTING_STICKIES_QUESTION' => 'HELP_FAQ_FORMATTING_STICKIES_ANSWER', +				'HELP_FAQ_FORMATTING_LOCKED_QUESTION' => 'HELP_FAQ_FORMATTING_LOCKED_ANSWER', +				'HELP_FAQ_FORMATTING_ICONS_QUESTION' => 'HELP_FAQ_FORMATTING_ICONS_ANSWER', +			) +		); +		$this->manager->add_block( +			'HELP_FAQ_BLOCK_GROUPS', +			true, +			array( +				'HELP_FAQ_GROUPS_ADMINISTRATORS_QUESTION' => 'HELP_FAQ_GROUPS_ADMINISTRATORS_ANSWER', +				'HELP_FAQ_GROUPS_MODERATORS_QUESTION' => 'HELP_FAQ_GROUPS_MODERATORS_ANSWER', +				'HELP_FAQ_GROUPS_USERGROUPS_QUESTION' => 'HELP_FAQ_GROUPS_USERGROUPS_ANSWER', +				'HELP_FAQ_GROUPS_USERGROUPS_JOIN_QUESTION' => 'HELP_FAQ_GROUPS_USERGROUPS_JOIN_ANSWER', +				'HELP_FAQ_GROUPS_USERGROUPS_LEAD_QUESTION' => 'HELP_FAQ_GROUPS_USERGROUPS_LEAD_ANSWER', +				'HELP_FAQ_GROUPS_COLORS_QUESTION' => 'HELP_FAQ_GROUPS_COLORS_ANSWER', +				'HELP_FAQ_GROUPS_DEFAULT_QUESTION' => 'HELP_FAQ_GROUPS_DEFAULT_ANSWER', +				'HELP_FAQ_GROUPS_TEAM_QUESTION' => 'HELP_FAQ_GROUPS_TEAM_ANSWER', +			) +		); +		$this->manager->add_block( +			'HELP_FAQ_BLOCK_PMS', +			false, +			array( +				'HELP_FAQ_PMS_CANNOT_SEND_QUESTION' => 'HELP_FAQ_PMS_CANNOT_SEND_ANSWER', +				'HELP_FAQ_PMS_UNWANTED_QUESTION' => 'HELP_FAQ_PMS_UNWANTED_ANSWER', +				'HELP_FAQ_PMS_SPAM_QUESTION' => 'HELP_FAQ_PMS_SPAM_ANSWER', +			) +		); +		$this->manager->add_block( +			'HELP_FAQ_BLOCK_FRIENDS', +			false, +			array( +				'HELP_FAQ_FRIENDS_BASIC_QUESTION' => 'HELP_FAQ_FRIENDS_BASIC_ANSWER', +				'HELP_FAQ_FRIENDS_MANAGE_QUESTION' => 'HELP_FAQ_FRIENDS_MANAGE_ANSWER', +			) +		); +		$this->manager->add_block( +			'HELP_FAQ_BLOCK_SEARCH', +			false, +			array( +				'HELP_FAQ_SEARCH_FORUM_QUESTION' => 'HELP_FAQ_SEARCH_FORUM_ANSWER', +				'HELP_FAQ_SEARCH_NO_RESULT_QUESTION' => 'HELP_FAQ_SEARCH_NO_RESULT_ANSWER', +				'HELP_FAQ_SEARCH_BLANK_QUESTION' => 'HELP_FAQ_SEARCH_BLANK_ANSWER', +				'HELP_FAQ_SEARCH_MEMBERS_QUESTION' => 'HELP_FAQ_SEARCH_MEMBERS_ANSWER', +				'HELP_FAQ_SEARCH_OWN_QUESTION' => 'HELP_FAQ_SEARCH_OWN_ANSWER', +			) +		); +		$this->manager->add_block( +			'HELP_FAQ_BLOCK_BOOKMARKS', +			false, +			array( +				'HELP_FAQ_BOOKMARKS_DIFFERENCE_QUESTION' => 'HELP_FAQ_BOOKMARKS_DIFFERENCE_ANSWER', +				'HELP_FAQ_BOOKMARKS_TOPIC_QUESTION' => 'HELP_FAQ_BOOKMARKS_TOPIC_ANSWER', +				'HELP_FAQ_BOOKMARKS_FORUM_QUESTION' => 'HELP_FAQ_BOOKMARKS_FORUM_ANSWER', +				'HELP_FAQ_BOOKMARKS_REMOVE_QUESTION' => 'HELP_FAQ_BOOKMARKS_REMOVE_ANSWER', +			) +		); +		$this->manager->add_block( +			'HELP_FAQ_BLOCK_ATTACHMENTS', +			false, +			array( +				'HELP_FAQ_ATTACHMENTS_ALLOWED_QUESTION' => 'HELP_FAQ_ATTACHMENTS_ALLOWED_ANSWER', +				'HELP_FAQ_ATTACHMENTS_OWN_QUESTION' => 'HELP_FAQ_ATTACHMENTS_OWN_ANSWER', +			) +		); +		$this->manager->add_block( +			'HELP_FAQ_BLOCK_ISSUES', +			false, +			array( +				'HELP_FAQ_ISSUES_WHOIS_PHPBB_QUESTION' => 'HELP_FAQ_ISSUES_WHOIS_PHPBB_ANSWER', +				'HELP_FAQ_ISSUES_FEATURE_QUESTION' => 'HELP_FAQ_ISSUES_FEATURE_ANSWER', +				'HELP_FAQ_ISSUES_LEGAL_QUESTION' => 'HELP_FAQ_ISSUES_LEGAL_ANSWER', +				'HELP_FAQ_ISSUES_ADMIN_QUESTION' => 'HELP_FAQ_ISSUES_ADMIN_ANSWER', +			) +		); + +		return $this->language->lang('FAQ_EXPLAIN'); +	} +} 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/help/manager.php b/phpBB/phpbb/help/manager.php new file mode 100644 index 0000000000..39f52d343b --- /dev/null +++ b/phpBB/phpbb/help/manager.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\help; + +/** + * Class help page manager + */ +class manager +{ +	/** @var \phpbb\event\dispatcher */ +	protected $dispatcher; + +	/** @var \phpbb\language\language */ +	protected $language; + +	/** @var \phpbb\template\template */ +	protected $template; + +	/** @var bool */ +	protected $switched_column; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\event\dispatcher $dispatcher +	 * @param \phpbb\language\language $language +	 * @param \phpbb\template\template $template +	 */ +	public function __construct(\phpbb\event\dispatcher $dispatcher, \phpbb\language\language $language, \phpbb\template\template $template) +	{ +		$this->dispatcher = $dispatcher; +		$this->language = $language; +		$this->template = $template; +	} + +	/** +	 * Add a new faq block +	 * +	 * @param string $block_name	Name or language key with the name of the block +	 * @param bool $switch_column	Switch the column of the menu +	 * @param array $questions		Array of frequently asked questions +	 */ +	public function add_block($block_name, $switch_column = false, $questions = array()) +	{ +		/** +		 * You can use this event to add a block before the current one. +		 * +		 * @event core.help_manager_add_block_before +		 * @var	string	block_name		Language key of the block headline +		 * @var	bool	switch_column	Should we switch the menu column before this headline +		 * @var	array	questions		Array with questions +		 * @since 3.2.0-a1 +		 */ +		$vars = array('block_name', 'switch_column', 'questions'); +		extract($this->dispatcher->trigger_event('core.help_manager_add_block_before', compact($vars))); + +		$this->template->assign_block_vars('faq_block', array( +			'BLOCK_TITLE'		=> $this->language->lang($block_name), +			'SWITCH_COLUMN'		=> !$this->switched_column && $switch_column, +		)); + +		foreach ($questions as $question => $answer) +		{ +			$this->add_question($question, $answer); +		} + +		$this->switched_column = $this->switched_column || $switch_column; + +		/** +		 * You can use this event to add a block after the current one. +		 * +		 * @event core.help_manager_add_block_after +		 * @var	string	block_name		Language key of the block headline +		 * @var	bool	switch_column	Should we switch the menu column before this headline +		 * @var	array	questions		Array with questions +		 * @since 3.2.0-a1 +		 */ +		$vars = array('block_name', 'switch_column', 'questions'); +		extract($this->dispatcher->trigger_event('core.help_manager_add_block_after', compact($vars))); +	} + +	/** +	 * Add a new faq question +	 * +	 * @param string $question	Question or language key with the question of the block +	 * @param string $answer	Answer or language key with the answer of the block +	 */ +	public function add_question($question, $answer) +	{ +		/** +		 * You can use this event to add a question before the current one. +		 * +		 * @event core.help_manager_add_question_before +		 * @var	string	question	Language key of the question +		 * @var	string	answer		Language key of the answer +		 * @since 3.2.0-a1 +		 */ +		$vars = array('question', 'answer'); +		extract($this->dispatcher->trigger_event('core.help_manager_add_question_before', compact($vars))); + +		$this->template->assign_block_vars('faq_block.faq_row', array( +			'FAQ_QUESTION'		=> $this->language->lang($question), +			'FAQ_ANSWER'		=> $this->language->lang($answer), +		)); + +		/** +		 * You can use this event to add a question after the current one. +		 * +		 * @event core.help_manager_add_question_after +		 * @var	string	question	Language key of the question +		 * @var	string	answer		Language key of the answer +		 * @since 3.2.0-a1 +		 */ +		$vars = array('question', 'answer'); +		extract($this->dispatcher->trigger_event('core.help_manager_add_question_after', compact($vars))); +	} + +	/** +	 * Returns whether the block titles switched side +	 * @return bool +	 */ +	public function switched_column() +	{ +		return $this->switched_column; +	} +} diff --git a/phpBB/phpbb/hook/finder.php b/phpBB/phpbb/hook/finder.php index a3d02d3aa0..f5a68a1370 100644 --- a/phpBB/phpbb/hook/finder.php +++ b/phpBB/phpbb/hook/finder.php @@ -18,8 +18,19 @@ namespace phpbb\hook;  */  class finder  { -	protected $phpbb_root_path; +	/** +	 * @var \phpbb\cache\driver\driver_interface +	 */  	protected $cache; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * @var string +	 */  	protected $php_ext;  	/** diff --git a/phpBB/phpbb/install/console/command/install/config/show.php b/phpBB/phpbb/install/console/command/install/config/show.php new file mode 100644 index 0000000000..b6c11956fe --- /dev/null +++ b/phpBB/phpbb/install/console/command/install/config/show.php @@ -0,0 +1,123 @@ +<?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\install\console\command\install\config; + +use phpbb\install\helper\iohandler\factory; +use phpbb\install\installer_configuration; +use phpbb\language\language; +use Symfony\Component\Config\Definition\Exception\Exception; +use Symfony\Component\Config\Definition\Processor; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Yaml; + +class show extends \phpbb\console\command\command +{ +	/** +	 * @var factory +	 */ +	protected $iohandler_factory; + +	/** +	 * @var language +	 */ +	protected $language; + +	/** +	 * Constructor +	 * +	 * @param language $language +	 * @param factory $factory +	 */ +	public function __construct(language $language, factory $factory) +	{ +		$this->iohandler_factory = $factory; +		$this->language = $language; + +		parent::__construct(new \phpbb\user($language, 'datetime')); +	} + +	/** +	 * +	 * {@inheritdoc} +	 */ +	protected function configure() +	{ +		$this +			->setName('install:config:show') +			->addArgument( +				'config-file', +				InputArgument::REQUIRED, +				$this->language->lang('CLI_CONFIG_FILE')) +			->setDescription($this->language->lang('CLI_INSTALL_SHOW_CONFIG')) +		; +	} + +	/** +	 * Show the validated configuration +	 * +	 * @param InputInterface  $input  An InputInterface instance +	 * @param OutputInterface $output An OutputInterface instance +	 * +	 * @return null +	 */ +	protected function execute(InputInterface $input, OutputInterface $output) +	{ +		$this->iohandler_factory->set_environment('cli'); + +		/** @var \phpbb\install\helper\iohandler\cli_iohandler $iohandler */ +		$iohandler = $this->iohandler_factory->get(); +		$style = new SymfonyStyle($input, $output); +		$iohandler->set_style($style, $output); + +		$config_file = $input->getArgument('config-file'); + +		if (!is_file($config_file)) +		{ +			$iohandler->add_error_message(array('MISSING_FILE', $config_file)); + +			return; +		} + +		try +		{ +			$config = Yaml::parse(file_get_contents($config_file), true, false); +		} +		catch (ParseException $e) +		{ +			$iohandler->add_error_message('INVALID_YAML_FILE'); + +			return; +		} + +		$processor = new Processor(); +		$configuration = new installer_configuration(); + +		try +		{ +			$config = $processor->processConfiguration($configuration, $config); +		} +		catch (Exception $e) +		{ +			$iohandler->add_error_message('INVALID_CONFIGURATION', $e->getMessage()); + +			return; +		} + +		$style->block(Yaml::dump(array('installer' => $config), 10, 4, true, false)); +	} +} diff --git a/phpBB/phpbb/install/console/command/install/config/validate.php b/phpBB/phpbb/install/console/command/install/config/validate.php new file mode 100644 index 0000000000..b48a1acbd4 --- /dev/null +++ b/phpBB/phpbb/install/console/command/install/config/validate.php @@ -0,0 +1,124 @@ +<?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\install\console\command\install\config; + +use phpbb\install\helper\iohandler\factory; +use phpbb\install\installer_configuration; +use phpbb\language\language; +use Symfony\Component\Config\Definition\Exception\Exception; +use Symfony\Component\Config\Definition\Processor; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Yaml; + +class validate extends \phpbb\console\command\command +{ +	/** +	 * @var factory +	 */ +	protected $iohandler_factory; + +	/** +	 * @var language +	 */ +	protected $language; + +	/** +	 * Constructor +	 * +	 * @param language $language +	 * @param factory $factory +	 */ +	public function __construct(language $language, factory $factory) +	{ +		$this->iohandler_factory = $factory; +		$this->language = $language; + +		parent::__construct(new \phpbb\user($language, 'datetime')); +	} + +	/** +	 * +	 * {@inheritdoc} +	 */ +	protected function configure() +	{ +		$this +			->setName('install:config:validate') +			->addArgument( +				'config-file', +				InputArgument::REQUIRED, +				$this->language->lang('CLI_CONFIG_FILE')) +			->setDescription($this->language->lang('CLI_INSTALL_VALIDATE_CONFIG')) +		; +	} + +	/** +	 * Validate the configuration file +	 * +	 * @param InputInterface  $input  An InputInterface instance +	 * @param OutputInterface $output An OutputInterface instance +	 * +	 * @return null +	 */ +	protected function execute(InputInterface $input, OutputInterface $output) +	{ +		$this->iohandler_factory->set_environment('cli'); + +		/** @var \phpbb\install\helper\iohandler\cli_iohandler $iohandler */ +		$iohandler = $this->iohandler_factory->get(); +		$style = new SymfonyStyle($input, $output); +		$iohandler->set_style($style, $output); + +		$config_file = $input->getArgument('config-file'); + +		if (!is_file($config_file)) +		{ +			$iohandler->add_error_message(array('MISSING_FILE', array($config_file))); + +			return 1; +		} + +		try +		{ +			$config = Yaml::parse(file_get_contents($config_file), true, false); +		} +		catch (ParseException $e) +		{ +			$iohandler->add_error_message('INVALID_YAML_FILE'); + +			return 1; +		} + +		$processor = new Processor(); +		$configuration = new installer_configuration(); + +		try +		{ +			$processor->processConfiguration($configuration, $config); +		} +		catch (Exception $e) +		{ +			$iohandler->add_error_message('INVALID_CONFIGURATION', $e->getMessage()); + +			return 1; +		} + +		$iohandler->add_success_message('CONFIGURATION_VALID'); +		return 0; +	} +} diff --git a/phpBB/phpbb/install/console/command/install/install.php b/phpBB/phpbb/install/console/command/install/install.php new file mode 100644 index 0000000000..de3a2e2d61 --- /dev/null +++ b/phpBB/phpbb/install/console/command/install/install.php @@ -0,0 +1,207 @@ +<?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\install\console\command\install; + +use phpbb\install\exception\installer_exception; +use phpbb\install\helper\install_helper; +use phpbb\install\helper\iohandler\cli_iohandler; +use phpbb\install\helper\iohandler\factory; +use phpbb\install\installer; +use phpbb\install\installer_configuration; +use phpbb\language\language; +use Symfony\Component\Config\Definition\Exception\Exception; +use Symfony\Component\Config\Definition\Processor; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Yaml; + +class install extends \phpbb\console\command\command +{ +	/** +	 * @var factory +	 */ +	protected $iohandler_factory; + +	/** +	 * @var installer +	 */ +	protected $installer; + +	/** +	 * @var install_helper +	 */ +	protected $install_helper; + +	/** +	 * @var language +	 */ +	protected $language; + +	/** +	 * Constructor +	 * +	 * @param language			$language +	 * @param factory			$factory +	 * @param installer			$installer +	 * @param install_helper	$install_helper +	 */ +	public function __construct(language $language, factory $factory, installer $installer, install_helper $install_helper) +	{ +		$this->iohandler_factory = $factory; +		$this->installer = $installer; +		$this->language = $language; +		$this->install_helper = $install_helper; + +		parent::__construct(new \phpbb\user($language, 'datetime')); +	} + +	/** +	 * {@inheritdoc} +	 */ +	protected function configure() +	{ +		$this +			->setName('install') +			->addArgument( +				'config-file', +				InputArgument::REQUIRED, +				$this->language->lang('CLI_CONFIG_FILE')) +			->setDescription($this->language->lang('CLI_INSTALL_BOARD')) +		; +	} + +	/** +	 * Executes the command install. +	 * +	 * Install the board +	 * +	 * @param InputInterface  $input  An InputInterface instance +	 * @param OutputInterface $output An OutputInterface instance +	 * +	 * @return null +	 */ +	protected function execute(InputInterface $input, OutputInterface $output) +	{ +		$this->iohandler_factory->set_environment('cli'); + +		/** @var \phpbb\install\helper\iohandler\cli_iohandler $iohandler */ +		$iohandler = $this->iohandler_factory->get(); +		$style = new SymfonyStyle($input, $output); +		$iohandler->set_style($style, $output); + +		$this->installer->set_iohandler($iohandler); + +		$config_file = $input->getArgument('config-file'); + +		if ($this->install_helper->is_phpbb_installed()) +		{ +			$iohandler->add_error_message('INSTALL_PHPBB_INSTALLED'); + +			return 1; +		} + +		if (!is_file($config_file)) +		{ +			$iohandler->add_error_message(array('MISSING_FILE', $config_file)); + +			return 1; +		} + +		try +		{ +			$config = Yaml::parse(file_get_contents($config_file), true, false); +		} +		catch (ParseException $e) +		{ +			$iohandler->add_error_message(array('INVALID_YAML_FILE', $config_file)); + +			return 1; +		} + +		$processor = new Processor(); +		$configuration = new installer_configuration(); + +		try +		{ +			$config = $processor->processConfiguration($configuration, $config); +		} +		catch (Exception $e) +		{ +			$iohandler->add_error_message('INVALID_CONFIGURATION', $e->getMessage()); + +			return 1; +		} + +		$this->register_configuration($iohandler, $config); + +		try +		{ +			$this->installer->run(); +		} +		catch (installer_exception $e) +		{ +			$iohandler->add_error_message($e->getMessage()); +			return 1; +		} +	} + +	/** +	 * Register the configuration to simulate the forms. +	 * +	 * @param cli_iohandler $iohandler +	 * @param array $config +	 */ +	private function register_configuration(cli_iohandler $iohandler, $config) +	{ +		$iohandler->set_input('admin_name', $config['admin']['name']); +		$iohandler->set_input('admin_pass1', $config['admin']['password']); +		$iohandler->set_input('admin_pass2', $config['admin']['password']); +		$iohandler->set_input('board_email', $config['admin']['email']); +		$iohandler->set_input('submit_admin', 'submit'); + +		$iohandler->set_input('default_lang', $config['board']['lang']); +		$iohandler->set_input('board_name', $config['board']['name']); +		$iohandler->set_input('board_description', $config['board']['description']); +		$iohandler->set_input('submit_board', 'submit'); + +		$iohandler->set_input('dbms', $config['database']['dbms']); +		$iohandler->set_input('dbhost', $config['database']['dbhost']); +		$iohandler->set_input('dbport', $config['database']['dbport']); +		$iohandler->set_input('dbuser', $config['database']['dbuser']); +		$iohandler->set_input('dbpasswd', $config['database']['dbpasswd']); +		$iohandler->set_input('dbname', $config['database']['dbname']); +		$iohandler->set_input('table_prefix', $config['database']['table_prefix']); +		$iohandler->set_input('submit_database', 'submit'); + +		$iohandler->set_input('email_enable', $config['email']['enabled']); +		$iohandler->set_input('smtp_delivery', $config['email']['smtp_delivery']); +		$iohandler->set_input('smtp_host', $config['email']['smtp_host']); +		$iohandler->set_input('smtp_port', $config['email']['smtp_port']); +		$iohandler->set_input('smtp_auth', $config['email']['smtp_auth']); +		$iohandler->set_input('smtp_user', $config['email']['smtp_user']); +		$iohandler->set_input('smtp_pass', $config['email']['smtp_pass']); +		$iohandler->set_input('submit_email', 'submit'); + +		$iohandler->set_input('cookie_secure', $config['server']['cookie_secure']); +		$iohandler->set_input('server_protocol', $config['server']['server_protocol']); +		$iohandler->set_input('force_server_vars', $config['server']['force_server_vars']); +		$iohandler->set_input('server_name', $config['server']['server_name']); +		$iohandler->set_input('server_port', $config['server']['server_port']); +		$iohandler->set_input('script_path', $config['server']['script_path']); +		$iohandler->set_input('submit_server', 'submit'); +	} +} diff --git a/phpBB/phpbb/install/console/command/update/config/show.php b/phpBB/phpbb/install/console/command/update/config/show.php new file mode 100644 index 0000000000..e462763b5d --- /dev/null +++ b/phpBB/phpbb/install/console/command/update/config/show.php @@ -0,0 +1,123 @@ +<?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\install\console\command\update\config; + +use phpbb\install\helper\iohandler\factory; +use phpbb\install\updater_configuration; +use phpbb\language\language; +use Symfony\Component\Config\Definition\Exception\Exception; +use Symfony\Component\Config\Definition\Processor; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Yaml; + +class show extends \phpbb\console\command\command +{ +	/** +	 * @var factory +	 */ +	protected $iohandler_factory; + +	/** +	 * @var language +	 */ +	protected $language; + +	/** +	 * Constructor +	 * +	 * @param language $language +	 * @param factory $factory +	 */ +	public function __construct(language $language, factory $factory) +	{ +		$this->iohandler_factory = $factory; +		$this->language = $language; + +		parent::__construct(new \phpbb\user($language, 'datetime')); +	} + +	/** +	 * +	 * {@inheritdoc} +	 */ +	protected function configure() +	{ +		$this +			->setName('update:config:show') +			->addArgument( +				'config-file', +				InputArgument::REQUIRED, +				$this->language->lang('CLI_CONFIG_FILE')) +			->setDescription($this->language->lang('CLI_INSTALL_SHOW_CONFIG')) +		; +	} + +	/** +	 * Show the validated configuration +	 * +	 * @param InputInterface  $input  An InputInterface instance +	 * @param OutputInterface $output An OutputInterface instance +	 * +	 * @return null +	 */ +	protected function execute(InputInterface $input, OutputInterface $output) +	{ +		$this->iohandler_factory->set_environment('cli'); + +		/** @var \phpbb\install\helper\iohandler\cli_iohandler $iohandler */ +		$iohandler = $this->iohandler_factory->get(); +		$style = new SymfonyStyle($input, $output); +		$iohandler->set_style($style, $output); + +		$config_file = $input->getArgument('config-file'); + +		if (!is_file($config_file)) +		{ +			$iohandler->add_error_message(array('MISSING_FILE', $config_file)); + +			return; +		} + +		try +		{ +			$config = Yaml::parse(file_get_contents($config_file), true, false); +		} +		catch (ParseException $e) +		{ +			$iohandler->add_error_message('INVALID_YAML_FILE'); + +			return; +		} + +		$processor = new Processor(); +		$configuration = new updater_configuration(); + +		try +		{ +			$config = $processor->processConfiguration($configuration, $config); +		} +		catch (Exception $e) +		{ +			$iohandler->add_error_message('INVALID_CONFIGURATION', $e->getMessage()); + +			return; +		} + +		$style->block(Yaml::dump(array('updater' => $config), 10, 4, true, false)); +	} +} diff --git a/phpBB/phpbb/install/console/command/update/config/validate.php b/phpBB/phpbb/install/console/command/update/config/validate.php new file mode 100644 index 0000000000..18de5eab46 --- /dev/null +++ b/phpBB/phpbb/install/console/command/update/config/validate.php @@ -0,0 +1,124 @@ +<?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\install\console\command\update\config; + +use phpbb\install\helper\iohandler\factory; +use phpbb\install\updater_configuration; +use phpbb\language\language; +use Symfony\Component\Config\Definition\Exception\Exception; +use Symfony\Component\Config\Definition\Processor; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Yaml; + +class validate extends \phpbb\console\command\command +{ +	/** +	 * @var factory +	 */ +	protected $iohandler_factory; + +	/** +	 * @var language +	 */ +	protected $language; + +	/** +	 * Constructor +	 * +	 * @param language $language +	 * @param factory $factory +	 */ +	public function __construct(language $language, factory $factory) +	{ +		$this->iohandler_factory = $factory; +		$this->language = $language; + +		parent::__construct(new \phpbb\user($language, 'datetime')); +	} + +	/** +	 * +	 * {@inheritdoc} +	 */ +	protected function configure() +	{ +		$this +			->setName('update:config:validate') +			->addArgument( +				'config-file', +				InputArgument::REQUIRED, +				$this->language->lang('CLI_CONFIG_FILE')) +			->setDescription($this->language->lang('CLI_INSTALL_VALIDATE_CONFIG')) +		; +	} + +	/** +	 * Validate the configuration file +	 * +	 * @param InputInterface  $input  An InputInterface instance +	 * @param OutputInterface $output An OutputInterface instance +	 * +	 * @return null +	 */ +	protected function execute(InputInterface $input, OutputInterface $output) +	{ +		$this->iohandler_factory->set_environment('cli'); + +		/** @var \phpbb\install\helper\iohandler\cli_iohandler $iohandler */ +		$iohandler = $this->iohandler_factory->get(); +		$style = new SymfonyStyle($input, $output); +		$iohandler->set_style($style, $output); + +		$config_file = $input->getArgument('config-file'); + +		if (!is_file($config_file)) +		{ +			$iohandler->add_error_message(array('MISSING_FILE', array($config_file))); + +			return 1; +		} + +		try +		{ +			$config = Yaml::parse(file_get_contents($config_file), true, false); +		} +		catch (ParseException $e) +		{ +			$iohandler->add_error_message('INVALID_YAML_FILE'); + +			return 1; +		} + +		$processor = new Processor(); +		$configuration = new updater_configuration(); + +		try +		{ +			$processor->processConfiguration($configuration, $config); +		} +		catch (Exception $e) +		{ +			$iohandler->add_error_message('INVALID_CONFIGURATION', $e->getMessage()); + +			return 1; +		} + +		$iohandler->add_success_message('CONFIGURATION_VALID'); +		return 0; +	} +} diff --git a/phpBB/phpbb/install/console/command/update/update.php b/phpBB/phpbb/install/console/command/update/update.php new file mode 100644 index 0000000000..116f42f758 --- /dev/null +++ b/phpBB/phpbb/install/console/command/update/update.php @@ -0,0 +1,179 @@ +<?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\install\console\command\update; + +use phpbb\install\exception\installer_exception; +use phpbb\install\helper\install_helper; +use phpbb\install\helper\iohandler\cli_iohandler; +use phpbb\install\helper\iohandler\factory; +use phpbb\install\installer; +use phpbb\install\updater_configuration; +use phpbb\language\language; +use Symfony\Component\Config\Definition\Exception\Exception; +use Symfony\Component\Config\Definition\Processor; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Yaml; + +class update extends \phpbb\console\command\command +{ +	/** +	 * @var factory +	 */ +	protected $iohandler_factory; + +	/** +	 * @var installer +	 */ +	protected $installer; + +	/** +	 * @var install_helper +	 */ +	protected $install_helper; + +	/** +	 * @var language +	 */ +	protected $language; + +	/** +	 * Constructor +	 * +	 * @param language			$language +	 * @param factory			$factory +	 * @param installer			$installer +	 * @param install_helper	$install_helper +	 */ +	public function __construct(language $language, factory $factory, installer $installer, install_helper $install_helper) +	{ +		$this->iohandler_factory = $factory; +		$this->installer = $installer; +		$this->language = $language; +		$this->install_helper = $install_helper; + +		parent::__construct(new \phpbb\user($language, 'datetime')); +	} + +	/** +	 * {@inheritdoc} +	 */ +	protected function configure() +	{ +		$this +			->setName('update') +			->addArgument( +				'config-file', +				InputArgument::REQUIRED, +				$this->language->lang('CLI_CONFIG_FILE')) +			->setDescription($this->language->lang('CLI_UPDATE_BOARD')) +		; +	} + +	/** +	 * Executes the command update. +	 * +	 * Update the board +	 * +	 * @param InputInterface  $input  An InputInterface instance +	 * @param OutputInterface $output An OutputInterface instance +	 * +	 * @return int +	 */ +	protected function execute(InputInterface $input, OutputInterface $output) +	{ +		$this->iohandler_factory->set_environment('cli'); + +		/** @var \phpbb\install\helper\iohandler\cli_iohandler $iohandler */ +		$iohandler = $this->iohandler_factory->get(); +		$style = new SymfonyStyle($input, $output); +		$iohandler->set_style($style, $output); + +		$this->installer->set_iohandler($iohandler); + +		$config_file = $input->getArgument('config-file'); + +		if (!$this->install_helper->is_phpbb_installed()) +		{ +			$iohandler->add_error_message('INSTALL_PHPBB_NOT_INSTALLED'); + +			return 1; +		} + +		if (!is_file($config_file)) +		{ +			$iohandler->add_error_message(array('MISSING_FILE', $config_file)); + +			return 1; +		} + +		try +		{ +			$config = Yaml::parse(file_get_contents($config_file), true, false); +		} +		catch (ParseException $e) +		{ +			$iohandler->add_error_message(array('INVALID_YAML_FILE', $config_file)); + +			return 1; +		} + +		$processor = new Processor(); +		$configuration = new updater_configuration(); + +		try +		{ +			$config = $processor->processConfiguration($configuration, $config); +		} +		catch (Exception $e) +		{ +			$iohandler->add_error_message('INVALID_CONFIGURATION', $e->getMessage()); + +			return 1; +		} + +		$this->register_configuration($iohandler, $config); + +		try +		{ +			$this->installer->run(); +		} +		catch (installer_exception $e) +		{ +			$iohandler->add_error_message($e->getMessage()); +			return 1; +		} +	} + +	/** +	 * Register the configuration to simulate the forms. +	 * +	 * @param cli_iohandler $iohandler +	 * @param array $config +	 */ +	private function register_configuration(cli_iohandler $iohandler, $config) +	{ +		$iohandler->set_input('update_type', $config['type']); +		$iohandler->set_input('submit_update', 'submit'); + +		$iohandler->set_input('compression_method', '.tar'); +		$iohandler->set_input('method', 'direct_file'); +		$iohandler->set_input('submit_update_file', 'submit'); + +		$iohandler->set_input('submit_continue_file_update', 'submit'); +	} +} diff --git a/phpBB/phpbb/install/controller/archive_download.php b/phpBB/phpbb/install/controller/archive_download.php new file mode 100644 index 0000000000..eabc0a9976 --- /dev/null +++ b/phpBB/phpbb/install/controller/archive_download.php @@ -0,0 +1,93 @@ +<?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\install\controller; + +use phpbb\exception\http_exception; +use phpbb\install\helper\config; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; + +class archive_download +{ +	/** +	 * @var config +	 */ +	protected $installer_config; + +	/** +	 * Constructor +	 * +	 * @param config $config +	 */ +	public function __construct(config $config) +	{ +		$this->installer_config = $config; +		$this->installer_config->load_config(); +	} + +	/** +	 * Sends response with the merge conflict archive +	 * +	 * Merge conflicts always have to be resolved manually, +	 * so we use a different archive for that. +	 * +	 * @return BinaryFileResponse +	 */ +	public function conflict_archive() +	{ +		$filename = $this->installer_config->get('update_file_conflict_archive', ''); + +		if (empty($filename)) +		{ +			throw new http_exception(404, 'URL_NOT_FOUND'); +		} + +		return $this->send_response($filename); +	} + +	/** +	 * Sends response with the updated files' archive +	 * +	 * @return BinaryFileResponse +	 */ +	public function update_archive() +	{ +		$filename = $this->installer_config->get('update_file_archive', ''); + +		if (empty($filename)) +		{ +			throw new http_exception(404, 'URL_NOT_FOUND'); +		} + +		return $this->send_response($filename); +	} + +	/** +	 * Generates a download response +	 * +	 * @param string	$filename	Path to the file to download +	 * +	 * @return BinaryFileResponse	Response object +	 */ +	private function send_response($filename) +	{ +		$response = new BinaryFileResponse($filename); +		$response->setContentDisposition( +			ResponseHeaderBag::DISPOSITION_ATTACHMENT, +			basename($filename) +		); + +		return $response; +	} +} diff --git a/phpBB/phpbb/install/controller/helper.php b/phpBB/phpbb/install/controller/helper.php new file mode 100644 index 0000000000..ff7e691224 --- /dev/null +++ b/phpBB/phpbb/install/controller/helper.php @@ -0,0 +1,413 @@ +<?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\install\controller; + +use phpbb\install\helper\config; +use phpbb\install\helper\navigation\navigation_provider; +use phpbb\language\language; +use phpbb\language\language_file_helper; +use phpbb\path_helper; +use phpbb\request\request; +use phpbb\request\request_interface; +use phpbb\routing\router; +use phpbb\symfony_request; +use phpbb\template\template; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Cookie; + +/** + * A duplicate of \phpbb\controller\helper + * + * This class is necessary because of controller\helper's legacy function calls + * to page_header() page_footer() functions which has unavailable dependencies. + */ +class helper +{ +	/** +	 * @var config +	 */ +	protected $installer_config; + +	/** +	 * @var \phpbb\language\language +	 */ +	protected $language; + +	/** +	 * @var bool|string +	 */ +	protected $language_cookie; + +	/** +	 * @var \phpbb\language\language_file_helper +	 */ +	protected $lang_helper; + +	/** +	 * @var \phpbb\install\helper\navigation\navigation_provider +	 */ +	protected $navigation_provider; + +	/** +	 * @var \phpbb\template\template +	 */ +	protected $template; + +	/** +	 * @var \phpbb\path_helper +	 */ +	protected $path_helper; + +	/** +	 * @var \phpbb\request\request +	 */ +	protected $phpbb_request; + +	/** +	 * @var \phpbb\symfony_request +	 */ +	protected $request; + +	/** +	 * @var \phpbb\routing\router +	 */ +	protected $router; + +	/** +	 * @var string +	 */ +	protected $phpbb_admin_path; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * Constructor +	 * +	 * @param config				$config +	 * @param language				$language +	 * @param language_file_helper	$lang_helper +	 * @param navigation_provider	$nav +	 * @param template				$template +	 * @param path_helper			$path_helper +	 * @param request				$phpbb_request +	 * @param symfony_request		$request +	 * @param router				$router +	 * @param string				$phpbb_root_path +	 */ +	public function __construct(config $config, language $language, language_file_helper $lang_helper, navigation_provider $nav, template $template, path_helper $path_helper, request $phpbb_request, symfony_request $request, router $router, $phpbb_root_path) +	{ +		$this->installer_config = $config; +		$this->language = $language; +		$this->language_cookie = false; +		$this->lang_helper = $lang_helper; +		$this->navigation_provider = $nav; +		$this->template = $template; +		$this->path_helper = $path_helper; +		$this->phpbb_request = $phpbb_request; +		$this->request = $request; +		$this->router = $router; +		$this->phpbb_root_path = $phpbb_root_path; +		$this->phpbb_admin_path = $phpbb_root_path . 'adm/'; +	} + +	/** +	 * Automate setting up the page and creating the response object. +	 * +	 * @param string	$template_file		The template handle to render +	 * @param string	$page_title			The title of the page to output +	 * @param bool		$selected_language	True to enable language selector it, false otherwise +	 * @param int		$status_code		The status code to be sent to the page header +	 * +	 * @return Response object containing rendered page +	 */ +	public function render($template_file, $page_title = '', $selected_language = false, $status_code = 200) +	{ +		$this->page_header($page_title, $selected_language); + +		$this->template->set_filenames(array( +			'body'	=> $template_file, +		)); + +		$response = new Response($this->template->assign_display('body'), $status_code); + +		// Set language cookie +		if ($this->language_cookie !== false) +		{ +			$cookie = new Cookie('lang', $this->language_cookie, time() + 3600); +			$response->headers->setCookie($cookie); + +			$this->language_cookie = false; +		} + +		return $response; +	} + +	/** +	 * Returns path from route name +	 * +	 * @param string	$route_name +	 * @param array		$parameters +	 * +	 * @return string +	 */ +	public function route($route_name, $parameters = array()) +	{ +		$url = $this->router->generate($route_name, $parameters); + +		return $url; +	} + +	/** +	 * Handles language selector form +	 */ +	public function handle_language_select() +	{ +		$lang = null; + +		// Check if language form has been submited +		$submit = $this->phpbb_request->variable('change_lang', ''); +		if (!empty($submit)) +		{ +			$lang = $this->phpbb_request->variable('language', ''); +		} + +		// Retrieve language from cookie +		$lang_cookie = $this->phpbb_request->variable('lang', '', false, request_interface::COOKIE); +		if (empty($lang) && !empty($lang_cookie)) +		{ +			$lang = $lang_cookie; +		} + +		$lang = (!empty($lang) && strpos($lang, '/') === false) ? $lang : null; +		$this->language_cookie = $lang; + +		$this->render_language_select($lang); + +		if ($lang !== null) +		{ +			$this->language->set_user_language($lang, true); +			$this->installer_config->set('user_language', $lang); +		} +	} + +	/** +	 * Process navigation data to reflect active/completed stages +	 * +	 * @param \phpbb\install\helper\iohandler\iohandler_interface|null	$iohandler +	 */ +	public function handle_navigation($iohandler = null) +	{ +		$nav_data = $this->installer_config->get_navigation_data(); + +		// Set active navigation stage +		if (isset($nav_data['active']) && is_array($nav_data['active'])) +		{ +			if ($iohandler !== null) +			{ +				$iohandler->set_active_stage_menu($nav_data['active']); +			} + +			$this->navigation_provider->set_nav_property($nav_data['active'], array( +				'selected'	=> true, +				'completed'	=> false, +			)); +		} + +		// Set finished navigation stages +		if (isset($nav_data['finished']) && is_array($nav_data['finished'])) +		{ +			foreach ($nav_data['finished'] as $finished_stage) +			{ +				if ($iohandler !== null) +				{ +					$iohandler->set_finished_stage_menu($finished_stage); +				} + +				$this->navigation_provider->set_nav_property($finished_stage, array( +					'selected'	=> false, +					'completed'	=> true, +				)); +			} +		} +	} + +	/** +	 * Set default template variables +	 * +	 * @param string	$page_title			Title of the page +	 * @param bool		$selected_language	True to enable language selector it, false otherwise +	 */ +	protected function page_header($page_title, $selected_language = false) +	{ +		// Path to templates +		$paths = array($this->phpbb_root_path . 'install/update/new/adm/', $this->phpbb_admin_path); +		$paths = array_filter($paths, 'is_dir'); +		$path = array_shift($paths); +		$path = substr($path, strlen($this->phpbb_root_path)); + +		$this->template->assign_vars(array( +			'L_CHANGE'				=> $this->language->lang('CHANGE'), +			'L_COLON'				=> $this->language->lang('COLON'), +			'L_INSTALL_PANEL'		=> $this->language->lang('INSTALL_PANEL'), +			'L_SELECT_LANG'			=> $this->language->lang('SELECT_LANG'), +			'L_SKIP'				=> $this->language->lang('SKIP'), +			'PAGE_TITLE'			=> $this->language->lang($page_title), +			'T_IMAGE_PATH'			=> $this->path_helper->get_web_root_path() . $path . 'images', +			'T_JQUERY_LINK'			=> $this->path_helper->get_web_root_path() . $path . '../assets/javascript/jquery.min.js', +			'T_TEMPLATE_PATH'		=> $this->path_helper->get_web_root_path() . $path . 'style', +			'T_ASSETS_PATH'			=> $this->path_helper->get_web_root_path() . $path . '../assets', + +			'S_CONTENT_DIRECTION' 	=> $this->language->lang('DIRECTION'), +			'S_CONTENT_FLOW_BEGIN'	=> ($this->language->lang('DIRECTION') === 'ltr') ? 'left' : 'right', +			'S_CONTENT_FLOW_END'	=> ($this->language->lang('DIRECTION') === 'ltr') ? 'right' : 'left', +			'S_CONTENT_ENCODING' 	=> 'UTF-8', +			'S_LANG_SELECT'			=> $selected_language, + +			'S_USER_LANG'			=> $this->language->lang('USER_LANG'), +		)); + +		$this->render_navigation(); +	} + +	/** +	 * Render navigation +	 */ +	protected function render_navigation() +	{ +		// Get navigation items +		$nav_array = $this->navigation_provider->get(); +		$nav_array = $this->sort_navigation_level($nav_array); + +		$active_main_menu = $this->get_active_main_menu($nav_array); + +		// Pass navigation to template +		foreach ($nav_array as $key => $entry) +		{ +			$this->template->assign_block_vars('t_block1', array( +				'L_TITLE' => $this->language->lang($entry['label']), +				'S_SELECTED' => ($active_main_menu === $key), +				'U_TITLE' => $this->route($entry['route']), +			)); + +			if (is_array($entry[0]) && $active_main_menu === $key) +			{ +				$entry[0] = $this->sort_navigation_level($entry[0]); + +				foreach ($entry[0] as $name => $sub_entry) +				{ +					if (isset($sub_entry['stage']) && $sub_entry['stage'] === true) +					{ +						$this->template->assign_block_vars('l_block2', array( +							'L_TITLE' => $this->language->lang($sub_entry['label']), +							'S_SELECTED' => (isset($sub_entry['selected']) && $sub_entry['selected'] === true), +							'S_COMPLETE' => (isset($sub_entry['completed']) && $sub_entry['completed'] === true), +							'STAGE_NAME' => $name, +						)); +					} +					else +					{ +						$this->template->assign_block_vars('l_block1', array( +							'L_TITLE' => $this->language->lang($sub_entry['label']), +							'S_SELECTED' => (isset($sub_entry['route']) && $sub_entry['route'] === $this->request->get('_route')), +							'U_TITLE' => $this->route($sub_entry['route']), +						)); +					} +				} +			} +		} +	} + +	/** +	 * Render language select form +	 * +	 * @param string	$selected_language +	 */ +	protected function render_language_select($selected_language = null) +	{ +		$langs = $this->lang_helper->get_available_languages(); +		foreach ($langs as $lang) +		{ +			$this->template->assign_block_vars('language_select_item', array( +				'VALUE' => $lang['iso'], +				'NAME' => $lang['local_name'], +				'SELECTED' => ($lang['iso'] === $selected_language), +			)); +		} +	} + +	/** +	 * Returns the name of the active main menu item +	 * +	 * @param array	$nav_array +	 * +	 * @return string|bool	Returns the name of the active main menu element, if the element not found, returns false +	 */ +	protected function get_active_main_menu($nav_array) +	{ +		$active_route = $this->request->get('_route'); + +		foreach ($nav_array as $nav_name => $nav_options) +		{ +			$current_menu = $nav_name; + +			if (isset($nav_options['route']) && $nav_options['route'] === $active_route) +			{ +				return $nav_name; +			} + +			if (is_array($nav_options[0])) +			{ +				foreach ($nav_options[0] as $sub_menus) +				{ +					if (isset($sub_menus['route']) && $sub_menus['route'] === $active_route) +					{ +						return $current_menu; +					} +				} +			} +		} + +		return false; +	} + +	/** +	 * Sorts the top level of navigation array +	 * +	 * @param array	$nav_array	Navigation array +	 * +	 * @return array +	 */ +	protected function sort_navigation_level($nav_array) +	{ +		$sorted = array(); +		foreach ($nav_array as $key => $nav) +		{ +			$order = (isset($nav['order'])) ? $nav['order'] : 0; +			$sorted[$order][$key] = $nav; +		} + +		// Linearization of navigation array +		$nav_array = array(); +		ksort($sorted); +		foreach ($sorted as $nav) +		{ +			$nav_array = array_merge($nav_array, $nav); +		} + +		return $nav_array; +	} +} diff --git a/phpBB/phpbb/install/controller/install.php b/phpBB/phpbb/install/controller/install.php new file mode 100644 index 0000000000..92506872a3 --- /dev/null +++ b/phpBB/phpbb/install/controller/install.php @@ -0,0 +1,172 @@ +<?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\install\controller; + +use phpbb\exception\http_exception; +use phpbb\install\helper\install_helper; +use phpbb\install\helper\navigation\navigation_provider; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpFoundation\Response; +use phpbb\install\helper\iohandler\factory; +use phpbb\template\template; +use phpbb\request\request_interface; +use phpbb\install\installer; +use phpbb\language\language; + +/** + * Controller for installing phpBB + */ +class install +{ +	/** +	 * @var helper +	 */ +	protected $controller_helper; + +	/** +	 * @var factory +	 */ +	protected $iohandler_factory; + +	/** +	 * @var navigation_provider +	 */ +	protected $menu_provider; + +	/** +	 * @var language +	 */ +	protected $language; + +	/** +	 * @var template +	 */ +	protected $template; + +	/** +	 * @var request_interface +	 */ +	protected $request; + +	/** +	 * @var installer +	 */ +	protected $installer; + +	/** +	 * @var install_helper +	 */ +	protected $install_helper; + +	/** +	 * Constructor +	 * +	 * @param helper 				$helper +	 * @param factory 				$factory +	 * @param navigation_provider	$nav_provider +	 * @param language				$language +	 * @param template				$template +	 * @param request_interface		$request +	 * @param installer				$installer +	 * @param install_helper		$install_helper +	 */ +	public function __construct(helper $helper, factory $factory, navigation_provider $nav_provider, language $language, template $template, request_interface $request, installer $installer, install_helper $install_helper) +	{ +		$this->controller_helper	= $helper; +		$this->iohandler_factory	= $factory; +		$this->menu_provider		= $nav_provider; +		$this->language				= $language; +		$this->template				= $template; +		$this->request				= $request; +		$this->installer			= $installer; +		$this->install_helper		= $install_helper; +	} + +	/** +	 * Controller logic +	 * +	 * @return Response|StreamedResponse +	 * +	 * @throws http_exception When phpBB is already installed +	 */ +	public function handle() +	{ +		if ($this->install_helper->is_phpbb_installed()) +		{ +			throw new http_exception(403, 'INSTALL_PHPBB_INSTALLED'); +		} + +		$this->template->assign_vars(array( +			'U_ACTION' => $this->controller_helper->route('phpbb_installer_install'), +		)); + +		// Set up input-output handler +		if ($this->request->is_ajax()) +		{ +			$this->iohandler_factory->set_environment('ajax'); +		} +		else +		{ +			$this->iohandler_factory->set_environment('nojs'); +		} + +		// Set the appropriate input-output handler +		$this->installer->set_iohandler($this->iohandler_factory->get()); +		$this->controller_helper->handle_language_select(); + +		if ($this->request->is_ajax()) +		{ +			$installer = $this->installer; +			$response = new StreamedResponse(); +			$response->setCallback(function() use ($installer) { +				$installer->run(); +			}); + +			// Try to bypass any server output buffers +			$response->headers->set('X-Accel-Buffering', 'no'); + +			return $response; +		} +		else +		{ +			// Determine whether the installation was started or not +			if (true) +			{ +				// Set active stage +				$this->menu_provider->set_nav_property( +					array('install', 0, 'introduction'), +					array( +						'selected'	=> true, +						'completed'	=> false, +					) +				); + +				// If not, let's render the welcome page +				$this->template->assign_vars(array( +					'SHOW_INSTALL_START_FORM'	=> true, +					'TITLE'						=> $this->language->lang('INSTALL_INTRO'), +					'CONTENT'					=> $this->language->lang('INSTALL_INTRO_BODY'), +				)); + +				/** @var \phpbb\install\helper\iohandler\iohandler_interface $iohandler */ +				$iohandler = $this->iohandler_factory->get(); +				$this->controller_helper->handle_navigation($iohandler); + +				return $this->controller_helper->render('installer_install.html', 'INSTALL', true); +			} + +			// @todo: implement no js controller logic +		} +	} +} diff --git a/phpBB/phpbb/install/controller/installer_index.php b/phpBB/phpbb/install/controller/installer_index.php new file mode 100644 index 0000000000..c2d9572284 --- /dev/null +++ b/phpBB/phpbb/install/controller/installer_index.php @@ -0,0 +1,81 @@ +<?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\install\controller; + +class installer_index +{ +	/** +	 * @var helper +	 */ +	protected $helper; + +	/** +	 * @var \phpbb\language\language +	 */ +	protected $language; + +	/** +	 * @var \phpbb\template\template +	 */ +	protected $template; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * Constructor +	 * +	 * @param helper 					$helper +	 * @param \phpbb\language\language	$language +	 * @param \phpbb\template\template	$template +	 * @param string					$phpbb_root_path +	 */ +	public function __construct(helper $helper, \phpbb\language\language $language, \phpbb\template\template $template, $phpbb_root_path) +	{ +		$this->helper = $helper; +		$this->language = $language; +		$this->template = $template; +		$this->phpbb_root_path = $phpbb_root_path; +	} + +	public function handle($mode) +	{ +		$this->helper->handle_language_select(); + +		switch ($mode) +		{ +			case "intro": +				$title = $this->language->lang('INTRODUCTION_TITLE'); +				$body = $this->language->lang('INTRODUCTION_BODY'); +			break; +			case "support": +				$title = $this->language->lang('SUPPORT_TITLE'); +				$body = $this->language->lang('SUPPORT_BODY'); +			break; +			case "license": +				$title = $this->language->lang('LICENSE_TITLE'); +				$body = implode("<br/>\n", file($this->phpbb_root_path . 'docs/LICENSE.txt')); +			break; +		} + +		$this->template->assign_vars(array( +			'TITLE'	=> $title, +			'BODY'	=> $body, +		)); + +		return $this->helper->render('installer_main.html', $title, true); +	} +} diff --git a/phpBB/phpbb/install/controller/timeout_check.php b/phpBB/phpbb/install/controller/timeout_check.php new file mode 100644 index 0000000000..1c90e3caf3 --- /dev/null +++ b/phpBB/phpbb/install/controller/timeout_check.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\install\controller; + +use Symfony\Component\HttpFoundation\JsonResponse; + +class timeout_check +{ +	/** +	 * @var helper +	 */ +	protected $helper; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * Constructor +	 * +	 * @param helper	$helper +	 * @param string	$phpbb_root_path +	 */ +	public function __construct(helper $helper, $phpbb_root_path) +	{ +		$this->helper = $helper; +		$this->phpbb_root_path = $phpbb_root_path; +	} + +	/** +	 * Controller for querying installer status +	 */ +	public function status() +	{ +		$lock_file = $this->phpbb_root_path . 'store/io_lock.lock'; +		$response = new JsonResponse(); + +		if (!file_exists($lock_file)) +		{ +			$response->setData(array( +				'status' => 'fail', +			)); +		} +		else +		{ +			$fp = @fopen($lock_file, 'r'); + +			if ($fp && flock($fp, LOCK_EX | LOCK_NB)) +			{ +				$status = (filesize($lock_file) >= 2 && fread($fp, 2) === 'ok') ? 'continue' : 'fail'; + +				$response->setData(array( +					'status' => $status, +				)); +				flock($fp, LOCK_UN); +				fclose($fp); +			} +			else +			{ +				$response->setData(array( +					'status' => 'running', +				)); +			} +		} + +		return $response; +	} +} diff --git a/phpBB/phpbb/install/controller/update.php b/phpBB/phpbb/install/controller/update.php new file mode 100644 index 0000000000..6b88827940 --- /dev/null +++ b/phpBB/phpbb/install/controller/update.php @@ -0,0 +1,166 @@ +<?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\install\controller; + +use phpbb\exception\http_exception; +use phpbb\install\helper\install_helper; +use phpbb\install\helper\iohandler\factory; +use phpbb\install\helper\navigation\navigation_provider; +use phpbb\install\installer; +use phpbb\language\language; +use phpbb\request\request_interface; +use phpbb\template\template; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * Updater controller + */ +class update +{ +	/** +	 * @var helper +	 */ +	protected $controller_helper; + +	/** +	 * @var installer +	 */ +	protected $installer; + +	/** +	 * @var install_helper +	 */ +	protected $install_helper; + +	/** +	 * @var factory +	 */ +	protected $iohandler_factory; + +	/** +	 * @var language +	 */ +	protected $language; + +	/** +	 * @var navigation_provider +	 */ +	protected $menu_provider; + +	/** +	 * @var request_interface +	 */ +	protected $request; + +	/** +	 * @var template +	 */ +	protected $template; + +	/** +	 * Constructor +	 * +	 * @param helper				$controller_helper +	 * @param installer				$installer +	 * @param install_helper		$install_helper +	 * @param factory				$iohandler +	 * @param language				$language +	 * @param navigation_provider	$menu_provider +	 * @param request_interface		$request +	 * @param template				$template +	 */ +	public function __construct(helper $controller_helper, installer $installer, install_helper $install_helper, factory $iohandler, language $language, navigation_provider $menu_provider, request_interface $request, template $template) +	{ +		$this->controller_helper	= $controller_helper; +		$this->installer			= $installer; +		$this->install_helper		= $install_helper; +		$this->iohandler_factory	= $iohandler; +		$this->language				= $language; +		$this->menu_provider		= $menu_provider; +		$this->request				= $request; +		$this->template				= $template; +	} + +	/** +	 * Controller entry point +	 * +	 * @return Response|StreamedResponse +	 * +	 * @throws http_exception When phpBB is not installed +	 */ +	public function handle() +	{ +		if (!$this->install_helper->is_phpbb_installed()) +		{ +			throw new http_exception(403, 'INSTALL_PHPBB_NOT_INSTALLED'); +		} + +		$this->template->assign_vars(array( +			'U_ACTION' => $this->controller_helper->route('phpbb_installer_update'), +		)); + +		// Set up input-output handler +		if ($this->request->is_ajax()) +		{ +			$this->iohandler_factory->set_environment('ajax'); +		} +		else +		{ +			$this->iohandler_factory->set_environment('nojs'); +		} + +		// Set the appropriate input-output handler +		$this->installer->set_iohandler($this->iohandler_factory->get()); +		$this->controller_helper->handle_language_select(); + +		// Render the intro page +		if ($this->request->is_ajax()) +		{ +			$installer = $this->installer; +			$response = new StreamedResponse(); +			$response->setCallback(function() use ($installer) { +				$installer->run(); +			}); + +			// Try to bypass any server output buffers +			$response->headers->set('X-Accel-Buffering', 'no'); +			$response->headers->set('Content-type', 'application/json'); + +			return $response; +		} +		else +		{ +			// Set active stage +			$this->menu_provider->set_nav_property( +				array('update', 0, 'introduction'), +				array( +					'selected'	=> true, +					'completed'	=> false, +				) +			); + +			$this->template->assign_vars(array( +				'SHOW_INSTALL_START_FORM'	=> true, +				'TITLE'						=> $this->language->lang('UPDATE_INSTALLATION'), +				'CONTENT'					=> $this->language->lang('UPDATE_INSTALLATION_EXPLAIN'), +			)); + +			/** @var \phpbb\install\helper\iohandler\iohandler_interface $iohandler */ +			$iohandler = $this->iohandler_factory->get(); +			$this->controller_helper->handle_navigation($iohandler); + +			return $this->controller_helper->render('installer_update.html', 'UPDATE_INSTALLATION', true); +		} +	} +} diff --git a/phpBB/phpbb/install/event/kernel_exception_subscriber.php b/phpBB/phpbb/install/event/kernel_exception_subscriber.php new file mode 100644 index 0000000000..60b7d9a400 --- /dev/null +++ b/phpBB/phpbb/install/event/kernel_exception_subscriber.php @@ -0,0 +1,126 @@ +<?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\install\event; + +use phpbb\exception\exception_interface; +use phpbb\install\controller\helper; +use phpbb\language\language; +use phpbb\template\template; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpFoundation\JsonResponse; + +/** + * Exception handler for the installer + */ +class kernel_exception_subscriber implements EventSubscriberInterface +{ +	/** +	 * @var helper +	 */ +	protected $controller_helper; + +	/** +	 * @var language +	 */ +	protected $language; + +	/** +	 * @var template +	 */ +	protected $template; + +	/** +	 * Constructor +	 * +	 * @param helper	$controller_helper +	 * @param language	$language +	 * @param template	$template +	 */ +	public function __construct(helper $controller_helper, language $language, template $template) +	{ +		$this->controller_helper	= $controller_helper; +		$this->language				= $language; +		$this->template				= $template; +	} + +	/** +	 * This listener is run when the KernelEvents::EXCEPTION event is triggered +	 * +	 * @param GetResponseForExceptionEvent	$event +	 */ +	public function on_kernel_exception(GetResponseForExceptionEvent $event) +	{ +		$exception = $event->getException(); +		$message = $exception->getMessage(); + +		if ($exception instanceof exception_interface) +		{ +			$message = $this->language->lang_array($message, $exception->get_parameters()); +		} + +		if (!$event->getRequest()->isXmlHttpRequest()) +		{ +			$this->template->assign_vars(array( +				'TITLE'	=> $this->language->lang('INFORMATION'), +				'BODY'	=> $message, +			)); + +			$response = $this->controller_helper->render( +				'installer_main.html', +				$this->language->lang('INFORMATION'), +				false, +				500 +			); +		} +		else +		{ +			$data = array(); + +			if (!empty($message)) +			{ +				$data['message'] = $message; +			} + +			if (defined('DEBUG')) +			{ +				$data['trace'] = $exception->getTrace(); +			} + +			$response = new JsonResponse($data, 500); +		} + +		if ($exception instanceof HttpExceptionInterface) +		{ +			$response->setStatusCode($exception->getStatusCode()); +			$response->headers->add($exception->getHeaders()); +		} + +		$event->setResponse($response); +	} + +	/** +	 * Returns an array of events the object is subscribed to +	 * +	 * @return array	Array of events the object is subscribed to +	 */ +	static public function getSubscribedEvents() +	{ +		return array( +			KernelEvents::EXCEPTION		=> 'on_kernel_exception', +		); +	} +} diff --git a/phpBB/phpbb/install/exception/cannot_build_container_exception.php b/phpBB/phpbb/install/exception/cannot_build_container_exception.php new file mode 100644 index 0000000000..6cf12b008b --- /dev/null +++ b/phpBB/phpbb/install/exception/cannot_build_container_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\install\exception; + +/** + * Thrown when the container cannot be built + */ +class cannot_build_container_exception extends installer_exception +{ + +} diff --git a/phpBB/phpbb/install/exception/file_updater_failure_exception.php b/phpBB/phpbb/install/exception/file_updater_failure_exception.php new file mode 100644 index 0000000000..46ba2ed32d --- /dev/null +++ b/phpBB/phpbb/install/exception/file_updater_failure_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\install\exception; + +/** + * Thrown when the file updater fails + */ +class file_updater_failure_exception extends installer_exception +{ + +} diff --git a/phpBB/phpbb/install/exception/installer_config_not_writable_exception.php b/phpBB/phpbb/install/exception/installer_config_not_writable_exception.php new file mode 100644 index 0000000000..51864c5dca --- /dev/null +++ b/phpBB/phpbb/install/exception/installer_config_not_writable_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\install\exception; + +/** + * Thrown when installer config is not writable to disk + */ +class installer_config_not_writable_exception extends installer_exception +{ + +} diff --git a/phpBB/phpbb/install/exception/installer_exception.php b/phpBB/phpbb/install/exception/installer_exception.php new file mode 100644 index 0000000000..f17dca8f17 --- /dev/null +++ b/phpBB/phpbb/install/exception/installer_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\install\exception; + +use phpbb\exception\runtime_exception; + +/** + * Installer's base exception + */ +class installer_exception extends runtime_exception +{ + +} diff --git a/phpBB/phpbb/install/exception/invalid_dbms_exception.php b/phpBB/phpbb/install/exception/invalid_dbms_exception.php new file mode 100644 index 0000000000..38de5f613a --- /dev/null +++ b/phpBB/phpbb/install/exception/invalid_dbms_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\install\exception; + +/** + * Thrown when an unavailable DBMS has been selected + */ +class invalid_dbms_exception extends installer_exception +{ + +} diff --git a/phpBB/phpbb/install/exception/jump_to_restart_point_exception.php b/phpBB/phpbb/install/exception/jump_to_restart_point_exception.php new file mode 100644 index 0000000000..b628c4fbe3 --- /dev/null +++ b/phpBB/phpbb/install/exception/jump_to_restart_point_exception.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\install\exception; + +class jump_to_restart_point_exception extends installer_exception +{ +	/** +	 * @var string +	 */ +	protected $restart_point_name; + +	/** +	 * Constructor +	 * +	 * @param string $restart_point_name +	 */ +	public function __construct($restart_point_name) +	{ +		$this->restart_point_name = $restart_point_name; + +		parent::__construct(); +	} + +	/** +	 * Returns the restart point's name +	 * +	 * @return string +	 */ +	public function get_restart_point_name() +	{ +		return $this->restart_point_name; +	} +} diff --git a/phpBB/phpbb/install/exception/resource_limit_reached_exception.php b/phpBB/phpbb/install/exception/resource_limit_reached_exception.php new file mode 100644 index 0000000000..025e09fbd3 --- /dev/null +++ b/phpBB/phpbb/install/exception/resource_limit_reached_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\install\exception; + +/** + * Thrown when the installer is out of memory or time + */ +class resource_limit_reached_exception extends installer_exception +{ + +} diff --git a/phpBB/phpbb/install/exception/user_interaction_required_exception.php b/phpBB/phpbb/install/exception/user_interaction_required_exception.php new file mode 100644 index 0000000000..d65a448841 --- /dev/null +++ b/phpBB/phpbb/install/exception/user_interaction_required_exception.php @@ -0,0 +1,25 @@ +<?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\install\exception; + +/** + * This exception should be thrown when user interaction is inevitable + * + * Note: Please note that the output should already be setup for the user + * 		 when you use throw this exception + */ +class user_interaction_required_exception extends installer_exception +{ + +} diff --git a/phpBB/phpbb/install/helper/config.php b/phpBB/phpbb/install/helper/config.php new file mode 100644 index 0000000000..fad6749019 --- /dev/null +++ b/phpBB/phpbb/install/helper/config.php @@ -0,0 +1,450 @@ +<?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\install\helper; + +use phpbb\install\exception\installer_config_not_writable_exception; + +/** + * Stores common settings and installation status + */ +class config +{ +	/** +	 * @var \phpbb\filesystem\filesystem_interface +	 */ +	protected $filesystem; + +	/** +	 * Array which contains config settings for the installer +	 * +	 * The array will also store all the user input, as well as any +	 * data that is passed to other tasks by a task. +	 * +	 * @var array +	 */ +	protected $installer_config; + +	/** +	 * @var string +	 */ +	protected $install_config_file; + +	/** +	 * @var \bantu\IniGetWrapper\IniGetWrapper +	 */ +	protected $php_ini; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * Array containing progress information +	 * +	 * @var array +	 */ +	protected $progress_data; + +	/** +	 * Array containing system information +	 * +	 * The array contains run time and memory limitations. +	 * +	 * @var array +	 */ +	protected $system_data; + +	/** +	 * Array containing navigation bar information +	 * +	 * @var array +	 */ +	protected $navigation_data; + +	/** +	 * Flag indicating that config file should be cleaned up +	 * +	 * @var bool +	 */ +	protected $do_clean_up; + +	/** +	 * Constructor +	 */ +	public function __construct(\phpbb\filesystem\filesystem_interface $filesystem, \bantu\IniGetWrapper\IniGetWrapper $php_ini, $phpbb_root_path) +	{ +		$this->filesystem		= $filesystem; +		$this->php_ini			= $php_ini; +		$this->phpbb_root_path	= $phpbb_root_path; +		$this->do_clean_up		= false; + +		// Set up data arrays +		$this->navigation_data	= array(); +		$this->installer_config	= array(); +		$this->system_data		= array(); +		$this->progress_data	= array( +			'last_task_module_name'		=> '', // Stores the service name of the latest finished module +			'last_task_module_index'	=> 0, // Stores the index of the latest finished module +			'last_task_index'			=> 0, // Stores the index of the latest finished task +			'max_task_progress'			=> 0, +			'current_task_progress'		=> 0, +			'_restart_points'			=> array(), +			'use_restart_point'			=> false, +		); + +		$this->install_config_file = $this->phpbb_root_path . 'store/install_config.php'; + +		$this->setup_system_data(); +	} + +	/** +	 * Returns data for a specified parameter +	 * +	 * @param	string	$param_name	Name of the parameter to return +	 * @param	mixed	$default	Default value to return when the specified data +	 * 								does not exist. +	 * +	 * @return 	mixed	value of the specified parameter or the default value if the data +	 * 					cannot be recovered. +	 */ +	public function get($param_name, $default = false) +	{ +		return (isset($this->installer_config[$param_name])) ? $this->installer_config[$param_name] : $default; +	} + +	/** +	 * Sets a parameter in installer_config +	 * +	 * @param	string	$param_name	Name of the parameter +	 * @param	mixed	$value		Values to set the parameter +	 */ +	public function set($param_name, $value) +	{ +		$this->installer_config = array_merge($this->installer_config, array( +			$param_name => $value, +		)); +	} + +	/** +	 * Returns system parameter +	 * +	 * @param string	$param_name	Name of the parameter +	 * +	 * @return mixed	Returns system parameter if it is defined, false otherwise +	 */ +	public function system_get($param_name) +	{ +		return (isset($this->system_data[$param_name])) ? $this->system_data[$param_name] : false; +	} + +	/** +	 * Returns remaining time until the run time limit +	 * +	 * @return int	Remaining time until the run time limit in seconds +	 */ +	public function get_time_remaining() +	{ +		if ($this->system_data['max_execution_time'] <= 0) +		{ +			return PHP_INT_MAX; +		} + +		return ($this->system_data['start_time'] + $this->system_data['max_execution_time']) - microtime(true); +	} + +	/** +	 * Returns remaining memory available for PHP +	 * +	 * @return int	Remaining memory until reaching the limit +	 */ +	public function get_memory_remaining() +	{ +		if ($this->system_data['memory_limit'] <= 0) +		{ +			return 1; +		} + +		if (function_exists('memory_get_usage')) +		{ +			return ($this->system_data['memory_limit'] - memory_get_usage()); +		} + +		// If we cannot get the information then just return a positive number (and cross fingers) +		return 1; +	} + +	/** +	 * Saves the latest executed task +	 * +	 * @param int	$task_service_index	Index of the installer task service in the module +	 */ +	public function set_finished_task($task_service_index) +	{ +		$this->progress_data['last_task_index']	= $task_service_index; +	} + +	/** +	 * Set active module +	 * +	 * @param string	$module_service_name	Name of the installer module service +	 * @param int		$module_service_index	Index of the installer module service +	 */ +	public function set_active_module($module_service_name, $module_service_index) +	{ +		$this->progress_data['last_task_module_name']	= $module_service_name; +		$this->progress_data['last_task_module_index']	= $module_service_index; +	} + +	/** +	 * Getter for progress data +	 * +	 * @return array +	 */ +	public function get_progress_data() +	{ +		return $this->progress_data; +	} + +	/** +	 * Recovers install configuration from file +	 */ +	public function load_config() +	{ +		if (!$this->filesystem->exists($this->install_config_file)) +		{ +			return; +		} + +		$file_content = @file_get_contents($this->install_config_file); +		$serialized_data = trim(substr($file_content, 8)); + +		$installer_config = array(); +		$progress_data = array(); +		$navigation_data = array(); + +		if (!empty($serialized_data)) +		{ +			$unserialized_data = json_decode($serialized_data, true); + +			$installer_config = (is_array($unserialized_data['installer_config'])) ? $unserialized_data['installer_config'] : array(); +			$progress_data = (is_array($unserialized_data['progress_data'])) ? $unserialized_data['progress_data'] : array(); +			$navigation_data = (is_array($unserialized_data['navigation_data'])) ? $unserialized_data['navigation_data'] : array(); +		} + +		$this->installer_config = array_merge($this->installer_config, $installer_config); +		$this->progress_data = array_merge($this->progress_data, $progress_data); +		$this->navigation_data = array_merge($this->navigation_data, $navigation_data); +	} + +	/** +	 * Creates a progress restart point +	 * +	 * Restart points can be used to repeat certain tasks periodically. +	 * You need to call this method from the first task you want to repeat. +	 * +	 * @param string	$name	Name of the restart point +	 */ +	public function create_progress_restart_point($name) +	{ +		$tmp_progress_data = $this->progress_data; +		unset($tmp_progress_data['_restart_points']); + +		$this->progress_data['_restart_points'][$name] = $tmp_progress_data; +	} + +	/** +	 * Set restart point to continue from +	 * +	 * @param string	$name	Name of the restart point +	 * +	 * @return bool	Returns false if the restart point name does not exist, otherwise true +	 */ +	public function jump_to_restart_point($name) +	{ +		if (!isset($this->progress_data['_restart_points'][$name]) || empty($this->progress_data['_restart_points'][$name])) +		{ +			return false; +		} + +		foreach ($this->progress_data['_restart_points'][$name] as $key => $value) +		{ +			$this->progress_data[$key] = $value; +		} + +		return true; +	} + +	/** +	 * Returns whether a restart point with a given name exists or not +	 * +	 * @param string	$name Name of the restart point +	 * +	 * @return bool +	 */ +	public function has_restart_point($name) +	{ +		return isset($this->progress_data['_restart_points'][$name]); +	} + +	/** +	 * Dumps install configuration to disk +	 */ +	public function save_config() +	{ +		if ($this->do_clean_up) +		{ +			@unlink($this->install_config_file); +			return; +		} + +		// Create array to save +		$save_array = array( +			'installer_config'	=> $this->installer_config, +			'progress_data'		=> $this->progress_data, +			'navigation_data'	=> $this->navigation_data, +		); + +		// Create file content +		$file_content = '<?php // '; +		$file_content .= json_encode($save_array); +		$file_content .= "\n"; + +		// Dump file_content to disk +		$fp = @fopen($this->install_config_file, 'w'); +		if (!$fp) +		{ +			throw new installer_config_not_writable_exception(); +		} + +		fwrite($fp, $file_content); +		fclose($fp); +	} + +	/** +	 * Increments the task progress +	 * +	 * @param int	$increment_by	The amount to increment by +	 */ +	public function increment_current_task_progress($increment_by = 1) +	{ +		$this->progress_data['current_task_progress'] += $increment_by; + +		if ($this->progress_data['current_task_progress'] > $this->progress_data['max_task_progress']) +		{ +			$this->progress_data['current_task_progress'] = $this->progress_data['max_task_progress']; +		} +	} + +	/** +	 * Sets the task progress to a specific number +	 * +	 * @param int	$task_progress	The task progress number to be set +	 */ +	public function set_current_task_progress($task_progress) +	{ +		$this->progress_data['current_task_progress'] = $task_progress; +	} + +	/** +	 * Sets the number of tasks belonging to the installer in the current mode. +	 * +	 * @param int	$task_progress_count	Number of tasks +	 */ +	public function set_task_progress_count($task_progress_count) +	{ +		$this->progress_data['max_task_progress'] = $task_progress_count; +	} + +	/** +	 * Returns the number of the current task being executed +	 * +	 * @return int +	 */ +	public function get_current_task_progress() +	{ +		return $this->progress_data['current_task_progress']; +	} + +	/** +	 * Returns the number of tasks belonging to the installer in the current mode. +	 * +	 * @return int +	 */ +	public function get_task_progress_count() +	{ +		return $this->progress_data['max_task_progress']; +	} + +	/** +	 * Marks stage as completed in the navigation bar +	 * +	 * @param array	$nav_path	Array to the navigation elem +	 */ +	public function set_finished_navigation_stage($nav_path) +	{ +		if (isset($this->navigation_data['finished']) && in_array($nav_path, $this->navigation_data['finished'])) +		{ +			return; +		} + +		$this->navigation_data['finished'][] = $nav_path; +	} + +	/** +	 * Marks stage as active in the navigation bar +	 * +	 * @param array	$nav_path	Array to the navigation elem +	 */ +	public function set_active_navigation_stage($nav_path) +	{ +		$this->navigation_data['active'] = $nav_path; +	} + +	/** +	 * Returns navigation data +	 * +	 * @return array +	 */ +	public function get_navigation_data() +	{ +		return $this->navigation_data; +	} + +	/** +	 * Removes install config file +	 */ +	public function clean_up_config_file() +	{ +		$this->do_clean_up = true; +		@unlink($this->install_config_file); +	} + +	/** +	 * Filling up system_data array +	 */ +	protected function setup_system_data() +	{ +		// Query maximum runtime from php.ini +		$execution_time = $this->php_ini->getNumeric('max_execution_time'); +		$execution_time = min(15, $execution_time / 2); +		$this->system_data['max_execution_time'] = $execution_time; + +		// Set start time +		$this->system_data['start_time'] = microtime(true); + +		// Get memory limit +		$this->system_data['memory_limit'] = $this->php_ini->getBytes('memory_limit'); +	} +} diff --git a/phpBB/phpbb/install/helper/container_factory.php b/phpBB/phpbb/install/helper/container_factory.php new file mode 100644 index 0000000000..9e372fecde --- /dev/null +++ b/phpBB/phpbb/install/helper/container_factory.php @@ -0,0 +1,191 @@ +<?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\install\helper; + +use phpbb\install\exception\cannot_build_container_exception; +use phpbb\language\language; +use phpbb\request\request; + +class container_factory +{ +	/** +	 * @var language +	 */ +	protected $language; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * @var string +	 */ +	protected $php_ext; + +	/** +	 * @var \phpbb\request\request +	 */ +	protected $request; + +	/** +	 * @var update_helper +	 */ +	protected $update_helper; + +	/** +	 * The full phpBB container +	 * +	 * @var \Symfony\Component\DependencyInjection\ContainerInterface +	 */ +	protected $container; + +	/** +	 * Constructor +	 * +	 * @param language 		$language			Language service +	 * @param request		$request			Request interface +	 * @param update_helper	$update_helper		Update helper +	 * @param string		$phpbb_root_path	Path to phpBB's root +	 * @param string		$php_ext			Extension of PHP files +	 */ +	public function __construct(language $language, request $request, update_helper $update_helper, $phpbb_root_path, $php_ext) +	{ +		$this->language			= $language; +		$this->request			= $request; +		$this->update_helper	= $update_helper; +		$this->phpbb_root_path	= $phpbb_root_path; +		$this->php_ext			= $php_ext; +		$this->container		= null; +	} + +	/** +	 * Container getter +	 * +	 * @param null|string	$service_name	Name of the service to return +	 * +	 * @return \Symfony\Component\DependencyInjection\ContainerInterface|Object	phpBB's dependency injection container +	 * 																			or the service specified in $service_name +	 * +	 * @throws \phpbb\install\exception\cannot_build_container_exception							When container cannot be built +	 * @throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException			If the service is not defined +	 * @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException	When a circular reference is detected +	 * @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException			When the service is not defined +	 */ +	public function get($service_name = null) +	{ +		// Check if container was built, if not try to build it +		if ($this->container === null) +		{ +			$this->build_container(); +		} + +		return ($service_name === null) ? $this->container : $this->container->get($service_name); +	} + +	/** +	 * Returns the specified parameter from the container +	 * +	 * @param string	$param_name +	 * +	 * @return mixed +	 * +	 * @throws \phpbb\install\exception\cannot_build_container_exception	When container cannot be built +	 */ +	public function get_parameter($param_name) +	{ +		// Check if container was built, if not try to build it +		if ($this->container === null) +		{ +			$this->build_container(); +		} + +		return $this->container->getParameter($param_name); +	} + +	/** +	 * Build dependency injection container +	 * +	 * @throws \phpbb\install\exception\cannot_build_container_exception	When container cannot be built +	 */ +	protected function build_container() +	{ +		// If the container has been already built just return. +		// Although this should never happen +		if ($this->container instanceof \Symfony\Component\DependencyInjection\ContainerInterface) +		{ +			return; +		} + +		// Check whether container can be built +		// We need config.php for that so let's check if it has been set up yet +		if (!filesize($this->phpbb_root_path . 'config.' . $this->php_ext)) +		{ +			throw new cannot_build_container_exception(); +		} + +		$phpbb_config_php_file = new \phpbb\config_php_file($this->phpbb_root_path, $this->php_ext); +		$phpbb_container_builder = new \phpbb\di\container_builder($this->phpbb_root_path, $this->php_ext); + +		// For BC with functions that we need during install +		global $phpbb_container, $table_prefix; + +		$disable_super_globals = $this->request->super_globals_disabled(); + +		// This is needed because container_builder::get_env_parameters() uses $_SERVER +		if ($disable_super_globals) +		{ +			$this->request->enable_super_globals(); +		} + +		$other_config_path = $this->phpbb_root_path . 'install/update/new/config'; +		$config_path = (is_dir($other_config_path)) ? $other_config_path : $this->phpbb_root_path . 'config'; + +		$this->container = $phpbb_container_builder +			->with_environment('production') +			->with_config($phpbb_config_php_file) +			->with_config_path($config_path) +			->without_compiled_container() +			->get_container(); + +		// Setting request is required for the compatibility globals as those are generated from +		// this container +		if (!$this->container->isFrozen()) +		{ +			$this->container->register('request')->setSynthetic(true); +			$this->container->register('language')->setSynthetic(true); +		} + +		$this->container->set('request', $this->request); +		$this->container->set('language', $this->language); + +		$this->container->compile(); + +		$phpbb_container = $this->container; +		$table_prefix = $phpbb_config_php_file->get('table_prefix'); + +		// Restore super globals to previous state +		if ($disable_super_globals) +		{ +			$this->request->disable_super_globals(); +		} + +		// Get compatibilty globals and constants +		$this->update_helper->include_file('includes/compatibility_globals.' . $this->php_ext); + +		register_compatibility_globals(); + +		$this->update_helper->include_file('includes/constants.' . $this->php_ext); +	} +} diff --git a/phpBB/phpbb/install/helper/database.php b/phpBB/phpbb/install/helper/database.php new file mode 100644 index 0000000000..b8422fc1ed --- /dev/null +++ b/phpBB/phpbb/install/helper/database.php @@ -0,0 +1,456 @@ +<?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\install\helper; + +use phpbb\install\exception\invalid_dbms_exception; + +/** + * Database related general functionality for installer + */ +class database +{ +	/** +	 * @var \phpbb\filesystem\filesystem_interface +	 */ +	protected $filesystem; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * @var array +	 */ +	protected $supported_dbms = array( +		// Note: php 5.5 alpha 2 deprecated mysql. +		// Keep mysqli before mysql in this list. +		'mysqli'	=> array( +			'LABEL'			=> 'MySQL with MySQLi Extension', +			'SCHEMA'		=> 'mysql_41', +			'MODULE'		=> 'mysqli', +			'DELIM'			=> ';', +			'DRIVER'		=> 'phpbb\db\driver\mysqli', +			'AVAILABLE'		=> true, +			'2.0.x'			=> true, +		), +		'mysql'		=> array( +			'LABEL'			=> 'MySQL', +			'SCHEMA'		=> 'mysql', +			'MODULE'		=> 'mysql', +			'DELIM'			=> ';', +			'DRIVER'		=> 'phpbb\db\driver\mysql', +			'AVAILABLE'		=> true, +			'2.0.x'			=> true, +		), +		'mssql'		=> array( +			'LABEL'			=> 'MS SQL Server 2000+', +			'SCHEMA'		=> 'mssql', +			'MODULE'		=> 'mssql', +			'DELIM'			=> ';', +			'DRIVER'		=> 'phpbb\db\driver\mssql', +			'AVAILABLE'		=> true, +			'2.0.x'			=> true, +		), +		'mssql_odbc'=>	array( +			'LABEL'			=> 'MS SQL Server [ ODBC ]', +			'SCHEMA'		=> 'mssql', +			'MODULE'		=> 'odbc', +			'DELIM'			=> ';', +			'DRIVER'		=> 'phpbb\db\driver\mssql_odbc', +			'AVAILABLE'		=> true, +			'2.0.x'			=> true, +		), +		'mssqlnative'		=> array( +			'LABEL'			=> 'MS SQL Server 2005+ [ Native ]', +			'SCHEMA'		=> 'mssql', +			'MODULE'		=> 'sqlsrv', +			'DELIM'			=> ';', +			'DRIVER'		=> 'phpbb\db\driver\mssqlnative', +			'AVAILABLE'		=> true, +			'2.0.x'			=> false, +		), +		'oracle'	=>	array( +			'LABEL'			=> 'Oracle', +			'SCHEMA'		=> 'oracle', +			'MODULE'		=> 'oci8', +			'DELIM'			=> '/', +			'DRIVER'		=> 'phpbb\db\driver\oracle', +			'AVAILABLE'		=> true, +			'2.0.x'			=> false, +		), +		'postgres' => array( +			'LABEL'			=> 'PostgreSQL 8.3+', +			'SCHEMA'		=> 'postgres', +			'MODULE'		=> 'pgsql', +			'DELIM'			=> ';', +			'DRIVER'		=> 'phpbb\db\driver\postgres', +			'AVAILABLE'		=> true, +			'2.0.x'			=> true, +		), +		'sqlite'		=> array( +			'LABEL'			=> 'SQLite', +			'SCHEMA'		=> 'sqlite', +			'MODULE'		=> 'sqlite', +			'DELIM'			=> ';', +			'DRIVER'		=> 'phpbb\db\driver\sqlite', +			'AVAILABLE'		=> true, +			'2.0.x'			=> false, +		), +		'sqlite3'		=> array( +			'LABEL'			=> 'SQLite3', +			'SCHEMA'		=> 'sqlite', +			'MODULE'		=> 'sqlite3', +			'DELIM'			=> ';', +			'DRIVER'		=> 'phpbb\db\driver\sqlite3', +			'AVAILABLE'		=> true, +			'2.0.x'			=> false, +		), +	); + +	/** +	 * Constructor +	 * +	 * @param \phpbb\filesystem\filesystem_interface	$filesystem			Filesystem interface +	 * @param string									$phpbb_root_path	Path to phpBB's root +	 */ +	public function __construct(\phpbb\filesystem\filesystem_interface $filesystem, $phpbb_root_path) +	{ +		$this->filesystem		= $filesystem; +		$this->phpbb_root_path	= $phpbb_root_path; +	} + +	/** +	 * Returns an array of available DBMS supported by phpBB +	 * +	 * If a DBMS is specified it will only return data for that DBMS +	 * and will load its extension if necessary. +	 * +	 * @param	mixed	$dbms				name of the DBMS that's info is required or false for all DBMS info +	 * @param	bool	$return_unavailable	set it to true if you expect unavailable but supported DBMS +	 * 										returned as well +	 * @param	bool	$only_20x_options	set it to true if you only want to recover 2.0.x options +	 * +	 * @return	array	Array of available and supported DBMS +	 */ +	public function get_available_dbms($dbms = false, $return_unavailable = false, $only_20x_options = false) +	{ +		$available_dbms = $this->supported_dbms; + +		if ($dbms) +		{ +			if (isset($this->supported_dbms[$dbms])) +			{ +				$available_dbms = array($dbms => $this->supported_dbms[$dbms]); +			} +			else +			{ +				return array(); +			} +		} + +		$any_dbms_available = false; +		foreach ($available_dbms as $db_name => $db_array) +		{ +			if ($only_20x_options && !$db_array['2.0.x']) +			{ +				if ($return_unavailable) +				{ +					$available_dbms[$db_name]['AVAILABLE'] = false; +				} +				else +				{ +					unset($available_dbms[$db_name]); +				} + +				continue; +			} + +			$dll = $db_array['MODULE']; +			if (!@extension_loaded($dll)) +			{ +				if ($return_unavailable) +				{ +					$available_dbms[$db_name]['AVAILABLE'] = false; +				} +				else +				{ +					unset($available_dbms[$db_name]); +				} + +				continue; +			} + +			$any_dbms_available = true; +		} + +		if ($return_unavailable) +		{ +			$available_dbms['ANY_DB_SUPPORT'] = $any_dbms_available; +		} + +		return $available_dbms; +	} + +	/** +	 * Removes "/* style" as well as "# style" comments from $input. +	 * +	 * @param string $sql_query	Input string +	 * +	 * @return string Input string with comments removed +	 */ +	public function remove_comments($sql_query) +	{ +		// Remove /* */ comments (http://ostermiller.org/findcomment.html) +		$sql_query = preg_replace('#/\*(.|[\r\n])*?\*/#', "\n", $sql_query); + +		// Remove # style comments +		$sql_query = preg_replace('/\n{2,}/', "\n", preg_replace('/^#.*$/m', "\n", $sql_query)); + +		return $sql_query; +	} + +	/** +	 * split_sql_file() will split an uploaded sql file into single sql statements. +	 * +	 * Note: expects trim() to have already been run on $sql. +	 * +	 * @param	string	$sql		SQL statements +	 * @param	string	$delimiter	Delimiter between sql statements +	 * +	 * @return array Array of sql statements +	 */ +	public function split_sql_file($sql, $delimiter) +	{ +		$sql = str_replace("\r" , '', $sql); +		$data = preg_split('/' . preg_quote($delimiter, '/') . '$/m', $sql); + +		$data = array_map('trim', $data); + +		// The empty case +		$end_data = end($data); + +		if (empty($end_data)) +		{ +			unset($data[key($data)]); +		} + +		return $data; +	} + +	/** +	 * Validates table prefix +	 * +	 * @param string	$dbms			The selected dbms +	 * @param string	$table_prefix	The table prefix to validate +	 * +	 * @return bool|array	true if table prefix is valid, array of errors otherwise +	 * +	 * @throws \phpbb\install\exception\invalid_dbms_exception When $dbms is not a valid +	 */ +	public function validate_table_prefix($dbms, $table_prefix) +	{ +		$errors = array(); + +		if (!preg_match('#^[a-zA-Z][a-zA-Z0-9_]*$#', $table_prefix)) +		{ +			$errors[] = array( +				'title' => 'INST_ERR_DB_INVALID_PREFIX', +			); +		} + +		// Do dbms specific checks +		$dbms_info = $this->get_available_dbms($dbms); +		switch ($dbms_info[$dbms]['SCHEMA']) +		{ +			case 'mysql': +			case 'mysql_41': +				$prefix_length = 36; +			break; +			case 'mssql': +				$prefix_length = 90; +			break; +			case 'oracle': +				$prefix_length = 6; +			break; +			case 'postgres': +				$prefix_length = 36; +			break; +			case 'sqlite': +				$prefix_length = 200; +			break; +			default: +				throw new invalid_dbms_exception(); +			break; +		} + +		// Check the prefix length to ensure that index names are not too long +		if (strlen($table_prefix) > $prefix_length) +		{ +			$errors[] = array( +				'title' => array('INST_ERR_PREFIX_TOO_LONG', $prefix_length), +			); +		} + +		return (empty($errors)) ? true : $errors; +	} + +	/** +	 * Check if the user provided database parameters are correct +	 * +	 * This function checks the database connection data and also checks for +	 * any other problems that could cause an error during the installation +	 * such as if there is any database table names conflicting. +	 * +	 * Note: The function assumes that $table_prefix has been already validated +	 * with validate_table_prefix(). +	 * +	 * @param string	$dbms			Selected database type +	 * @param string	$dbhost			Database host address +	 * @param int		$dbport			Database port number +	 * @param string	$dbuser			Database username +	 * @param string	$dbpass			Database password +	 * @param string	$dbname			Database name +	 * @param string	$table_prefix	Database table prefix +	 * +	 * @return array|bool	Returns true if test is successful, array of errors otherwise +	 */ +	public function check_database_connection($dbms, $dbhost, $dbport, $dbuser, $dbpass, $dbname, $table_prefix) +	{ +		$dbms_info = $this->get_available_dbms($dbms); +		$dbms_info = $dbms_info[$dbms]; +		$errors = array(); + +		// Instantiate it and set return on error true +		/** @var \phpbb\db\driver\driver_interface $db */ +		$db = new $dbms_info['DRIVER']; +		$db->sql_return_on_error(true); + +		// Check that we actually have a database name before going any further +		if (!in_array($dbms_info['SCHEMA'], array('sqlite', 'oracle'), true) && $dbname === '') +		{ +			$errors[] = array( +				'title' => 'INST_ERR_DB_NO_NAME', +			); +		} + +		// Make sure we don't have a daft user who thinks having the SQLite database in the forum directory is a good idea +		if ($dbms_info['SCHEMA'] === 'sqlite' +			&& stripos($this->filesystem->realpath($dbhost), $this->filesystem->realpath($this->phpbb_root_path) === 0)) +		{ +			$errors[] = array( +				'title' =>'INST_ERR_DB_FORUM_PATH', +			); +		} + +		// Try to connect to db +		if (is_array($db->sql_connect($dbhost, $dbuser, $dbpass, $dbname, $dbport, false, true))) +		{ +			$db_error = $db->sql_error(); +			$errors[] = array( +				'title' => 'INST_ERR_DB_CONNECT', +				'description' => ($db_error['message']) ? utf8_convert_message($db_error['message']) : 'INST_ERR_DB_NO_ERROR', +			); +		} +		else +		{ +			// Check if there is any table name collisions +			$temp_prefix = strtolower($table_prefix); +			$table_ary = array( +				$temp_prefix . 'attachments', +				$temp_prefix . 'config', +				$temp_prefix . 'sessions', +				$temp_prefix . 'topics', +				$temp_prefix . 'users', +			); + +			$db_tools_factory = new \phpbb\db\tools\factory(); +			$db_tools = $db_tools_factory->get($db); +			$tables = $db_tools->sql_list_tables(); +			$tables = array_map('strtolower', $tables); +			$table_intersect = array_intersect($tables, $table_ary); + +			if (sizeof($table_intersect)) +			{ +				$errors[] = array( +					'title' => 'INST_ERR_PREFIX', +				); +			} + +			// Check if database version is supported +			switch ($dbms) +			{ +				case 'mysqli': +					if (version_compare($db->sql_server_info(true), '4.1.3', '<')) +					{ +						$errors[] = array( +							'title' => 'INST_ERR_DB_NO_MYSQLI', +						); +					} +				break; +				case 'sqlite': +					if (version_compare($db->sql_server_info(true), '2.8.2', '<')) +					{ +						$errors[] = array( +							'title' => 'INST_ERR_DB_NO_SQLITE', +						); +					} +				break; +				case 'sqlite3': +					if (version_compare($db->sql_server_info(true), '3.6.15', '<')) +					{ +						$errors[] = array( +							'title' => 'INST_ERR_DB_NO_SQLITE3', +						); +					} +				break; +				case 'oracle': +					$sql = "SELECT * +						FROM NLS_DATABASE_PARAMETERS +						WHERE PARAMETER = 'NLS_RDBMS_VERSION' +							OR PARAMETER = 'NLS_CHARACTERSET'"; +					$result = $db->sql_query($sql); + +					while ($row = $db->sql_fetchrow($result)) +					{ +						$stats[$row['parameter']] = $row['value']; +					} +					$db->sql_freeresult($result); + +					if (version_compare($stats['NLS_RDBMS_VERSION'], '9.2', '<') && $stats['NLS_CHARACTERSET'] !== 'UTF8') +					{ +						$errors[] = array( +							'title' => 'INST_ERR_DB_NO_ORACLE', +						); +					} +				break; +				case 'postgres': +					$sql = "SHOW server_encoding;"; +					$result = $db->sql_query($sql); +					$row = $db->sql_fetchrow($result); +					$db->sql_freeresult($result); + +					if ($row['server_encoding'] !== 'UNICODE' && $row['server_encoding'] !== 'UTF8') +					{ +						$errors[] = array( +							'title' => 'INST_ERR_DB_NO_POSTGRES', +						); +					} +				break; +			} +		} + +		return (empty($errors)) ? true : $errors; +	} +} diff --git a/phpBB/phpbb/install/helper/file_updater/compression_file_updater.php b/phpBB/phpbb/install/helper/file_updater/compression_file_updater.php new file mode 100644 index 0000000000..ede992fb6e --- /dev/null +++ b/phpBB/phpbb/install/helper/file_updater/compression_file_updater.php @@ -0,0 +1,133 @@ +<?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\install\helper\file_updater; + +use phpbb\install\helper\update_helper; + +/** + * File updater for generating archive with updated files + */ +class compression_file_updater implements file_updater_interface +{ +	/** +	 * @var \compress +	 */ +	protected $compress; + +	/** +	 * @var update_helper +	 */ +	protected $update_helper; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * @var string +	 */ +	protected $php_ext; + +	/** +	 * Constructor +	 * +	 * @param update_helper	$update_helper +	 * @param string		$phpbb_root_path +	 * @param string		$php_ext +	 */ +	public function __construct(update_helper $update_helper, $phpbb_root_path, $php_ext) +	{ +		$this->compress			= null; +		$this->update_helper	= $update_helper; +		$this->phpbb_root_path	= $phpbb_root_path; +		$this->php_ext			= $php_ext; +	} + +	/** +	 * Set the compression method +	 * +	 * @param string	$method	Compression method's file extension +	 * +	 * @return string	Archive's filename +	 */ +	public function init($method) +	{ +		$this->update_helper->include_file('includes/functions_compress.' . $this->php_ext); + +		$archive_filename = 'update_archive_' . time() . '_' . uniqid(); +		$path = $this->phpbb_root_path . 'store/' . $archive_filename . '' . $method; + +		if ($method === '.zip') +		{ +			$this->compress = new \compress_zip('w', $path); +		} +		else +		{ +			$this->compress = new \compress_tar('w', $path, $method); +		} + +		return $path; +	} + +	/** +	 * Close archive writing process +	 */ +	public function close() +	{ +		$this->compress->close(); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function delete_file($path_to_file) +	{ +		// We do absolutely nothing here, as this function is called when a file should be +		// removed from the filesystem, but since this is an archive generator, it clearly +		// cannot do that. +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function create_new_file($path_to_file_to_create, $source, $create_from_content = false) +	{ +		if ($create_from_content) +		{ +			$this->compress->add_data($source, $path_to_file_to_create); +		} +		else +		{ +			$this->compress->add_custom_file($source, $path_to_file_to_create); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function update_file($path_to_file_to_update, $source, $create_from_content = false) +	{ +		// Both functions are identical here +		$this->create_new_file($path_to_file_to_update, $source, $create_from_content); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_method_name() +	{ +		return 'compression'; +	} +} diff --git a/phpBB/phpbb/install/helper/file_updater/factory.php b/phpBB/phpbb/install/helper/file_updater/factory.php new file mode 100644 index 0000000000..d3a2f22782 --- /dev/null +++ b/phpBB/phpbb/install/helper/file_updater/factory.php @@ -0,0 +1,69 @@ +<?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\install\helper\file_updater; + +use phpbb\di\service_collection; +use phpbb\install\exception\file_updater_failure_exception; + +/** + * File updater factory + */ +class factory +{ +	/** +	 * @var array +	 */ +	protected $file_updaters; + +	/** +	 * Constructor +	 * +	 * @param service_collection $collection	File updater service collection +	 */ +	public function __construct(service_collection $collection) +	{ +		foreach ($collection as $service) +		{ +			$this->register($service); +		} +	} + +	/** +	 * Register updater object +	 * +	 * @param file_updater_interface $updater	Updater object +	 */ +	public function register(file_updater_interface $updater) +	{ +		$name = $updater->get_method_name(); +		$this->file_updaters[$name] = $updater; +	} + +	/** +	 * Returns file updater object +	 * +	 * @param string $name	Name of the updater method +	 * +	 * @throws file_updater_failure_exception	When the specified file updater does not exist +	 */ +	public function get($name) +	{ +		if (!isset($this->file_updaters[$name])) +		{ +			throw new file_updater_failure_exception(); +		} + +		return $this->file_updaters[$name]; +	} +} diff --git a/phpBB/phpbb/install/helper/file_updater/file_updater.php b/phpBB/phpbb/install/helper/file_updater/file_updater.php new file mode 100644 index 0000000000..cc0f5c6b5f --- /dev/null +++ b/phpBB/phpbb/install/helper/file_updater/file_updater.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\install\helper\file_updater; + +use phpbb\filesystem\exception\filesystem_exception; +use phpbb\filesystem\filesystem; +use phpbb\install\exception\file_updater_failure_exception; + +/** + * File updater for direct filesystem access + */ +class file_updater implements file_updater_interface +{ +	/** +	 * @var filesystem +	 */ +	protected $filesystem; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * Constructor +	 * +	 * @param filesystem	$filesystem +	 * @param string		$phpbb_root_path +	 */ +	public function __construct(filesystem $filesystem, $phpbb_root_path) +	{ +		$this->filesystem		= $filesystem; +		$this->phpbb_root_path	= $phpbb_root_path; +	} + +	/** +	 * {@inheritdoc} +	 * +	 * @throws file_updater_failure_exception	When the file is not writable +	 * @throws filesystem_exception				When the filesystem class fails +	 */ +	public function delete_file($path_to_file) +	{ +		$this->filesystem->remove($this->phpbb_root_path . $path_to_file); +	} + +	/** +	 * {@inheritdoc} +	 * +	 * @throws file_updater_failure_exception	When the file is not writable +	 * @throws filesystem_exception				When the filesystem class fails +	 */ +	public function create_new_file($path_to_file_to_create, $source, $create_from_content = false) +	{ +		$path_to_file_to_create = $this->phpbb_root_path . $path_to_file_to_create; + +		$dir = dirname($path_to_file_to_create); +		if (!$this->filesystem->exists($dir)) +		{ +			$this->make_dir($dir); +		} + +		$original_dir_perms = false; + +		if (!$this->filesystem->is_writable($dir)) +		{ +			// Extract last 9 bits we actually need +			$original_dir_perms = @fileperms($dir) & 511; +			$this->filesystem->phpbb_chmod($dir, filesystem::CHMOD_ALL); +		} + +		if (!$create_from_content) +		{ +			try +			{ +				$this->filesystem->copy($source, $path_to_file_to_create); +			} +			catch (filesystem_exception $e) +			{ +				$this->write_file($path_to_file_to_create, $source, $create_from_content); +			} +		} +		else +		{ +			$this->write_file($path_to_file_to_create, $source, $create_from_content); +		} + +		if ($original_dir_perms !== false) +		{ +			$this->filesystem->phpbb_chmod($dir, $original_dir_perms); +		} +	} + +	/** +	 * {@inheritdoc} +	 * +	 * @throws file_updater_failure_exception	When the file is not writable +	 * @throws filesystem_exception				When the filesystem class fails +	 */ +	public function update_file($path_to_file_to_update, $source, $create_from_content = false) +	{ +		$path_to_file_to_update = $this->phpbb_root_path . $path_to_file_to_update; +		$original_file_perms = false; + +		// Maybe necessary for binary files +		$dir = dirname($path_to_file_to_update); +		if (!$this->filesystem->exists($dir)) +		{ +			$this->make_dir($dir); +		} + +		if (!$this->filesystem->is_writable($path_to_file_to_update)) +		{ +			// Extract last 9 bits we actually need +			$original_file_perms = @fileperms($path_to_file_to_update) & 511; +			$this->filesystem->phpbb_chmod($path_to_file_to_update, filesystem::CHMOD_WRITE); +		} + +		if (!$create_from_content) +		{ +			try +			{ +				$this->filesystem->copy($source, $path_to_file_to_update, true); +			} +			catch (filesystem_exception $e) +			{ +				$this->write_file($path_to_file_to_update, $source, $create_from_content); +			} +		} +		else +		{ +			$this->write_file($path_to_file_to_update, $source, $create_from_content); +		} + +		if ($original_file_perms !== false) +		{ +			$this->filesystem->phpbb_chmod($path_to_file_to_update, $original_file_perms); +		} +	} + +	/** +	 * Creates directory structure +	 * +	 * @param string	$path	Path to the directory where the file should be placed (and non-existent) +	 */ +	private function make_dir($path) +	{ +		if (is_dir($path)) +		{ +			return; +		} + +		$path = str_replace(DIRECTORY_SEPARATOR, '/', $path); +		$this->filesystem->mkdir($path, 493); // 493 === 0755 +	} + +	/** +	 * Fallback function for file writing +	 * +	 * @param string		$path_to_file			Path to the file's location +	 * @param string		$source					Path to file to copy or string with the new file's content +	 * @param bool|false	$create_from_content	Whether or not to use $source as the content, false by default +	 * +	 * @throws file_updater_failure_exception	When the file is not writable +	 */ +	private function write_file($path_to_file, $source, $create_from_content = false) +	{ +		if (!$create_from_content) +		{ +			$source = @file_get_contents($source); +		} + +		$file_pointer = @fopen($path_to_file, 'w'); + +		if (!is_resource($file_pointer)) +		{ +			throw new file_updater_failure_exception(); +		} + +		@fwrite($file_pointer, $source); +		@fclose($file_pointer); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_method_name() +	{ +		return 'direct_file'; +	} +} diff --git a/phpBB/phpbb/install/helper/file_updater/file_updater_interface.php b/phpBB/phpbb/install/helper/file_updater/file_updater_interface.php new file mode 100644 index 0000000000..b13d7c9fe1 --- /dev/null +++ b/phpBB/phpbb/install/helper/file_updater/file_updater_interface.php @@ -0,0 +1,49 @@ +<?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\install\helper\file_updater; + +interface file_updater_interface +{ +	/** +	 * Deletes a file +	 * +	 * @param string	$path_to_file	Path to the file to delete +	 */ +	public function delete_file($path_to_file); + +	/** +	 * Creates a new file +	 * +	 * @param string	$path_to_file_to_create	Path to the new file's location +	 * @param string	$source					Path to file to copy or string with the new file's content +	 * @param bool		$create_from_content	Whether or not to use $source as the content, false by default +	 */ +	public function create_new_file($path_to_file_to_create, $source, $create_from_content = false); + +	/** +	 * Update file +	 * +	 * @param string	$path_to_file_to_update	Path to the file's location +	 * @param string	$source					Path to file to copy or string with the new file's content +	 * @param bool		$create_from_content	Whether or not to use $source as the content, false by default +	 */ +	public function update_file($path_to_file_to_update, $source, $create_from_content = false); + +	/** +	 * Returns the name of the file updater method +	 * +	 * @return string +	 */ +	public function get_method_name(); +} diff --git a/phpBB/phpbb/install/helper/file_updater/ftp_file_updater.php b/phpBB/phpbb/install/helper/file_updater/ftp_file_updater.php new file mode 100644 index 0000000000..258a035768 --- /dev/null +++ b/phpBB/phpbb/install/helper/file_updater/ftp_file_updater.php @@ -0,0 +1,136 @@ +<?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\install\helper\file_updater; + +use phpbb\install\helper\update_helper; + +/** + * File updater for FTP updates + */ +class ftp_file_updater implements file_updater_interface +{ +	/** +	 * @var \transfer +	 */ +	protected $transfer; + +	/** +	 * @var update_helper +	 */ +	protected $update_helper; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * @var string +	 */ +	protected $php_ext; + +	/** +	 * Constructor +	 * +	 * @param update_helper	$update_helper +	 * @param string		$phpbb_root_path +	 * @param string		$php_ext +	 */ +	public function __constructor(update_helper $update_helper, $phpbb_root_path, $php_ext) +	{ +		$this->transfer			= null; +		$this->update_helper	= $update_helper; +		$this->phpbb_root_path	= $phpbb_root_path; +		$this->php_ext			= $php_ext; +	} + +	/** +	 * Initialize FTP connection +	 * +	 * @param string	$method +	 * @param string	$host +	 * @param string	$user +	 * @param string	$pass +	 * @param string	$path +	 * @param int		$port +	 * @param int		$timeout +	 */ +	public function init($method, $host, $user, $pass, $path, $port, $timeout) +	{ +		$this->update_helper->include_file('includes/functions_transfer.' . $this->php_ext); +		$this->transfer = new $method($host, $user, $pass, $path, $port, $timeout); +		$this->transfer->open_session(); +	} + +	/** +	 * Close FTP session +	 */ +	public function close() +	{ +		$this->transfer->close_session(); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function delete_file($path_to_file) +	{ +		$this->transfer->delete_file($path_to_file); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function create_new_file($path_to_file_to_create, $source, $create_from_content = false) +	{ +		$dirname = dirname($path_to_file_to_create); + +		if ($dirname && !file_exists($this->phpbb_root_path . $dirname)) +		{ +			$this->transfer->make_dir($dirname); +		} + +		if ($create_from_content) +		{ +			$this->transfer->write_file($path_to_file_to_create, $source); +		} +		else +		{ +			$this->transfer->copy_file($path_to_file_to_create, $source); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function update_file($path_to_file_to_update, $source, $create_from_content = false) +	{ +		if ($create_from_content) +		{ +			$this->transfer->write_file($path_to_file_to_update, $source); +		} +		else +		{ +			$this->transfer->copy_file($path_to_file_to_update, $source); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_method_name() +	{ +		return 'ftp'; +	} +} diff --git a/phpBB/phpbb/install/helper/install_helper.php b/phpBB/phpbb/install/helper/install_helper.php new file mode 100644 index 0000000000..ffe36cd645 --- /dev/null +++ b/phpBB/phpbb/install/helper/install_helper.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\install\helper; + +/** + * General helper functionality for the installer + */ +class install_helper +{ +	/** +	 * @var string +	 */ +	protected $php_ext; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * 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; +	} + +	/** +	 * Check whether phpBB is installed. +	 * +	 * @return bool +	 */ +	public function is_phpbb_installed() +	{ +		$config_path = $this->phpbb_root_path . 'config.' . $this->php_ext; +		$install_lock_path = $this->phpbb_root_path . 'cache/install_lock'; + +		if (file_exists($config_path) && !file_exists($install_lock_path) && filesize($config_path)) +		{ +			return true; +		} + +		return false; +	} +} diff --git a/phpBB/phpbb/install/helper/iohandler/ajax_iohandler.php b/phpBB/phpbb/install/helper/iohandler/ajax_iohandler.php new file mode 100644 index 0000000000..c168d26425 --- /dev/null +++ b/phpBB/phpbb/install/helper/iohandler/ajax_iohandler.php @@ -0,0 +1,489 @@ +<?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\install\helper\iohandler; + +use phpbb\path_helper; +use phpbb\routing\router; + +/** + * Input-Output handler for the AJAX frontend + */ +class ajax_iohandler extends iohandler_base +{ +	/** +	 * @var path_helper +	 */ +	protected $path_helper; + +	/** +	 * @var \phpbb\request\request_interface +	 */ +	protected $request; + +	/** +	 * @var \phpbb\template\template +	 */ +	protected $template; + +	/** +	 * @var router +	 */ +	protected $router; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * @var string +	 */ +	protected $file_status; + +	/** +	 * @var string +	 */ +	protected $form; + +	/** +	 * @var bool +	 */ +	protected $request_client_refresh; + +	/** +	 * @var array +	 */ +	protected $nav_data; + +	/** +	 * @var array +	 */ +	protected $cookies; + +	/** +	 * @var array +	 */ +	protected $download; + +	/** +	 * @var array +	 */ +	protected $redirect_url; + +	/** +	 * @var resource +	 */ +	protected $file_lock_pointer; + +	/** +	 * Constructor +	 * +	 * @param path_helper						$path_helper +	 * @param \phpbb\request\request_interface	$request	HTTP request interface +	 * @param \phpbb\template\template			$template	Template engine +	 * @param router 							$router		Router +	 * @param string 							$root_path	Path to phpBB's root +	 */ +	public function __construct(path_helper $path_helper, \phpbb\request\request_interface $request, \phpbb\template\template $template, router $router, $root_path) +	{ +		$this->path_helper = $path_helper; +		$this->request	= $request; +		$this->router	= $router; +		$this->template	= $template; +		$this->form		= ''; +		$this->nav_data	= array(); +		$this->cookies	= array(); +		$this->download	= array(); +		$this->redirect_url = array(); +		$this->file_status = ''; +		$this->phpbb_root_path = $root_path; + +		parent::__construct(); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_input($name, $default, $multibyte = false) +	{ +		return $this->request->variable($name, $default, $multibyte); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_server_variable($name, $default = '') +	{ +		return $this->request->server($name, $default); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_header_variable($name, $default = '') +	{ +		return $this->request->header($name, $default); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function is_secure() +	{ +		return $this->request->is_secure(); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function add_user_form_group($title, $form) +	{ +		$this->form = $this->generate_form_render_data($title, $form); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function generate_form_render_data($title, $form) +	{ +		$this->template->assign_block_vars('options', array( +			'LEGEND'	=> $this->language->lang($title), +			'S_LEGEND'	=> true, +		)); + +		$not_button_form = false; + +		foreach ($form as $input_name => $input_options) +		{ +			if (!isset($input_options['type'])) +			{ +				continue; +			} + +			$tpl_ary = array(); +			$not_button_form = ($input_options['type'] !== 'submit' || $not_button_form); + +			$tpl_ary['TYPE'] = $input_options['type']; +			$tpl_ary['TITLE'] = $this->language->lang($input_options['label']); +			$tpl_ary['KEY'] = $input_name; +			$tpl_ary['S_EXPLAIN'] = false; + +			if (isset($input_options['default'])) +			{ +				$default = $input_options['default']; +				$default = preg_replace_callback('#\{L_([A-Z0-9\-_]*)\}#s', array($this, 'lang_replace_callback'), $default); +				$tpl_ary['DEFAULT'] = $default; +			} + +			if (isset($input_options['description'])) +			{ +				$tpl_ary['TITLE_EXPLAIN'] = $this->language->lang($input_options['description']); +				$tpl_ary['S_EXPLAIN'] = true; +			} + +			if (in_array($input_options['type'], array('select', 'radio'), true)) +			{ +				for ($i = 0, $total = sizeof($input_options['options']); $i < $total; $i++) +				{ +					if (isset($input_options['options'][$i]['label'])) +					{ +						$input_options['options'][$i]['label'] = $this->language->lang($input_options['options'][$i]['label']); +					} +				} + +				$tpl_ary['OPTIONS'] = $input_options['options']; +			} + +			$block_name = ($input_options['type'] === 'submit') ? 'submit_buttons' : 'options'; +			$this->template->assign_block_vars($block_name, $tpl_ary); +		} + +		$this->template->assign_var('S_NOT_ONLY_BUTTON_FORM', $not_button_form); + +		$this->template->set_filenames(array( +			'form_install' => 'installer_form.html', +		)); + +		return $this->template->assign_display('form_install'); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function send_response($no_more_output = false) +	{ +		$json_data_array = $this->prepare_json_array($no_more_output); + +		if (empty($json_data_array)) +		{ +			return; +		} + +		$json_data = json_encode($json_data_array); + +		// Try to push content to the browser +		print(str_pad(' ', 4096) . "\n"); +		print($json_data . "\n\n"); +		flush(); +	} + +	/** +	 * Prepares iohandler's data to be sent out to the client. +	 * +	 * @param bool	$no_more_output	Whether or not there will be more output in this response +	 * +	 * @return array +	 */ +	protected function prepare_json_array($no_more_output = false) +	{ +		$json_array = array(); + +		if (!empty($this->errors)) +		{ +			$json_array['errors'] = $this->errors; +			$this->errors = array(); +		} + +		if (!empty($this->warnings)) +		{ +			$json_array['warnings'] = $this->warnings; +			$this->warnings = array(); +		} + +		if (!empty($this->logs)) +		{ +			$json_array['logs'] = $this->logs; +			$this->logs = array(); +		} + +		if (!empty($this->success)) +		{ +			$json_array['success'] = $this->success; +			$this->success = array(); +		} + +		if (!empty($this->download)) +		{ +			$json_array['download'] = $this->download; +			$this->download = array(); +		} + +		if (!empty($this->form)) +		{ +			$json_array['form'] = $this->form; +			$this->form = ''; +		} + +		if (!empty($this->file_status)) +		{ +			$json_array['file_status'] = $this->file_status; +			$this->file_status = ''; +		} + +		// If current task name is set, we push progress message to the client side +		if (!empty($this->current_task_name)) +		{ +			$json_array['progress'] = array( +				'task_name'		=> $this->current_task_name, +				'task_num'		=> $this->current_task_progress, +				'task_count'	=> $this->task_progress_count, +			); + +			if ($this->restart_progress_bar) +			{ +				$json_array['progress']['restart'] = 1; +				$this->restart_progress_bar = false; +			} +		} + +		if (!empty($this->nav_data)) +		{ +			$json_array['nav'] = $this->nav_data; +			$this->nav_data = array(); +		} + +		if ($this->request_client_refresh) +		{ +			$json_array['refresh'] = true; +			$this->request_client_refresh = false; +		} + +		if (!empty($this->cookies)) +		{ +			$json_array['cookies'] = $this->cookies; +			$this->cookies = array(); +		} + +		if (!empty($this->redirect_url)) +		{ +			$json_array['redirect'] = $this->redirect_url; +			$this->redirect_url = array(); +		} + +		if ($no_more_output) +		{ +			$json_array['over'] = true; +		} + +		return $json_array; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function set_progress($task_lang_key, $task_number) +	{ +		parent::set_progress($task_lang_key, $task_number); +		$this->send_response(); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function request_refresh() +	{ +		$this->request_client_refresh = true; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function set_active_stage_menu($menu_path) +	{ +		$this->nav_data['active'] = $menu_path[sizeof($menu_path) - 1]; +		$this->send_response(); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function set_finished_stage_menu($menu_path) +	{ +		$this->nav_data['finished'][] = $menu_path[sizeof($menu_path) - 1]; +		$this->send_response(); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function set_cookie($cookie_name, $cookie_value) +	{ +		$this->cookies[] = array( +			'name' => $cookie_name, +			'value' => $cookie_value +		); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function add_download_link($route, $title, $msg = null) +	{ +		$link_properties = array( +			'href'	=> $this->router->generate($route), +			'title'	=> $this->language->lang($title), +			'download' => $this->language->lang('DOWNLOAD'), +		); + +		if ($msg !== null) +		{ +			$link_properties['msg'] = htmlspecialchars_decode($this->language->lang($msg)); +		} + +		$this->download[] = $link_properties; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function render_update_file_status($status_array) +	{ +		$this->template->assign_vars(array( +			'T_IMAGE_PATH'	=> $this->path_helper->get_web_root_path() . 'adm/images/', +		)); + +		foreach ($status_array as $block => $list) +		{ +			foreach ($list as $filename) +			{ +				$dirname = dirname($filename); + +				$this->template->assign_block_vars($block, array( +					'STATUS'			=> $block, +					'FILENAME'			=> $filename, +					'DIR_PART'			=> (!empty($dirname) && $dirname !== '.') ? dirname($filename) . '/' : false, +					'FILE_PART'			=> basename($filename), +				)); +			} +		} + +		$this->template->set_filenames(array( +			'file_status' => 'installer_update_file_status.html', +		)); + +		$this->file_status = $this->template->assign_display('file_status'); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function redirect($url, $use_ajax = false) +	{ +		$this->redirect_url = array('url' => $url, 'use_ajax' => $use_ajax); +		$this->send_response(true); +	} + +	/** +	 * Acquires a file lock +	 */ +	public function acquire_lock() +	{ +		$lock_file = $this->phpbb_root_path . 'store/io_lock.lock'; +		$this->file_lock_pointer = @fopen($lock_file, 'w+'); + +		if ($this->file_lock_pointer) +		{ +			flock($this->file_lock_pointer, LOCK_EX); +		} +	} + +	/** +	 * Release file lock +	 */ +	public function release_lock() +	{ +		if ($this->file_lock_pointer) +		{ +			fwrite($this->file_lock_pointer, 'ok'); +			flock($this->file_lock_pointer, LOCK_UN); +			fclose($this->file_lock_pointer); +		} +	} + +	/** +	 * Callback function for language replacing +	 * +	 * @param array	$matches +	 * @return string +	 */ +	public function lang_replace_callback($matches) +	{ +		if (!empty($matches[1])) +		{ +			return $this->language->lang($matches[1]); +		} + +		return ''; +	} +} diff --git a/phpBB/phpbb/install/helper/iohandler/cli_iohandler.php b/phpBB/phpbb/install/helper/iohandler/cli_iohandler.php new file mode 100644 index 0000000000..2a41cb10ba --- /dev/null +++ b/phpBB/phpbb/install/helper/iohandler/cli_iohandler.php @@ -0,0 +1,300 @@ +<?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\install\helper\iohandler; + +use phpbb\install\exception\installer_exception; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\OutputStyle; + +/** + * Input-Output handler for the CLI frontend + */ +class cli_iohandler extends iohandler_base +{ +	/** +	 * @var OutputInterface +	 */ +	protected $output; + +	/** +	 * @var OutputStyle +	 */ +	protected $io; + +	/** +	 * @var array +	 */ +	protected $input_values = array(); + +	/** +	 * @var \Symfony\Component\Console\Helper\ProgressBar +	 */ +	protected $progress_bar; + +	/** +	 * Set the style and output used to display feedback; +	 * +	 * @param OutputStyle 		$style +	 * @param OutputInterface	$output +	 */ +	public function set_style(OutputStyle $style, OutputInterface $output) +	{ +		$this->io = $style; +		$this->output = $output; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_input($name, $default, $multibyte = false) +	{ +		$result = $default; + +		if (isset($this->input_values[$name])) +		{ +			$result = $this->input_values[$name]; +		} + +		if ($multibyte) +		{ +			return utf8_normalize_nfc($result); +		} + +		return $result; +	} + +	public function set_input($name, $value) +	{ +		$this->input_values[$name] = $value; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_server_variable($name, $default = '') +	{ +		return $default; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_header_variable($name, $default = '') +	{ +		return $default; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function is_secure() +	{ +		return false; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function add_user_form_group($title, $form) +	{ +		throw new installer_exception('MISSING_DATA'); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function send_response($no_more_output = false) +	{ +	} + +	/** +	 * {@inheritdoc +	 */ +	public function add_error_message($error_title, $error_description = false) +	{ +		$this->io->newLine(); +		$message = $this->translate_message($error_title, $error_description); +		$message_string = $message['title'] . (!empty($message['description']) ? "\n" . $message['description'] : ''); + +		if (strpos($message_string, '<br />') !== false) +		{ +			$message_string = strip_tags(str_replace('<br />', "\n", $message_string)); +		} + +		$this->io->error($message_string); + +		if ($this->progress_bar !== null) +		{ +			$this->io->newLine(2); +			$this->progress_bar->display(); +		} +	} + +	/** +	 * {@inheritdoc +	 */ +	public function add_warning_message($warning_title, $warning_description = false) +	{ +		$this->io->newLine(); + +		$message = $this->translate_message($warning_title, $warning_description); +		$message_string = $message['title'] . (!empty($message['description']) ? "\n" . $message['description'] : ''); +		$this->io->warning($message_string); + +		if ($this->progress_bar !== null) +		{ +			$this->io->newLine(2); +			$this->progress_bar->display(); +		} +	} + +	/** +	 * {@inheritdoc +	 */ +	public function add_log_message($log_title, $log_description = false) +	{ +		if ($this->output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL) +		{ +			$message = $this->translate_message($log_title, $log_description); +			$this->output->writeln(sprintf('[%3d/%-3d] ---- %s', $this->current_task_progress, $this->task_progress_count, $message['title'])); +		} +	} + +	/** +	 * {@inheritdoc +	 */ +	public function add_success_message($error_title, $error_description = false) +	{ +		$this->io->newLine(); + +		$message = $this->translate_message($error_title, $error_description); +		$message_string = $message['title'] . (!empty($message['description']) ? "\n" . $message['description'] : ''); +		$this->io->success($message_string); + +		if ($this->progress_bar !== null) +		{ +			$this->io->newLine(2); +			$this->progress_bar->display(); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function set_task_count($task_count, $restart = false) +	{ +		parent::set_task_count($task_count, $restart); + +		if ($this->output->getVerbosity() === OutputInterface::VERBOSITY_NORMAL) +		{ +			$this->progress_bar = $this->io->createProgressBar($task_count); +			$this->progress_bar->setFormat( +				"    %current:3s%/%max:-3s% %bar%  %percent:3s%%\n" . +				"             %message%\n"); +			$this->progress_bar->setBarWidth(60); + +			if (!defined('PHP_WINDOWS_VERSION_BUILD')) +			{ +				$this->progress_bar->setEmptyBarCharacter('â–‘'); // light shade character \u2591 +				$this->progress_bar->setProgressCharacter(''); +				$this->progress_bar->setBarCharacter('â–“'); // dark shade character \u2593 +			} + +			$this->progress_bar->setMessage(''); +			$this->io->newLine(2); +			$this->progress_bar->start(); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function set_progress($task_lang_key, $task_number) +	{ +		parent::set_progress($task_lang_key, $task_number); + +		if ($this->progress_bar !== null) +		{ +			$this->progress_bar->setProgress($this->current_task_progress); +			$this->progress_bar->setMessage($this->current_task_name); +		} +		else +		{ +			$this->output->writeln(sprintf('[%3d/%-3d] %s', $this->current_task_progress, $this->task_progress_count, $this->current_task_name)); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function finish_progress($message_lang_key) +	{ +		parent::finish_progress($message_lang_key); + +		if ($this->progress_bar !== null) +		{ +			$this->progress_bar->finish(); +			$this->progress_bar = null; +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function request_refresh() +	{ +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function set_active_stage_menu($menu_path) +	{ +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function set_finished_stage_menu($menu_path) +	{ +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function set_cookie($cookie_name, $cookie_value) +	{ +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function add_download_link($route, $title, $msg = null) +	{ +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function render_update_file_status($status_array) +	{ +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function redirect($url, $use_ajax = false) +	{ +	} +} diff --git a/phpBB/phpbb/install/helper/iohandler/exception/iohandler_not_implemented_exception.php b/phpBB/phpbb/install/helper/iohandler/exception/iohandler_not_implemented_exception.php new file mode 100644 index 0000000000..f2ddeda6f7 --- /dev/null +++ b/phpBB/phpbb/install/helper/iohandler/exception/iohandler_not_implemented_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\install\helper\iohandler\exception; + +class iohandler_not_implemented_exception extends \Exception +{ + +} diff --git a/phpBB/phpbb/install/helper/iohandler/factory.php b/phpBB/phpbb/install/helper/iohandler/factory.php new file mode 100644 index 0000000000..1e8395760a --- /dev/null +++ b/phpBB/phpbb/install/helper/iohandler/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\install\helper\iohandler; + +use phpbb\install\helper\iohandler\exception\iohandler_not_implemented_exception; + +/** + * Input-output handler factory + */ +class factory +{ +	/** +	 * @var \Symfony\Component\DependencyInjection\ContainerInterface +	 */ +	protected $container; + +	/** +	 * @var string +	 */ +	protected $environment; + +	/** +	 * Constructor +	 * +	 * @param \Symfony\Component\DependencyInjection\ContainerInterface $container Dependency injection container +	 */ +	public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container) +	{ +		$this->container	= $container; +		$this->environment	= null; +	} + +	/** +	 * @param string	$environment	The name of the input-output handler to use +	 */ +	public function set_environment($environment) +	{ +		$this->environment = $environment; +	} + +	/** +	 * Factory getter for iohandler +	 * +	 * @return \phpbb\install\helper\iohandler\iohandler_interface +	 * +	 * @throws \phpbb\install\helper\iohandler\exception\iohandler_not_implemented_exception +	 * 		When the specified iohandler_interface does not exists +	 */ +	public function get() +	{ +		switch ($this->environment) +		{ +			case 'ajax': +				return $this->container->get('installer.helper.iohandler_ajax'); +			break; +			case 'nojs': +				// @todo replace this +				return $this->container->get('installer.helper.iohandler_ajax'); +			break; +			case 'cli': +				return $this->container->get('installer.helper.iohandler_cli'); +			break; +			default: +				throw new iohandler_not_implemented_exception(); +			break; +		} +	} +} diff --git a/phpBB/phpbb/install/helper/iohandler/iohandler_base.php b/phpBB/phpbb/install/helper/iohandler/iohandler_base.php new file mode 100644 index 0000000000..fed4bc101f --- /dev/null +++ b/phpBB/phpbb/install/helper/iohandler/iohandler_base.php @@ -0,0 +1,204 @@ +<?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\install\helper\iohandler; + +/** + * Base class for installer input-output handlers + */ +abstract class iohandler_base implements iohandler_interface +{ +	/** +	 * Array of errors +	 * +	 * Errors should be added, when the installation cannot continue without +	 * user interaction. If the aim is to notify the user about something, please +	 * use a warning instead. +	 * +	 * @var array +	 */ +	protected $errors; + +	/** +	 * Array of warnings +	 * +	 * @var array +	 */ +	protected $warnings; + +	/** +	 * Array of logs +	 * +	 * @var array +	 */ +	protected $logs; + +	/** +	 * Array of success messages +	 * +	 * @var array +	 */ +	protected $success; + +	/** +	 * @var \phpbb\language\language +	 */ +	protected $language; + +	/** +	 * @var int +	 */ +	protected $task_progress_count; + +	/** +	 * @var int +	 */ +	protected $current_task_progress; + +	/** +	 * @var string +	 */ +	protected $current_task_name; + +	/** +	 * Constructor +	 */ +	public function __construct() +	{ +		$this->errors	= array(); +		$this->warnings	= array(); +		$this->logs		= array(); +		$this->success	= array(); + +		$this->restart_progress_bar		= false; +		$this->task_progress_count		= 0; +		$this->current_task_progress	= 0; +		$this->current_task_name		= ''; +	} + +	/** +	 * Set language service +	 * +	 * @param \phpbb\language\language $language +	 */ +	public function set_language(\phpbb\language\language $language) +	{ +		$this->language = $language; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function add_error_message($error_title, $error_description = false) +	{ +		if (!is_array($error_title) && strpos($error_title, '<br />') !== false) +		{ +			$error_title = strip_tags(htmlspecialchars_decode($error_title)); +		} +		$this->errors[] = $this->translate_message($error_title, $error_description); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function add_warning_message($warning_title, $warning_description = false) +	{ +		$this->warnings[] = $this->translate_message($warning_title, $warning_description); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function add_log_message($log_title, $log_description = false) +	{ +		$this->logs[] = $this->translate_message($log_title, $log_description); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function add_success_message($success_title, $success_description = false) +	{ +		$this->success[] = $this->translate_message($success_title, $success_description); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function set_task_count($task_count, $restart = false) +	{ +		$this->task_progress_count = $task_count; +		$this->restart_progress_bar = $restart; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function set_progress($task_lang_key, $task_number) +	{ +		$this->current_task_name = ''; + +		if (!empty($task_lang_key)) +		{ +			$this->current_task_name = $this->language->lang($task_lang_key); +		} + +		$this->current_task_progress = $task_number; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function finish_progress($message_lang_key) +	{ +		if (!empty($message_lang_key)) +		{ +			$this->current_task_name = $this->language->lang($message_lang_key); +		} + +		$this->current_task_progress = $this->task_progress_count; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function generate_form_render_data($title, $form) +	{ +		return ''; +	} + +	/** +	 * Localize message. +	 * +	 * Note: When an array is passed into the parameters below, it will be +	 * resolved as printf($param[0], $param[1], ...). +	 * +	 * @param array|string		$title			Title of the message +	 * @param array|string|bool	$description	Description of the message +	 * +	 * @return array	Localized message in an array +	 */ +	protected function translate_message($title, $description) +	{ +		$message_array = array(); + +		$message_array['title'] = call_user_func_array(array($this->language, 'lang'), (array) $title); + +		if ($description !== false) +		{ +			$message_array['description'] = call_user_func_array(array($this->language, 'lang'), (array) $description); +		} + +		return $message_array; +	} +} diff --git a/phpBB/phpbb/install/helper/iohandler/iohandler_interface.php b/phpBB/phpbb/install/helper/iohandler/iohandler_interface.php new file mode 100644 index 0000000000..f22f33d9cb --- /dev/null +++ b/phpBB/phpbb/install/helper/iohandler/iohandler_interface.php @@ -0,0 +1,211 @@ +<?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\install\helper\iohandler; + +/** + * Input-Output handler interface for the installer + */ +interface iohandler_interface +{ +	/** +	 * Renders or returns response message +	 * +	 * @param bool	$no_more_output	Whether or not there will be more output in this output unit +	 */ +	public function send_response($no_more_output = false); + +	/** +	 * Returns input variable +	 * +	 * @param string	$name		Name of the input variable to obtain +	 * @param mixed		$default	A default value that is returned if the variable was not set. +	 * 								This function will always return a value of the same type as the default. +	 * @param bool		$multibyte	If $default is a string this paramater has to be true if the variable may contain any UTF-8 characters +	 *								Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks +	 * +	 * @return mixed	Value of the input variable +	 */ +	public function get_input($name, $default, $multibyte = false); + +	/** +	 * Returns server variable +	 * +	 * This function should work the same as request_interterface::server(). +	 * +	 * @param string	$name		Name of the server variable +	 * @param mixed		$default	Default value to return when the requested variable does not exist +	 * +	 * @return mixed	Value of the server variable +	 */ +	public function get_server_variable($name, $default = ''); + +	/** +	 * Wrapper function for request_interterface::header() +	 * +	 * @param string	$name		Name of the request header variable +	 * @param mixed		$default	Default value to return when the requested variable does not exist +	 * +	 * @return mixed +	 */ +	public function get_header_variable($name, $default = ''); + +	/** +	 * Returns true if the connection is encrypted +	 * +	 * @return bool +	 */ +	public function is_secure(); + +	/** +	 * Adds an error message to the rendering queue +	 * +	 * Note: When an array is passed into the parameters below, it will be +	 * resolved as printf($param[0], $param[1], ...). +	 * +	 * @param string|array		$error_title		Title of the error message. +	 * @param string|bool|array	$error_description	Description of the error (and possibly guidelines to resolve it), +	 * 												or false if the error description is not available. +	 */ +	public function add_error_message($error_title, $error_description = false); + +	/** +	 * Adds a warning message to the rendering queue +	 * +	 * Note: When an array is passed into the parameters below, it will be +	 * resolved as printf($param[0], $param[1], ...). +	 * +	 * @param string|array		$warning_title			Title of the warning message +	 * @param string|bool|array	$warning_description	Description of the warning (and possibly guidelines to resolve it), +	 * 													or false if the warning description is not available +	 */ +	public function add_warning_message($warning_title, $warning_description = false); + +	/** +	 * Adds a log message to the rendering queue +	 * +	 * Note: When an array is passed into the parameters below, it will be +	 * resolved as printf($param[0], $param[1], ...). +	 * +	 * @param string|array		$log_title			Title of the log message +	 * @param string|bool|array	$log_description	Description of the log, +	 * 												or false if the log description is not available +	 */ +	public function add_log_message($log_title, $log_description = false); + +	/** +	 * Adds a success message to the rendering queue +	 * +	 * Note: When an array is passed into the parameters below, it will be +	 * resolved as printf($param[0], $param[1], ...). +	 * +	 * @param string|array		$success_title			Title of the success message +	 * @param string|bool|array	$success_description	Description of the success, +	 * 													or false if the success description is not available +	 * +	 * @return null +	 */ +	public function add_success_message($success_title, $success_description = false); + +	/** +	 * Adds a requested data group to the rendering queue +	 * +	 * @param string	$title	Language variable with the title of the form +	 * @param array		$form	An array describing the required data (options etc) +	 */ +	public function add_user_form_group($title, $form); + +	/** +	 * Returns the rendering information for the form +	 * +	 * @param string	$title	Language variable with the title of the form +	 * @param array		$form	An array describing the required data (options etc) +	 * +	 * @return string	Information to render the form +	 */ +	public function generate_form_render_data($title, $form); + +	/** +	 * Sets the number of tasks belonging to the installer in the current mode. +	 * +	 * @param int	$task_count	Number of tasks +	 * @param bool	$restart	Whether or not to restart the progress bar, false by default +	 */ +	public function set_task_count($task_count, $restart = false); + +	/** +	 * Sets the progress information +	 * +	 * @param string	$task_lang_key	Language key for the name of the task +	 * @param int		$task_number	Position of the current task in the task queue +	 */ +	public function set_progress($task_lang_key, $task_number); + +	/** +	 * Sends refresh request to the client +	 */ +	public function request_refresh(); + +	/** +	 * Marks stage as active in the navigation bar +	 * +	 * @param array	$menu_path	Array to the navigation elem +	 */ +	public function set_active_stage_menu($menu_path); + +	/** +	 * Marks stage as completed in the navigation bar +	 * +	 * @param array	$menu_path	Array to the navigation elem +	 */ +	public function set_finished_stage_menu($menu_path); + +	/** +	 * Finish the progress bar +	 * +	 * @param string	$message_lang_key	Language key for the message +	 */ +	public function finish_progress($message_lang_key); + +	/** +	 * Adds a download link +	 * +	 * @param string			$route	Route for the link +	 * @param string			$title	Language key for the title +	 * @param string|null|array	$msg	Language key for the message +	 */ +	public function add_download_link($route, $title, $msg = null); + +	/** +	 * Redirects the user to a new page +	 * +	 * @param string	$url		URL to redirect to +	 * @param bool		$use_ajax	Whether or not to use AJAX redirect +	 */ +	public function redirect($url, $use_ajax = false); + +	/** +	 * Renders the status of update files +	 * +	 * @param array	$status_array	Array containing files in groups to render +	 */ +	public function render_update_file_status($status_array); + +	/** +	 * Sends and sets cookies +	 * +	 * @param string	$cookie_name	Name of the cookie to set +	 * @param string	$cookie_value	Value of the cookie to set +	 */ +	public function set_cookie($cookie_name, $cookie_value); +} diff --git a/phpBB/phpbb/install/helper/navigation/convertor_navigation.php b/phpBB/phpbb/install/helper/navigation/convertor_navigation.php new file mode 100644 index 0000000000..54cab83b1d --- /dev/null +++ b/phpBB/phpbb/install/helper/navigation/convertor_navigation.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\install\helper\navigation; + +use phpbb\install\helper\install_helper; + +class convertor_navigation implements navigation_interface +{ +	/** +	 * @var install_helper +	 */ +	private $install_helper; + +	/** +	 * Constructor +	 * +	 * @param install_helper	$install_helper +	 */ +	public function __construct(install_helper $install_helper) +	{ +		$this->install_helper = $install_helper; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get() +	{ +		if (!$this->install_helper->is_phpbb_installed()) +		{ +			return array(); +		} + +		return array( +			'convert'	=> array( +				'label'	=> 'CONVERT', +				'route'	=> 'phpbb_convert_intro', +				'order'	=> 3, +				array( +					'intro'	=> array( +						'label'	=> 'SUB_INTRO', +						'stage'	=> true, +						'order'	=> 0, +					), +					'settings'	=> array( +						'label'	=> 'STAGE_SETTINGS', +						'stage'	=> true, +						'route'	=> 'phpbb_convert_settings', +						'order'	=> 1, +					), +					'convert'	=> array( +						'label'	=> 'STAGE_IN_PROGRESS', +						'stage'	=> true, +						'route'	=> 'phpbb_convert_convert', +						'order'	=> 2, +					), +					'finish'	=> array( +						'label'	=> 'CONVERT_COMPLETE', +						'stage'	=> true, +						'route'	=> 'phpbb_convert_finish', +						'order'	=> 3, +					), +				), +			), +		); +	} +} diff --git a/phpBB/phpbb/install/helper/navigation/install_navigation.php b/phpBB/phpbb/install/helper/navigation/install_navigation.php new file mode 100644 index 0000000000..f690f8de76 --- /dev/null +++ b/phpBB/phpbb/install/helper/navigation/install_navigation.php @@ -0,0 +1,75 @@ +<?php +/** + * + * This file is part of the phpBB Forum Software package. + * + * @copyright (c) phpBB Limited <https://www.phpbb.com> + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\helper\navigation; + +use phpbb\install\helper\install_helper; + +class install_navigation implements navigation_interface +{ +	/** +	 * @var install_helper +	 */ +	private $install_helper; + +	/** +	 * Constructor +	 * +	 * @param install_helper	$install_helper +	 */ +	public function __construct(install_helper $install_helper) +	{ +		$this->install_helper = $install_helper; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get() +	{ +		if ($this->install_helper->is_phpbb_installed()) +		{ +			return array(); +		} + +		return array( +			'install'	=> array( +				'label'	=> 'INSTALL', +				'route'	=> 'phpbb_installer_install', +				'order'	=> 1, +				array( +					'introduction'	=> array( +						'label'	=> 'INTRODUCTION_TITLE', +						'stage'	=> true, +						'order'	=> 0, +					), +					'requirements'	=> array( +						'label'	=> 'STAGE_REQUIREMENTS', +						'stage'	=> true, +						'order'	=> 1, +					), +					'obtain_data'	=> array( +						'label'	=> 'STAGE_OBTAIN_DATA', +						'stage'	=> true, +						'order'	=> 2, +					), +					'install'	=> array( +						'label'	=> 'STAGE_INSTALL', +						'stage'	=> true, +						'order'	=> 3, +					), +				), +			), +		); +	} +} diff --git a/phpBB/phpbb/install/helper/navigation/main_navigation.php b/phpBB/phpbb/install/helper/navigation/main_navigation.php new file mode 100644 index 0000000000..214bb04963 --- /dev/null +++ b/phpBB/phpbb/install/helper/navigation/main_navigation.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\install\helper\navigation; + +class main_navigation implements navigation_interface +{ +	/** +	 * {@inheritdoc} +	 */ +	public function get() +	{ +		return array( +			'overview'	=> array( +				'label'	=> 'MENU_OVERVIEW', +				'route'	=> 'phpbb_installer_index', +				'order'	=> 0, +				array( +					'introduction'	=> array( +						'label'	=> 'MENU_INTRO', +						'route'	=> 'phpbb_installer_index', +						'order'	=> 0, +					), +					'support'	=> array( +						'label'	=> 'MENU_SUPPORT', +						'route'	=> 'phpbb_installer_support', +						'order'	=> 1, +					), +					'license'	=> array( +						'label'	=> 'MENU_LICENSE', +						'route'	=> 'phpbb_installer_license', +						'order'	=> 2, +					), +				), +			), +		); +	} +} diff --git a/phpBB/phpbb/install/helper/navigation/navigation_interface.php b/phpBB/phpbb/install/helper/navigation/navigation_interface.php new file mode 100644 index 0000000000..eebdbe923f --- /dev/null +++ b/phpBB/phpbb/install/helper/navigation/navigation_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\install\helper\navigation; + +/** + * Interface for installer's navigation defining services + */ +interface navigation_interface +{ +	/** +	 * Returns an array with the navigation items +	 * +	 * The returned array should have the following format: +	 * <code> +	 * array( +	 * 	'parent_nav_name' => array( +	 * 		'nav_name' => array( +	 * 			'label' => 'MY_MENU', +	 * 			'route' => 'phpbb_route_name', +	 * 		) +	 * 	) +	 * ) +	 * </code> +	 * +	 * Navigation item setting options: +	 * 	- label: The language variable name +	 * 	- route: Name of the route which it is belongs to +	 * +	 * @return array +	 */ +	public function get(); +} diff --git a/phpBB/phpbb/install/helper/navigation/navigation_provider.php b/phpBB/phpbb/install/helper/navigation/navigation_provider.php new file mode 100644 index 0000000000..d52aec8999 --- /dev/null +++ b/phpBB/phpbb/install/helper/navigation/navigation_provider.php @@ -0,0 +1,121 @@ +<?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\install\helper\navigation; + +use phpbb\di\service_collection; + +/** + * Installers navigation provider + */ +class navigation_provider +{ +	/** +	 * @var array +	 */ +	private $menu_collection; + +	/** +	 * Constructor +	 * +	 * @param service_collection		$plugins +	 */ +	public function __construct(service_collection $plugins) +	{ +		$this->menu_collection	= array(); + +		foreach ($plugins as $plugin => $plugin_instance) +		{ +			$this->register($plugin_instance); +		} +	} + +	/** +	 * Returns navigation array +	 * +	 * @return array +	 */ +	public function get() +	{ +		return $this->menu_collection; +	} + +	/** +	 * Registers a navigation provider's navigation items +	 * +	 * @param navigation_interface	$navigation +	 */ +	public function register(navigation_interface $navigation) +	{ +		$nav_arry = $navigation->get(); +		$this->menu_collection = $this->merge($nav_arry, $this->menu_collection); +	} + +	/** +	 * Set a property in the navigation array +	 * +	 * @param array	$nav_element	Array to the navigation elem +	 * @param array	$property_array	Array with the properties to set +	 */ +	public function set_nav_property($nav_element, $property_array) +	{ +		$array_pointer = array(); +		$array_root_pointer = &$array_pointer; +		foreach ($nav_element as $array_path) +		{ +			$array_pointer[$array_path] = array(); +			$array_pointer = &$array_pointer[$array_path]; +		} + +		$array_pointer = $property_array; + +		$this->menu_collection = $this->merge($array_root_pointer, $this->menu_collection); +	} + +	/** +	 * Recursive array merge +	 * +	 * This function is necessary to be able to replace the options of +	 * already set navigation items. +	 * +	 * @param array	$array_to_merge +	 * @param array	$array_to_merge_into +	 * +	 * @return array	Merged array +	 */ +	private function merge($array_to_merge, $array_to_merge_into) +	{ +		$merged_array = $array_to_merge_into; + +		foreach ($array_to_merge as $key => $value) +		{ +			if (isset($array_to_merge_into[$key])) +			{ +				if (is_array($array_to_merge_into[$key]) && is_array($value)) +				{ +					$merged_array[$key] = $this->merge($value, $array_to_merge_into[$key]); +				} +				else +				{ +					$merged_array[$key] = $value; +				} +			} +			else +			{ +				$merged_array[$key] = $value; +			} +		} + +		return $merged_array; +	} +} diff --git a/phpBB/phpbb/install/helper/navigation/update_navigation.php b/phpBB/phpbb/install/helper/navigation/update_navigation.php new file mode 100644 index 0000000000..3d239c3451 --- /dev/null +++ b/phpBB/phpbb/install/helper/navigation/update_navigation.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\install\helper\navigation; + +use phpbb\install\helper\install_helper; + +class update_navigation implements navigation_interface +{ +	/** +	 * @var install_helper +	 */ +	private $install_helper; + +	/** +	 * Constructor +	 * +	 * @param install_helper	$install_helper +	 */ +	public function __construct(install_helper $install_helper) +	{ +		$this->install_helper = $install_helper; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get() +	{ +		if (!$this->install_helper->is_phpbb_installed()) +		{ +			return array(); +		} + +		return array( +			'update' => array( +				'label'		=> 'UPDATE', +				'route'		=> 'phpbb_installer_update', +				'order'		=> 1, +				array( +					'introduction'	=> array( +						'label'	=> 'INTRODUCTION_TITLE', +						'stage'	=> true, +						'order'	=> 0, +					), +					'requirements'	=> array( +						'label'	=> 'STAGE_REQUIREMENTS', +						'stage'	=> true, +						'order'	=> 1, +					), +					'obtain_data'	=> array( +						'label'	=> 'STAGE_OBTAIN_DATA', +						'stage'	=> true, +						'order'	=> 2, +					), +					'update_files'	=> array( +						'label'	=> 'STAGE_UPDATE_FILES', +						'stage'	=> true, +						'order'	=> 3, +					), +					'update_database'	=> array( +						'label'	=> 'STAGE_UPDATE_DATABASE', +						'stage'	=> true, +						'order'	=> 4, +					), +				), +			), +		); +	} +} diff --git a/phpBB/phpbb/install/helper/update_helper.php b/phpBB/phpbb/install/helper/update_helper.php new file mode 100644 index 0000000000..a00731d317 --- /dev/null +++ b/phpBB/phpbb/install/helper/update_helper.php @@ -0,0 +1,113 @@ +<?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\install\helper; + +/** + * General helper functionality for the updater + */ +class update_helper +{ +	/** +	 * @var string +	 */ +	protected $path_to_new_files; + +	/** +	 * @var string +	 */ +	protected $path_to_old_files; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * Constructor +	 * +	 * @param string	$phpbb_root_path +	 */ +	public function __construct($phpbb_root_path) +	{ +		$this->phpbb_root_path		= $phpbb_root_path; +		$this->path_to_new_files	= $phpbb_root_path . 'install/update/new/'; +		$this->path_to_old_files	= $phpbb_root_path . 'install/update/old/'; +	} + +	/** +	 * Returns path to new update files +	 * +	 * @return string +	 */ +	public function get_path_to_new_update_files() +	{ +		return $this->path_to_new_files; +	} + +	/** +	 * Returns path to new update files +	 * +	 * @return string +	 */ +	public function get_path_to_old_update_files() +	{ +		return $this->path_to_old_files; +	} + +	/** +	 * Includes the updated file if available +	 * +	 * @param string	$filename	Path to the file relative to phpBB root path +	 */ +	public function include_file($filename) +	{ +		if (is_file($this->path_to_new_files . $filename)) +		{ +			include_once($this->path_to_new_files . $filename); +		} +		else if (is_file($this->phpbb_root_path . $filename)) +		{ +			include_once($this->phpbb_root_path . $filename); +		} +	} + +	/** +	 * Customized version_compare() +	 * +	 * @param string		$version_number1 +	 * @param string		$version_number2 +	 * @param string|null	$operator +	 * @return int|bool	The returned value is identical to the PHP build-in function version_compare() +	 */ +	public function phpbb_version_compare($version_number1, $version_number2, $operator = null) +	{ +		if ($operator === null) +		{ +			$result = version_compare( +				str_replace('rc', 'RC', strtolower($version_number1)), +				str_replace('rc', 'RC', strtolower($version_number2)) +			); +		} +		else +		{ +			$result = version_compare( +				str_replace('rc', 'RC', strtolower($version_number1)), +				str_replace('rc', 'RC', strtolower($version_number2)), +				$operator +			); +		} + +		return $result; +	} +} diff --git a/phpBB/phpbb/install/installer.php b/phpBB/phpbb/install/installer.php new file mode 100644 index 0000000000..240423ae78 --- /dev/null +++ b/phpBB/phpbb/install/installer.php @@ -0,0 +1,337 @@ +<?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\install; + +use phpbb\cache\driver\driver_interface; +use phpbb\di\ordered_service_collection; +use phpbb\install\exception\cannot_build_container_exception; +use phpbb\install\exception\installer_config_not_writable_exception; +use phpbb\install\exception\jump_to_restart_point_exception; +use phpbb\install\exception\resource_limit_reached_exception; +use phpbb\install\exception\user_interaction_required_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\container_factory; +use phpbb\install\helper\iohandler\ajax_iohandler; +use phpbb\install\helper\iohandler\cli_iohandler; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\path_helper; + +class installer +{ +	/** +	 * @var driver_interface +	 */ +	protected $cache; + +	/** +	 * @var container_factory +	 */ +	protected $container_factory; + +	/** +	 * @var config +	 */ +	protected $install_config; + +	/** +	 * @var ordered_service_collection +	 */ +	protected $installer_modules; + +	/** +	 * @var iohandler_interface +	 */ +	protected $iohandler; + +	/** +	 * @var string +	 */ +	protected $web_root; + +	/** +	 * Stores the number of steps that a given module has +	 * +	 * @var array +	 */ +	protected $module_step_count; + +	/** +	 * @var bool +	 */ +	protected $purge_cache_before; + +	/** +	 * Constructor +	 * +	 * @param driver_interface	$cache			Cache service +	 * @param config			$config			Installer config handler +	 * @param path_helper		$path_helper	Path helper +	 * @param container_factory	$container		Container +	 */ +	public function __construct(driver_interface $cache, config $config, path_helper $path_helper, container_factory $container) +	{ +		$this->cache				= $cache; +		$this->install_config		= $config; +		$this->container_factory	= $container; +		$this->installer_modules	= null; +		$this->web_root				= $path_helper->get_web_root_path(); +		$this->purge_cache_before	= false; +	} + +	/** +	 * Sets modules to execute +	 * +	 * Note: The installer will run modules in the order they are set in +	 * the array. +	 * +	 * @param ordered_service_collection	$modules	Service collection of module service names +	 */ +	public function set_modules(ordered_service_collection $modules) +	{ +		$this->installer_modules = $modules; +	} + +	/** +	 * Sets input-output handler objects +	 * +	 * @param iohandler_interface	$iohandler +	 */ +	public function set_iohandler(iohandler_interface $iohandler) +	{ +		$this->iohandler = $iohandler; +	} + +	/** +	 * Sets whether to purge cache before the installation process +	 * +	 * @param bool	$purge_cache_before +	 */ +	public function set_purge_cache_before($purge_cache_before) +	{ +		$this->purge_cache_before = $purge_cache_before; +	} + +	/** +	 * Run phpBB installer +	 */ +	public function run() +	{ +		if ($this->iohandler instanceof ajax_iohandler) +		{ +			$this->iohandler->acquire_lock(); +		} + +		// Load install progress +		$this->install_config->load_config(); + +		if (!$this->install_config->get('cache_purged_before', false) && $this->purge_cache_before) +		{ +			/** @var \phpbb\cache\driver\driver_interface $cache */ +			$cache = $this->container_factory->get('cache.driver'); +			$cache->purge(); +			$this->install_config->set('cache_purged_before', true); +		} + +		// Recover install progress +		$module_index = $this->recover_progress(); + +		// Variable used to check if the install process have been finished +		$install_finished	= false; +		$fail_cleanup		= false; +		$send_refresh		= false; + +		// We are installing something, so the introduction stage can go now... +		$this->install_config->set_finished_navigation_stage(array('install', 0, 'introduction')); +		$this->iohandler->set_finished_stage_menu(array('install', 0, 'introduction')); + +		if ($this->install_config->get_task_progress_count() === 0) +		{ +			// Count all tasks in the current installer modules +			$step_count = 0; + +			/** @var \phpbb\install\module_interface $module */ +			foreach ($this->installer_modules as $name => $module) +			{ +				$module_step_count = $module->get_step_count(); +				$step_count += $module_step_count; +				$this->module_step_count[$name] = $module_step_count; +			} + +			// Set task count +			$this->install_config->set_task_progress_count($step_count); +		} + +		// Set up progress information +		$this->iohandler->set_task_count( +			$this->install_config->get_task_progress_count() +		); + +		try +		{ +			$iterator = $this->installer_modules->getIterator(); + +			if ($module_index < $iterator->count()) +			{ +				$iterator->seek($module_index); +			} +			else +			{ +				$iterator->seek($module_index - 1); +				$iterator->next(); +			} + +			while ($iterator->valid()) +			{ +				$module	= $iterator->current(); +				$name	= $iterator->key(); + +				// Check if module should be executed +				if (!$module->is_essential() && !$module->check_requirements()) +				{ +					$this->install_config->set_finished_navigation_stage($module->get_navigation_stage_path()); +					$this->iohandler->set_finished_stage_menu($module->get_navigation_stage_path()); + +					$this->iohandler->add_log_message(array( +						'SKIP_MODULE', +						$name, +					)); +					$this->install_config->increment_current_task_progress($this->module_step_count[$name]); +				} +				else +				{ +					// Set the correct stage in the navigation bar +					$this->install_config->set_active_navigation_stage($module->get_navigation_stage_path()); +					$this->iohandler->set_active_stage_menu($module->get_navigation_stage_path()); + +					$this->iohandler->send_response(); + +					$module->run(); + +					$this->install_config->set_finished_navigation_stage($module->get_navigation_stage_path()); +					$this->iohandler->set_finished_stage_menu($module->get_navigation_stage_path()); +				} + +				$module_index++; +				$iterator->next(); + +				// Save progress +				$this->install_config->set_active_module($name, $module_index); + +				if ($iterator->valid() && ($this->install_config->get_time_remaining() <= 0 || $this->install_config->get_memory_remaining() <= 0)) +				{ +					throw new resource_limit_reached_exception(); +				} +			} + +			// Installation finished +			$install_finished = true; + +			if ($this->iohandler instanceof cli_iohandler) +			{ +				$this->iohandler->add_success_message('INSTALLER_FINISHED'); +			} +			else +			{ +				global $SID; +				$acp_url = $this->web_root . 'adm/index.php' . $SID; +				$this->iohandler->add_success_message('INSTALLER_FINISHED', array( +					'ACP_LINK', +					$acp_url, +				)); +			} +		} +		catch (user_interaction_required_exception $e) +		{ +			$this->iohandler->send_response(true); +		} +		catch (resource_limit_reached_exception $e) +		{ +			$send_refresh = true; +		} +		catch (jump_to_restart_point_exception $e) +		{ +			$this->install_config->jump_to_restart_point($e->get_restart_point_name()); +			$send_refresh = true; +		} +		catch (\Exception $e) +		{ +			$this->iohandler->add_error_message($e->getMessage()); +			$this->iohandler->send_response(true); +			$fail_cleanup = true; +		} + +		if ($this->iohandler instanceof ajax_iohandler) +		{ +			$this->iohandler->release_lock(); +		} + +		if ($install_finished) +		{ +			// Send install finished message +			$this->iohandler->set_progress('INSTALLER_FINISHED', $this->install_config->get_task_progress_count()); +			$this->iohandler->send_response(true); +		} +		else if ($send_refresh) +		{ +			$this->iohandler->request_refresh(); +			$this->iohandler->send_response(true); +		} + +		// Save install progress +		try +		{ +			if ($install_finished || $fail_cleanup) +			{ +				$this->install_config->clean_up_config_file(); +				$this->cache->purge(); + +				try +				{ +					/** @var \phpbb\cache\driver\driver_interface $cache */ +					$cache = $this->container_factory->get('cache.driver'); +					$cache->purge(); +				} +				catch (cannot_build_container_exception $e) +				{ +					// Do not do anything, this just means there is no config.php yet +				} +			} +			else +			{ +				$this->install_config->save_config(); +			} +		} +		catch (installer_config_not_writable_exception $e) +		{ +			// It is allowed to fail this test during requirements testing +			$progress_data = $this->install_config->get_progress_data(); + +			if ($progress_data['last_task_module_name'] !== 'installer.module.requirements_install') +			{ +				$this->iohandler->add_error_message('INSTALLER_CONFIG_NOT_WRITABLE'); +			} +		} +	} + +	/** +	 * Recover install progress +	 * +	 * @return string	Index of the next installer module to execute +	 */ +	protected function recover_progress() +	{ +		$progress_array = $this->install_config->get_progress_data(); +		return $progress_array['last_task_module_index']; +	} +} diff --git a/phpBB/phpbb/install/installer_configuration.php b/phpBB/phpbb/install/installer_configuration.php new file mode 100644 index 0000000000..c660c99d0f --- /dev/null +++ b/phpBB/phpbb/install/installer_configuration.php @@ -0,0 +1,143 @@ +<?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\install; + +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\ConfigurationInterface; + +class installer_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('installer'); +		$rootNode +			->children() +				->arrayNode('admin') +					->children() +						->scalarNode('name')->defaultValue('admin')->cannotBeEmpty()->end() +						->scalarNode('password')->defaultValue('adminadmin')->cannotBeEmpty()->end() +						->scalarNode('email')->defaultValue('admin@example.org')->cannotBeEmpty()->end() +					->end() +				->end() +				->arrayNode('board') +					->children() +						->scalarNode('lang') +							->defaultValue('en') +							->cannotBeEmpty() +							->end() +						->scalarNode('name') +							->defaultValue('My Board') +							->cannotBeEmpty() +							->end() +						->scalarNode('description') +						->defaultValue('My amazing new phpBB board') +						->cannotBeEmpty() +						->end() +					->end() +				->end() +				->arrayNode('database') +					->children() +						->scalarNode('dbms') +							->defaultValue('sqlite3') +							->cannotBeEmpty() +							->isRequired() +							->end() +						->scalarNode('dbhost') +							->defaultValue(null) +							->end() +						->scalarNode('dbport') +							->defaultValue(null) +							->end() +						->scalarNode('dbuser') +							->defaultValue(null) +							->end() +						->scalarNode('dbpasswd') +							->defaultValue(null) +							->end() +						->scalarNode('dbname') +							->defaultValue(null) +							->end() +						->scalarNode('table_prefix') +							->defaultValue('phpbb_') +							->cannotBeEmpty() +							->isRequired() +							->end() +					->end() +				->end() +				->arrayNode('email') +					->canBeEnabled() +					->addDefaultsIfNotSet() +					->children() +						->booleanNode('smtp_delivery') +							->defaultValue(false) +							->treatNullLike(false) +							->end() +						->scalarNode('smtp_host') +							->defaultValue(null) +							->end() +						->scalarNode('smtp_port') +							->defaultValue(null) +							->end() +						->scalarNode('smtp_auth') +							->defaultValue(null) +							->end() +						->scalarNode('smtp_user') +							->defaultValue(null) +							->end() +						->scalarNode('smtp_pass') +							->defaultValue(null) +							->end() +					->end() +				->end() +				->arrayNode('server') +					->children() +						->booleanNode('cookie_secure') +							->defaultValue(false) +							->treatNullLike(false) +							->end() +						->scalarNode('server_protocol') +							->defaultValue('http://') +							->cannotBeEmpty() +							->end() +						->booleanNode('force_server_vars') +							->defaultValue(false) +							->treatNullLike(false) +							->end() +						->scalarNode('server_name') +							->defaultValue('localhost') +							->cannotBeEmpty() +							->end() +						->integerNode('server_port') +							->defaultValue(80) +							->min(1) +							->cannotBeEmpty() +							->end() +						->scalarNode('script_path') +							->defaultValue('/') +							->cannotBeEmpty() +							 ->end() +					->end() +				->end() +			->end() +		; +		return $treeBuilder; +	} +} diff --git a/phpBB/phpbb/install/module/install_data/module.php b/phpBB/phpbb/install/module/install_data/module.php new file mode 100644 index 0000000000..77f1f73f1f --- /dev/null +++ b/phpBB/phpbb/install/module/install_data/module.php @@ -0,0 +1,28 @@ +<?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\install\module\install_data; + +/** + * Installer module for recovering and installing default data installation + */ +class module extends \phpbb\install\module_base +{ +	/** +	 * {@inheritdoc} +	 */ +	public function get_navigation_stage_path() +	{ +		return array('install', 0, 'install'); +	} +} diff --git a/phpBB/phpbb/install/module/install_data/task/add_bots.php b/phpBB/phpbb/install/module/install_data/task/add_bots.php new file mode 100644 index 0000000000..1f1cecceb2 --- /dev/null +++ b/phpBB/phpbb/install/module/install_data/task/add_bots.php @@ -0,0 +1,263 @@ +<?php +/** + * + * This file is part of the phpBB Forum Software package. + * + * @copyright (c) phpBB Limited <https://www.phpbb.com> + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\install_data\task; + +use phpbb\install\exception\resource_limit_reached_exception; + +class add_bots extends \phpbb\install\task_base +{ +	/** +	 * A list of the web-crawlers/bots we recognise by default +	 * +	 * Candidates but not included: +	 * 'Accoona [Bot]'				'Accoona-AI-Agent/' +	 * 'ASPseek [Crawler]'			'ASPseek/' +	 * 'Boitho [Crawler]'			'boitho.com-dc/' +	 * 'Bunnybot [Bot]'				'powered by www.buncat.de' +	 * 'Cosmix [Bot]'				'cfetch/' +	 * 'Crawler Search [Crawler]'	'.Crawler-Search.de' +	 * 'Findexa [Crawler]'			'Findexa Crawler (' +	 * 'GBSpider [Spider]'			'GBSpider v' +	 * 'genie [Bot]'				'genieBot (' +	 * 'Hogsearch [Bot]'			'oegp v. 1.3.0' +	 * 'Insuranco [Bot]'			'InsurancoBot' +	 * 'IRLbot [Bot]'				'http://irl.cs.tamu.edu/crawler' +	 * 'ISC Systems [Bot]'			'ISC Systems iRc Search' +	 * 'Jyxobot [Bot]'				'Jyxobot/' +	 * 'Kraehe [Metasuche]'			'-DIE-KRAEHE- META-SEARCH-ENGINE/' +	 * 'LinkWalker'					'LinkWalker' +	 * 'MMSBot [Bot]'				'http://www.mmsweb.at/bot.html' +	 * 'Naver [Bot]'				'nhnbot@naver.com)' +	 * 'NetResearchServer'			'NetResearchServer/' +	 * 'Nimble [Crawler]'			'NimbleCrawler' +	 * 'Ocelli [Bot]'				'Ocelli/' +	 * 'Onsearch [Bot]'				'onCHECK-Robot' +	 * 'Orange [Spider]'			'OrangeSpider' +	 * 'Sproose [Bot]'				'http://www.sproose.com/bot' +	 * 'Susie [Sync]'				'!Susie (http://www.sync2it.com/susie)' +	 * 'Tbot [Bot]'					'Tbot/' +	 * 'Thumbshots [Capture]'		'thumbshots-de-Bot' +	 * 'Vagabondo [Crawler]'		'http://webagent.wise-guys.nl/' +	 * 'Walhello [Bot]'				'appie 1.1 (www.walhello.com)' +	 * 'WissenOnline [Bot]'			'WissenOnline-Bot' +	 * 'WWWeasel [Bot]'				'WWWeasel Robot v' +	 * 'Xaldon [Spider]'			'Xaldon WebSpider' +	 * +	 * @var array +	 */ +	protected $bot_list = array( +		'AdsBot [Google]'			=> array('AdsBot-Google', ''), +		'Alexa [Bot]'				=> array('ia_archiver', ''), +		'Alta Vista [Bot]'			=> array('Scooter/', ''), +		'Ask Jeeves [Bot]'			=> array('Ask Jeeves', ''), +		'Baidu [Spider]'			=> array('Baiduspider', ''), +		'Bing [Bot]'				=> array('bingbot/', ''), +		'Exabot [Bot]'				=> array('Exabot', ''), +		'FAST Enterprise [Crawler]'	=> array('FAST Enterprise Crawler', ''), +		'FAST WebCrawler [Crawler]'	=> array('FAST-WebCrawler/', ''), +		'Francis [Bot]'				=> array('http://www.neomo.de/', ''), +		'Gigabot [Bot]'				=> array('Gigabot/', ''), +		'Google Adsense [Bot]'		=> array('Mediapartners-Google', ''), +		'Google Desktop'			=> array('Google Desktop', ''), +		'Google Feedfetcher'		=> array('Feedfetcher-Google', ''), +		'Google [Bot]'				=> array('Googlebot', ''), +		'Heise IT-Markt [Crawler]'	=> array('heise-IT-Markt-Crawler', ''), +		'Heritrix [Crawler]'		=> array('heritrix/1.', ''), +		'IBM Research [Bot]'		=> array('ibm.com/cs/crawler', ''), +		'ICCrawler - ICjobs'		=> array('ICCrawler - ICjobs', ''), +		'ichiro [Crawler]'			=> array('ichiro/', ''), +		'Majestic-12 [Bot]'			=> array('MJ12bot/', ''), +		'Metager [Bot]'				=> array('MetagerBot/', ''), +		'MSN NewsBlogs'				=> array('msnbot-NewsBlogs/', ''), +		'MSN [Bot]'					=> array('msnbot/', ''), +		'MSNbot Media'				=> array('msnbot-media/', ''), +		'Nutch [Bot]'				=> array('http://lucene.apache.org/nutch/', ''), +		'Online link [Validator]'	=> array('online link validator', ''), +		'psbot [Picsearch]'			=> array('psbot/0', ''), +		'Sensis [Crawler]'			=> array('Sensis Web Crawler', ''), +		'SEO Crawler'				=> array('SEO search Crawler/', ''), +		'Seoma [Crawler]'			=> array('Seoma [SEO Crawler]', ''), +		'SEOSearch [Crawler]'		=> array('SEOsearch/', ''), +		'Snappy [Bot]'				=> array('Snappy/1.1 ( http://www.urltrends.com/ )', ''), +		'Steeler [Crawler]'			=> array('http://www.tkl.iis.u-tokyo.ac.jp/~crawler/', ''), +		'Telekom [Bot]'				=> array('crawleradmin.t-info@telekom.de', ''), +		'TurnitinBot [Bot]'			=> array('TurnitinBot/', ''), +		'Voyager [Bot]'				=> array('voyager/', ''), +		'W3 [Sitesearch]'			=> array('W3 SiteSearch Crawler', ''), +		'W3C [Linkcheck]'			=> array('W3C-checklink/', ''), +		'W3C [Validator]'			=> array('W3C_Validator', ''), +		'YaCy [Bot]'				=> array('yacybot', ''), +		'Yahoo MMCrawler [Bot]'		=> array('Yahoo-MMCrawler/', ''), +		'Yahoo Slurp [Bot]'			=> array('Yahoo! DE Slurp', ''), +		'Yahoo [Bot]'				=> array('Yahoo! Slurp', ''), +		'YahooSeeker [Bot]'			=> array('YahooSeeker/', ''), +	); + +	/** +	 * @var \phpbb\db\driver\driver_interface +	 */ +	protected $db; + +	/** +	 * @var \phpbb\install\helper\config +	 */ +	protected $install_config; + +	/** +	 * @var \phpbb\install\helper\iohandler\iohandler_interface +	 */ +	protected $io_handler; + +	/** +	 * @var \phpbb\language\language +	 */ +	protected $language; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * @var string +	 */ +	protected $php_ext; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\install\helper\config							$install_config		Installer's config +	 * @param \phpbb\install\helper\iohandler\iohandler_interface	$iohandler			Input-output handler for the installer +	 * @param \phpbb\install\helper\container_factory				$container			Installer's DI container +	 * @param \phpbb\language\language								$language			Language provider +	 * @param string												$phpbb_root_path	Relative path to phpBB root +	 * @param string												$php_ext			PHP extension +	 */ +	public function __construct(\phpbb\install\helper\config $install_config, +								\phpbb\install\helper\iohandler\iohandler_interface $iohandler, +								\phpbb\install\helper\container_factory $container, +								\phpbb\language\language $language, +								$phpbb_root_path, +								$php_ext) +	{ +		parent::__construct(true); + +		$this->db				= $container->get('dbal.conn'); +		$this->install_config	= $install_config; +		$this->io_handler		= $iohandler; +		$this->language			= $language; +		$this->phpbb_root_path	= $phpbb_root_path; +		$this->php_ext			= $php_ext; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		$this->db->sql_return_on_error(true); + +		$sql = 'SELECT group_id +			FROM ' . GROUPS_TABLE . " +			WHERE group_name = 'BOTS'"; +		$result = $this->db->sql_query($sql); +		$group_id = (int) $this->db->sql_fetchfield('group_id'); +		$this->db->sql_freeresult($result); + +		if (!$group_id) +		{ +			// If we reach this point then something has gone very wrong +			$this->io_handler->add_error_message('NO_GROUP'); +		} + +		$i = $this->install_config->get('add_bot_index', 0); +		$bot_list = array_slice($this->bot_list, $i); + +		foreach ($bot_list as $bot_name => $bot_ary) +		{ +			$user_row = array( +				'user_type'				=> USER_IGNORE, +				'group_id'				=> $group_id, +				'username'				=> $bot_name, +				'user_regdate'			=> time(), +				'user_password'			=> '', +				'user_colour'			=> '9E8DA7', +				'user_email'			=> '', +				'user_lang'				=> $this->install_config->get('default_lang'), +				'user_style'			=> 1, +				'user_timezone'			=> 'UTC', +				'user_dateformat'		=> $this->language->lang('default_dateformat'), +				'user_allow_massemail'	=> 0, +				'user_allow_pm'			=> 0, +			); + +			if (!function_exists('user_add')) +			{ +				include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); +			} + +			$user_id = user_add($user_row); + +			if (!$user_id) +			{ +				// If we can't insert this user then continue to the next one to avoid inconsistent data +				$this->io_handler->add_error_message('CONV_ERROR_INSERT_BOT'); + +				$i++; +				continue; +			} + +			$sql = 'INSERT INTO ' . BOTS_TABLE . ' ' . $this->db->sql_build_array('INSERT', array( +				'bot_active'	=> 1, +				'bot_name'		=> (string) $bot_name, +				'user_id'		=> (int) $user_id, +				'bot_agent'		=> (string) $bot_ary[0], +				'bot_ip'		=> (string) $bot_ary[1], +			)); + +			$this->db->sql_query($sql); + +			$i++; + +			// Stop execution if resource limit is reached +			if ($this->install_config->get_time_remaining() <= 0 || $this->install_config->get_memory_remaining() <= 0) +			{ +				break; +			} +		} + +		$this->install_config->set('add_bot_index', $i); + +		if ($i < sizeof($this->bot_list)) +		{ +			throw new resource_limit_reached_exception(); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 1; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return 'TASK_ADD_BOTS'; +	} +} diff --git a/phpBB/phpbb/install/module/install_data/task/add_languages.php b/phpBB/phpbb/install/module/install_data/task/add_languages.php new file mode 100644 index 0000000000..7ffdf4f276 --- /dev/null +++ b/phpBB/phpbb/install/module/install_data/task/add_languages.php @@ -0,0 +1,121 @@ +<?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\install\module\install_data\task; + +class add_languages extends \phpbb\install\task_base +{ +	/** +	 * @var \phpbb\db\driver\driver_interface +	 */ +	protected $db; + +	/** +	 * @var \phpbb\install\helper\iohandler\iohandler_interface +	 */ +	protected $iohandler; + +	/** +	 * @var \phpbb\language\language_file_helper +	 */ +	protected $language_helper; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\install\helper\iohandler\iohandler_interface	$iohandler			Installer's input-output handler +	 * @param \phpbb\install\helper\container_factory				$container			Installer's DI container +	 * @param \phpbb\language\language_file_helper					$language_helper	Language file helper service +	 */ +	public function __construct(\phpbb\install\helper\iohandler\iohandler_interface $iohandler, +								\phpbb\install\helper\container_factory $container, +								\phpbb\language\language_file_helper $language_helper) +	{ +		$this->db				= $container->get('dbal.conn'); +		$this->iohandler		= $iohandler; +		$this->language_helper	= $language_helper; + +		parent::__construct(true); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		$this->db->sql_return_on_error(true); + +		$languages = $this->language_helper->get_available_languages(); +		$installed_languages = array(); + +		foreach ($languages as $lang_info) +		{ +			$lang_pack = array( +				'lang_iso'			=> $lang_info['iso'], +				'lang_dir'			=> $lang_info['iso'], +				'lang_english_name'	=> htmlspecialchars($lang_info['name']), +				'lang_local_name'	=> htmlspecialchars($lang_info['local_name'], ENT_COMPAT, 'UTF-8'), +				'lang_author'		=> htmlspecialchars($lang_info['author'], ENT_COMPAT, 'UTF-8'), +			); + +			$this->db->sql_query('INSERT INTO ' . LANG_TABLE . ' ' . $this->db->sql_build_array('INSERT', $lang_pack)); + +			$installed_languages[] = (int) $this->db->sql_nextid(); +			if ($this->db->get_sql_error_triggered()) +			{ +				$error = $this->db->sql_error($this->db->get_sql_error_sql()); +				$this->iohandler->add_error_message($error['message']); +			} +		} + +		$sql = 'SELECT * FROM ' . PROFILE_FIELDS_TABLE; +		$result = $this->db->sql_query($sql); + +		$insert_buffer = new \phpbb\db\sql_insert_buffer($this->db, PROFILE_LANG_TABLE); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			foreach ($installed_languages as $lang_id) +			{ +				$insert_buffer->insert(array( +					'field_id'				=> $row['field_id'], +					'lang_id'				=> $lang_id, + +					// Remove phpbb_ from field name +					'lang_name'				=> strtoupper(substr($row['field_name'], 6)), +					'lang_explain'			=> '', +					'lang_default_value'	=> '', +				)); +			} +		} + +		$this->db->sql_freeresult($result); + +		$insert_buffer->flush(); +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 1; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return 'TASK_ADD_LANGUAGES'; +	} +} diff --git a/phpBB/phpbb/install/module/install_data/task/add_modules.php b/phpBB/phpbb/install/module/install_data/task/add_modules.php new file mode 100644 index 0000000000..d21a5be823 --- /dev/null +++ b/phpBB/phpbb/install/module/install_data/task/add_modules.php @@ -0,0 +1,568 @@ +<?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\install\module\install_data\task; + +use phpbb\install\exception\resource_limit_reached_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\container_factory; +use phpbb\install\helper\iohandler\iohandler_interface; + +class add_modules extends \phpbb\install\task_base +{ +	/** +	 * @var config +	 */ +	protected $config; + +	/** +	 * @var \phpbb\db\driver\driver_interface +	 */ +	protected $db; + +	/** +	 * @var \phpbb\extension\manager +	 */ +	protected $extension_manager; + +	/** +	 * @var \phpbb\install\helper\iohandler\iohandler_interface +	 */ +	protected $iohandler; + +	/** +	 * @var \phpbb\module\module_manager +	 */ +	protected $module_manager; + +	/** +	 * Define the module structure so that we can populate the database without +	 * needing to hard-code module_id values +	 * +	 * @var array +	 */ +	protected $module_categories = array( +		'acp' => array( +			'ACP_CAT_GENERAL' => array( +				'ACP_QUICK_ACCESS', +				'ACP_BOARD_CONFIGURATION', +				'ACP_CLIENT_COMMUNICATION', +				'ACP_SERVER_CONFIGURATION', +			), +			'ACP_CAT_FORUMS' => array( +				'ACP_MANAGE_FORUMS', +				'ACP_FORUM_BASED_PERMISSIONS', +			), +			'ACP_CAT_POSTING' => array( +				'ACP_MESSAGES', +				'ACP_ATTACHMENTS', +			), +			'ACP_CAT_USERGROUP' => array( +				'ACP_CAT_USERS', +				'ACP_GROUPS', +				'ACP_USER_SECURITY', +			), +			'ACP_CAT_PERMISSIONS' => array( +				'ACP_GLOBAL_PERMISSIONS', +				'ACP_FORUM_BASED_PERMISSIONS', +				'ACP_PERMISSION_ROLES', +				'ACP_PERMISSION_MASKS', +			), +			'ACP_CAT_CUSTOMISE' => array( +				'ACP_STYLE_MANAGEMENT', +				'ACP_EXTENSION_MANAGEMENT', +				'ACP_LANGUAGE', +			), +			'ACP_CAT_MAINTENANCE' => array( +				'ACP_FORUM_LOGS', +				'ACP_CAT_DATABASE', +			), +			'ACP_CAT_SYSTEM' => array( +				'ACP_AUTOMATION', +				'ACP_GENERAL_TASKS', +				'ACP_MODULE_MANAGEMENT', +			), +			'ACP_CAT_DOT_MODS' => null, +		), +		'mcp' => array( +			'MCP_MAIN'		=> null, +			'MCP_QUEUE'		=> null, +			'MCP_REPORTS'	=> null, +			'MCP_NOTES'		=> null, +			'MCP_WARN'		=> null, +			'MCP_LOGS'		=> null, +			'MCP_BAN'		=> null, +		), +		'ucp' => array( +			'UCP_MAIN'			=> null, +			'UCP_PROFILE'		=> null, +			'UCP_PREFS'			=> null, +			'UCP_PM'			=> null, +			'UCP_USERGROUPS'	=> null, +			'UCP_ZEBRA'			=> null, +		), +	); + +	/** +	 * @var array +	 */ +	protected $module_categories_basenames = array( +		'UCP_PM' => 'ucp_pm', +	); + +	/** +	 * @var array +	 */ +	protected $module_extras = array( +		'acp'	=> array( +			'ACP_QUICK_ACCESS' => array( +				'ACP_MANAGE_USERS', +				'ACP_GROUPS_MANAGE', +				'ACP_MANAGE_FORUMS', +				'ACP_MOD_LOGS', +				'ACP_BOTS', +				'ACP_PHP_INFO', +			), +			'ACP_FORUM_BASED_PERMISSIONS' => array( +				'ACP_FORUM_PERMISSIONS', +				'ACP_FORUM_PERMISSIONS_COPY', +				'ACP_FORUM_MODERATORS', +				'ACP_USERS_FORUM_PERMISSIONS', +				'ACP_GROUPS_FORUM_PERMISSIONS', +			), +		), +	); + +	/** +	 * Constructor +	 * +	 * @parma config				$config		Installer's config +	 * @param iohandler_interface	$iohandler	Installer's input-output handler +	 * @param container_factory		$container	Installer's DI container +	 */ +	public function __construct(config $config, iohandler_interface $iohandler, container_factory $container) +	{ +		$this->config				= $config; +		$this->db					= $container->get('dbal.conn'); +		$this->extension_manager	= $container->get('ext.manager'); +		$this->iohandler			= $iohandler; +		$this->module_manager		= $container->get('module.manager'); + +		parent::__construct(true); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		$this->db->sql_return_on_error(true); + +		$module_classes = array('acp', 'mcp', 'ucp'); +		$total = sizeof($module_classes); +		$i = $this->config->get('module_class_index', 0); +		$module_classes = array_slice($module_classes, $i); + +		foreach ($module_classes as $module_class) +		{ +			$categories = $this->config->get('module_categories_array', array()); + +			$k = $this->config->get('module_categories_index', 0); +			$module_categories = array_slice($this->module_categories[$module_class], $k); +			$timed_out = false; + +			foreach ($module_categories as $cat_name => $subs) +			{ +				// Check if this sub-category has a basename. If it has, use it. +				$basename = (isset($this->module_categories_basenames[$cat_name])) ? $this->module_categories_basenames[$cat_name] : ''; + +				$module_data = array( +					'module_basename'	=> $basename, +					'module_enabled'	=> 1, +					'module_display'	=> 1, +					'parent_id'			=> 0, +					'module_class'		=> $module_class, +					'module_langname'	=> $cat_name, +					'module_mode'		=> '', +					'module_auth'		=> '', +				); + +				$this->module_manager->update_module_data($module_data); + +				// Check for last sql error happened +				if ($this->db->get_sql_error_triggered()) +				{ +					$error = $this->db->sql_error($this->db->get_sql_error_sql()); +					$this->iohandler->add_error_message('INST_ERR_DB', $error['message']); +				} + +				$categories[$cat_name]['id'] = (int) $module_data['module_id']; +				$categories[$cat_name]['parent_id'] = 0; + +				if (is_array($subs)) +				{ +					foreach ($subs as $level2_name) +					{ +						// Check if this sub-category has a basename. If it has, use it. +						$basename = (isset($this->module_categories_basenames[$level2_name])) ? $this->module_categories_basenames[$level2_name] : ''; + +						$module_data = array( +							'module_basename'	=> $basename, +							'module_enabled'	=> 1, +							'module_display'	=> 1, +							'parent_id'			=> (int) $categories[$cat_name]['id'], +							'module_class'		=> $module_class, +							'module_langname'	=> $level2_name, +							'module_mode'		=> '', +							'module_auth'		=> '', +						); + +						$this->module_manager->update_module_data($module_data); + +						// Check for last sql error happened +						if ($this->db->get_sql_error_triggered()) +						{ +							$error = $this->db->sql_error($this->db->get_sql_error_sql()); +							$this->iohandler->add_error_message('INST_ERR_DB', $error['message']); +						} + +						$categories[$level2_name]['id'] = (int) $module_data['module_id']; +						$categories[$level2_name]['parent_id'] = (int) $categories[$cat_name]['id']; +					} +				} + +				$k++; + +				// Stop execution if resource limit is reached +				if ($this->config->get_time_remaining() <= 0 || $this->config->get_memory_remaining() <= 0) +				{ +					$timed_out = true; +					break; +				} +			} + +			$this->config->set('module_categories_array', $categories); +			$this->config->set('module_categories_index', $k); + +			if ($timed_out) +			{ +				throw new resource_limit_reached_exception(); +			} + +			// Get the modules we want to add... returned sorted by name +			$module_info = $this->module_manager->get_module_infos($module_class); + +			$k = $this->config->get('module_info_index', 0); +			$module_info = array_slice($module_info, $k); + +			foreach ($module_info as $module_basename => $fileinfo) +			{ +				foreach ($fileinfo['modes'] as $module_mode => $row) +				{ +					foreach ($row['cat'] as $cat_name) +					{ +						if (!isset($categories[$cat_name])) +						{ +							continue; +						} + +						$module_data = array( +							'module_basename'	=> $module_basename, +							'module_enabled'	=> 1, +							'module_display'	=> (isset($row['display'])) ? (int) $row['display'] : 1, +							'parent_id'			=> (int) $categories[$cat_name]['id'], +							'module_class'		=> $module_class, +							'module_langname'	=> $row['title'], +							'module_mode'		=> $module_mode, +							'module_auth'		=> $row['auth'], +						); + +						$this->module_manager->update_module_data($module_data); + +						// Check for last sql error happened +						if ($this->db->get_sql_error_triggered()) +						{ +							$error = $this->db->sql_error($this->db->get_sql_error_sql()); +							$this->iohandler->add_error_message('INST_ERR_DB', $error['message']); +						} +					} +				} + +				$k++; + +				// Stop execution if resource limit is reached +				if ($this->config->get_time_remaining() <= 0 || $this->config->get_memory_remaining() <= 0) +				{ +					$timed_out = true; +					break; +				} +			} + +			$this->config->set('module_info_index', $k); + +			// Stop execution if resource limit is reached +			if ($timed_out) +			{ +				throw new resource_limit_reached_exception(); +			} + +			// Move some of the modules around since the code above will put them in the wrong place +			if (!$this->config->get('modules_ordered', false)) +			{ +				$this->order_modules($module_class); +				$this->config->set('modules_ordered', true); + +				// Stop execution if resource limit is reached +				if ($this->config->get_time_remaining() <= 0 || $this->config->get_memory_remaining() <= 0) +				{ +					throw new resource_limit_reached_exception(); +				} +			} + +			// And now for the special ones +			// (these are modules which appear in multiple categories and thus get added manually +			// to some for more control) +			if (isset($this->module_extras[$module_class])) +			{ +				$this->add_module_extras($module_class); +			} + +			$this->module_manager->remove_cache_file($module_class); + +			$i++; + +			$this->config->set('module_class_index', $i); +			$this->config->set('module_categories_index', 0); +			$this->config->set('module_info_index', 0); +			$this->config->set('added_extra_modules', false); +			$this->config->set('modules_ordered', false); +			$this->config->set('module_categories_array', array()); + +			// Stop execution if resource limit is reached +			if ($this->config->get_time_remaining() <= 0 || $this->config->get_memory_remaining() <= 0) +			{ +				break; +			} +		} + +		if ($i < $total) +		{ +			throw new resource_limit_reached_exception(); +		} +	} + +	/** +	 * Move modules to their correct place +	 * +	 * @param string	$module_class +	 */ +	protected function order_modules($module_class) +	{ +		if ($module_class == 'acp') +		{ +			// Move main module 4 up... +			$sql = 'SELECT * +				FROM ' . MODULES_TABLE . " +				WHERE module_basename = 'acp_main' +					AND module_class = 'acp' +					AND module_mode = 'main'"; +			$result = $this->db->sql_query($sql); +			$row = $this->db->sql_fetchrow($result); +			$this->db->sql_freeresult($result); + +			$this->module_manager->move_module_by($row, 'acp', 'move_up', 4); + +			// Move permissions intro screen module 4 up... +			$sql = 'SELECT * +				FROM ' . MODULES_TABLE . " +				WHERE module_basename = 'acp_permissions' +					AND module_class = 'acp' +					AND module_mode = 'intro'"; +			$result = $this->db->sql_query($sql); +			$row = $this->db->sql_fetchrow($result); +			$this->db->sql_freeresult($result); + +			$this->module_manager->move_module_by($row, 'acp', 'move_up', 4); + +			// Move manage users screen module 5 up... +			$sql = 'SELECT * +				FROM ' . MODULES_TABLE . " +				WHERE module_basename = 'acp_users' +					AND module_class = 'acp' +					AND module_mode = 'overview'"; +			$result = $this->db->sql_query($sql); +			$row = $this->db->sql_fetchrow($result); +			$this->db->sql_freeresult($result); + +			$this->module_manager->move_module_by($row, 'acp', 'move_up', 5); + +			// Move extension management module 1 up... +			$sql = 'SELECT * +				FROM ' . MODULES_TABLE . " +				WHERE module_langname = 'ACP_EXTENSION_MANAGEMENT' +					AND module_class = 'acp' +					AND module_mode = '' +					AND module_basename = ''"; +			$result = $this->db->sql_query($sql); +			$row = $this->db->sql_fetchrow($result); +			$this->db->sql_freeresult($result); + +			$this->module_manager->move_module_by($row, 'acp', 'move_up', 1); +		} + +		if ($module_class == 'mcp') +		{ +			// Move pm report details module 3 down... +			$sql = 'SELECT * +				FROM ' . MODULES_TABLE . " +				WHERE module_basename = 'mcp_pm_reports' +					AND module_class = 'mcp' +					AND module_mode = 'pm_report_details'"; +			$result = $this->db->sql_query($sql); +			$row = $this->db->sql_fetchrow($result); +			$this->db->sql_freeresult($result); + +			$this->module_manager->move_module_by($row, 'mcp', 'move_down', 3); + +			// Move closed pm reports module 3 down... +			$sql = 'SELECT * +				FROM ' . MODULES_TABLE . " +				WHERE module_basename = 'mcp_pm_reports' +					AND module_class = 'mcp' +					AND module_mode = 'pm_reports_closed'"; +			$result = $this->db->sql_query($sql); +			$row = $this->db->sql_fetchrow($result); +			$this->db->sql_freeresult($result); + +			$this->module_manager->move_module_by($row, 'mcp', 'move_down', 3); + +			// Move open pm reports module 3 down... +			$sql = 'SELECT * +				FROM ' . MODULES_TABLE . " +				WHERE module_basename = 'mcp_pm_reports' +					AND module_class = 'mcp' +					AND module_mode = 'pm_reports'"; +			$result = $this->db->sql_query($sql); +			$row = $this->db->sql_fetchrow($result); +			$this->db->sql_freeresult($result); + +			$this->module_manager->move_module_by($row, 'mcp', 'move_down', 3); +		} + +		if ($module_class == 'ucp') +		{ +			// Move attachment module 4 down... +			$sql = 'SELECT * +				FROM ' . MODULES_TABLE . " +				WHERE module_basename = 'ucp_attachments' +					AND module_class = 'ucp' +					AND module_mode = 'attachments'"; +			$result = $this->db->sql_query($sql); +			$row = $this->db->sql_fetchrow($result); +			$this->db->sql_freeresult($result); + +			$this->module_manager->move_module_by($row, 'ucp', 'move_down', 4); + +			// Move notification options module 4 down... +			$sql = 'SELECT * +				FROM ' . MODULES_TABLE . " +				WHERE module_basename = 'ucp_notifications' +					AND module_class = 'ucp' +					AND module_mode = 'notification_options'"; +			$result = $this->db->sql_query($sql); +			$row = $this->db->sql_fetchrow($result); +			$this->db->sql_freeresult($result); + +			$this->module_manager->move_module_by($row, 'ucp', 'move_down', 4); + +			// Move OAuth module 5 down... +			$sql = 'SELECT * +				FROM ' . MODULES_TABLE . " +				WHERE module_basename = 'ucp_auth_link' +					AND module_class = 'ucp' +					AND module_mode = 'auth_link'"; +			$result = $this->db->sql_query($sql); +			$row = $this->db->sql_fetchrow($result); +			$this->db->sql_freeresult($result); + +			$this->module_manager->move_module_by($row, 'ucp', 'move_down', 5); +		} +	} + +	/** +	 * Add extra modules +	 * +	 * @param string	$module_class +	 */ +	protected function add_module_extras($module_class) +	{ +		foreach ($this->module_extras[$module_class] as $cat_name => $mods) +		{ +			$sql = 'SELECT module_id, left_id, right_id +				FROM ' . MODULES_TABLE . " +				WHERE module_langname = '" . $this->db->sql_escape($cat_name) . "' +					AND module_class = '" . $this->db->sql_escape($module_class) . "'"; +			$result = $this->db->sql_query_limit($sql, 1); +			$row2 = $this->db->sql_fetchrow($result); +			$this->db->sql_freeresult($result); + +			foreach ($mods as $mod_name) +			{ +				$sql = 'SELECT * +					FROM ' . MODULES_TABLE . " +					WHERE module_langname = '" . $this->db->sql_escape($mod_name) . "' +						AND module_class = '" . $this->db->sql_escape($module_class) . "' +						AND module_basename <> ''"; +				$result = $this->db->sql_query_limit($sql, 1); +				$row = $this->db->sql_fetchrow($result); +				$this->db->sql_freeresult($result); + +				$module_data = array( +					'module_basename'	=> $row['module_basename'], +					'module_enabled'	=> (int) $row['module_enabled'], +					'module_display'	=> (int) $row['module_display'], +					'parent_id'			=> (int) $row2['module_id'], +					'module_class'		=> $row['module_class'], +					'module_langname'	=> $row['module_langname'], +					'module_mode'		=> $row['module_mode'], +					'module_auth'		=> $row['module_auth'], +				); + +				$this->module_manager->update_module_data($module_data); + +				// Check for last sql error happened +				if ($this->db->get_sql_error_triggered()) +				{ +					$error = $this->db->sql_error($this->db->get_sql_error_sql()); +					$this->iohandler->add_error_message('INST_ERR_DB', $error['message']); +				} +			} +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 1; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return 'TASK_ADD_MODULES'; +	} +} diff --git a/phpBB/phpbb/install/module/install_database/module.php b/phpBB/phpbb/install/module/install_database/module.php new file mode 100644 index 0000000000..0d8b33087f --- /dev/null +++ b/phpBB/phpbb/install/module/install_database/module.php @@ -0,0 +1,28 @@ +<?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\install\module\install_database; + +/** + * Installer module for database installation + */ +class module extends \phpbb\install\module_base +{ +	/** +	 * {@inheritdoc} +	 */ +	public function get_navigation_stage_path() +	{ +		return array('install', 0, 'install'); +	} +} diff --git a/phpBB/phpbb/install/module/install_database/task/add_config_settings.php b/phpBB/phpbb/install/module/install_database/task/add_config_settings.php new file mode 100644 index 0000000000..8002e3ed97 --- /dev/null +++ b/phpBB/phpbb/install/module/install_database/task/add_config_settings.php @@ -0,0 +1,372 @@ +<?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\install\module\install_database\task; + +use phpbb\install\exception\resource_limit_reached_exception; + +/** + * Create database schema + */ +class add_config_settings extends \phpbb\install\task_base +{ +	/** +	 * @var \phpbb\db\driver\driver_interface +	 */ +	protected $db; + +	/** +	 * @var \phpbb\filesystem\filesystem_interface +	 */ +	protected $filesystem; + +	/** +	 * @var \phpbb\install\helper\config +	 */ +	protected $install_config; + +	/** +	 * @var \phpbb\install\helper\iohandler\iohandler_interface +	 */ +	protected $iohandler; + +	/** +	 * @var \phpbb\language\language +	 */ +	protected $language; + +	/** +	 * @var \phpbb\passwords\manager +	 */ +	protected $password_manager; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * @var string +	 */ +	protected $config_table; + +	/** +	 * @var string +	 */ +	protected $user_table; + +	/** +	 * @var string +	 */ +	protected $topics_table; + +	/** +	 * @var string +	 */ +	protected $forums_table; + +	/** +	 * @var string +	 */ +	protected $posts_table; + +	/** +	 * @var string +	 */ +	protected $moderator_cache_table; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\filesystem\filesystem_interface				$filesystem			Filesystem service +	 * @param \phpbb\install\helper\config							$install_config		Installer's config helper +	 * @param \phpbb\install\helper\iohandler\iohandler_interface	$iohandler			Installer's input-output handler +	 * @param \phpbb\install\helper\container_factory				$container			Installer's DI container +	 * @param \phpbb\language\language								$language			Language service +	 * @param string												$phpbb_root_path	Path to phpBB's root +	 */ +	public function __construct(\phpbb\filesystem\filesystem_interface $filesystem, +								\phpbb\install\helper\config $install_config, +								\phpbb\install\helper\iohandler\iohandler_interface $iohandler, +								\phpbb\install\helper\container_factory $container, +								\phpbb\language\language $language, +								$phpbb_root_path) +	{ +		$this->db				= $container->get('dbal.conn'); +		$this->filesystem		= $filesystem; +		$this->install_config	= $install_config; +		$this->iohandler		= $iohandler; +		$this->language			= $language; +		$this->password_manager	= $container->get('passwords.manager'); +		$this->phpbb_root_path	= $phpbb_root_path; + +		// Table names +		$this->config_table				= $container->get_parameter('tables.config'); +		$this->forums_table				= $container->get_parameter('tables.forums'); +		$this->topics_table				= $container->get_parameter('tables.topics'); +		$this->user_table				= $container->get_parameter('tables.users'); +		$this->moderator_cache_table	= $container->get_parameter('tables.moderator_cache'); +		$this->posts_table				= $container->get_parameter('tables.posts'); + +		parent::__construct(true); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		$this->db->sql_return_on_error(true); + +		$server_name	= $this->install_config->get('server_name'); +		$current_time 	= time(); +		$user_ip		= phpbb_ip_normalise($this->iohandler->get_server_variable('REMOTE_ADDR')); +		$user_ip		= ($user_ip === false) ? '' : $user_ip; +		$referer		= $this->iohandler->get_server_variable('REFERER'); + +		// Calculate cookie domain +		$cookie_domain = $server_name; + +		if (strpos($cookie_domain, 'www.') === 0) +		{ +			$cookie_domain = substr($cookie_domain, 3); +		} + +		// Set default config and post data, this applies to all DB's +		$sql_ary = array( +			'INSERT INTO ' . $this->config_table . " (config_name, config_value) +				VALUES ('board_startdate', '$current_time')", + +			'INSERT INTO ' . $this->config_table . " (config_name, config_value) +				VALUES ('default_lang', '" . $this->db->sql_escape($this->install_config->get('default_lang')) . "')", + +			'UPDATE ' . $this->config_table . " +				SET config_value = '" . $this->db->sql_escape($this->install_config->get('img_imagick')) . "' +				WHERE config_name = 'img_imagick'", + +			'UPDATE ' . $this->config_table . " +				SET config_value = '" . $this->db->sql_escape($this->install_config->get('server_name')) . "' +				WHERE config_name = 'server_name'", + +			'UPDATE ' . $this->config_table . " +				SET config_value = '" . $this->db->sql_escape($this->install_config->get('server_port')) . "' +				WHERE config_name = 'server_port'", + +			'UPDATE ' . $this->config_table . " +				SET config_value = '" . $this->db->sql_escape($this->install_config->get('board_email')) . "' +				WHERE config_name = 'board_email'", + +			'UPDATE ' . $this->config_table . " +				SET config_value = '" . $this->db->sql_escape($this->install_config->get('board_email')) . "' +				WHERE config_name = 'board_contact'", + +			'UPDATE ' . $this->config_table . " +				SET config_value = '" . $this->db->sql_escape($cookie_domain) . "' +				WHERE config_name = 'cookie_domain'", + +			'UPDATE ' . $this->config_table . " +				SET config_value = '" . $this->db->sql_escape($this->language->lang('default_dateformat')) . "' +				WHERE config_name = 'default_dateformat'", + +			'UPDATE ' . $this->config_table . " +				SET config_value = '" . $this->db->sql_escape($this->install_config->get('email_enable')) . "' +				WHERE config_name = 'email_enable'", + +			'UPDATE ' . $this->config_table . " +				SET config_value = '" . $this->db->sql_escape($this->install_config->get('smtp_delivery')) . "' +				WHERE config_name = 'smtp_delivery'", + +			'UPDATE ' . $this->config_table . " +				SET config_value = '" . $this->db->sql_escape($this->install_config->get('smtp_host')) . "' +				WHERE config_name = 'smtp_host'", + +			'UPDATE ' . $this->config_table . " +				SET config_value = '" . $this->db->sql_escape($this->install_config->get('smtp_port')) . "' +				WHERE config_name = 'smtp_port'", + +			'UPDATE ' . $this->config_table . " +				SET config_value = '" . $this->db->sql_escape($this->install_config->get('smtp_auth')) . "' +				WHERE config_name = 'smtp_auth_method'", + +			'UPDATE ' . $this->config_table . " +				SET config_value = '" . $this->db->sql_escape($this->install_config->get('smtp_user')) . "' +				WHERE config_name = 'smtp_username'", + +			'UPDATE ' . $this->config_table . " +				SET config_value = '" . $this->db->sql_escape($this->install_config->get('smtp_pass')) . "' +				WHERE config_name = 'smtp_password'", + +			'UPDATE ' . $this->config_table . " +				SET config_value = '" . $this->db->sql_escape($this->install_config->get('cookie_secure')) . "' +				WHERE config_name = 'cookie_secure'", + +			'UPDATE ' . $this->config_table . " +				SET config_value = '" . $this->db->sql_escape($this->install_config->get('force_server_vars')) . "' +				WHERE config_name = 'force_server_vars'", + +			'UPDATE ' . $this->config_table . " +				SET config_value = '" . $this->db->sql_escape($this->install_config->get('script_path')) . "' +				WHERE config_name = 'script_path'", + +			'UPDATE ' . $this->config_table . " +				SET config_value = '" . $this->db->sql_escape($this->install_config->get('server_protocol')) . "' +				WHERE config_name = 'server_protocol'", + +			'UPDATE ' . $this->config_table . " +				SET config_value = '" . $this->db->sql_escape($this->install_config->get('admin_name')) . "' +				WHERE config_name = 'newest_username'", + +			'UPDATE ' . $this->config_table . " +				SET config_value = '" . md5(mt_rand()) . "' +				WHERE config_name = 'avatar_salt'", + +			'UPDATE ' . $this->config_table . " +				SET config_value = '" . md5(mt_rand()) . "' +				WHERE config_name = 'plupload_salt'", + +			'UPDATE ' . $this->config_table . " +				SET config_value = '" . $this->db->sql_escape($this->install_config->get('board_name')) . "' +				WHERE config_name = 'sitename'", + +			'UPDATE ' . $this->config_table . " +				SET config_value = '" . $this->db->sql_escape($this->install_config->get('board_description')) . "' +				WHERE config_name = 'site_desc'", + +			'UPDATE ' . $this->user_table . " +				SET username = '" . $this->db->sql_escape($this->install_config->get('admin_name')) . "', +					user_password='" . $this->password_manager->hash($this->install_config->get('admin_passwd')) . "', +					user_ip = '" . $this->db->sql_escape($user_ip) . "', +					user_lang = '" . $this->db->sql_escape($this->install_config->get('user_language', 'en')) . "', +					user_email='" . $this->db->sql_escape($this->install_config->get('board_email')) . "', +					user_dateformat='" . $this->db->sql_escape($this->language->lang('default_dateformat')) . "', +					user_email_hash = " . $this->db->sql_escape(phpbb_email_hash($this->install_config->get('board_email'))) . ", +					username_clean = '" . $this->db->sql_escape(utf8_clean_string($this->install_config->get('admin_name'))) . "' +				WHERE username = 'Admin'", + +			'UPDATE ' . $this->moderator_cache_table . " +				SET username = '" . $this->db->sql_escape($this->install_config->get('admin_name')) . "' +				WHERE username = 'Admin'", + +			'UPDATE ' . $this->forums_table . " +				SET forum_last_poster_name = '" . $this->db->sql_escape($this->install_config->get('admin_name')) . "' +				WHERE forum_last_poster_name = 'Admin'", + +			'UPDATE ' . $this->topics_table . " +				SET topic_first_poster_name = '" . $this->db->sql_escape($this->install_config->get('admin_name')) . "', +				topic_last_poster_name = '" . $this->db->sql_escape($this->install_config->get('admin_name')) . "' +				WHERE topic_first_poster_name = 'Admin' +					OR topic_last_poster_name = 'Admin'", + +			'UPDATE ' . $this->user_table . " +				SET user_regdate = $current_time", + +			'UPDATE ' . $this->posts_table . " +				SET post_time = $current_time, poster_ip = '" . $this->db->sql_escape($user_ip) . "'", + +			'UPDATE ' . $this->topics_table . " +				SET topic_time = $current_time, topic_last_post_time = $current_time", + +			'UPDATE ' . $this->forums_table . " +				SET forum_last_post_time = $current_time", + +			'UPDATE ' . $this->config_table . " +				SET config_value = '" . $this->db->sql_escape($this->db->sql_server_info(true)) . "' +				WHERE config_name = 'dbms_version'", +		); + +		if (@extension_loaded('gd')) +		{ +			$sql_ary[] = 'UPDATE ' . $this->config_table . " +				SET config_value = 'core.captcha.plugins.gd' +				WHERE config_name = 'captcha_plugin'"; + +			$sql_ary[] = 'UPDATE ' . $this->config_table . " +				SET config_value = '1' +				WHERE config_name = 'captcha_gd'"; +		} + +		$ref = substr($referer, strpos($referer, '://') + 3); +		if (!(stripos($ref, $server_name) === 0)) +		{ +			$sql_ary[] = 'UPDATE ' . $this->config_table . " +				SET config_value = '0' +				WHERE config_name = 'referer_validation'"; +		} + +		// We set a (semi-)unique cookie name to bypass login issues related to the cookie name. +		$cookie_name = 'phpbb3_'; +		$rand_str = md5(mt_rand()); +		$rand_str = str_replace('0', 'z', base_convert($rand_str, 16, 35)); +		$rand_str = substr($rand_str, 0, 5); +		$cookie_name .= strtolower($rand_str); + +		$sql_ary[] = 'UPDATE ' . $this->config_table . " +			SET config_value = '" . $this->db->sql_escape($cookie_name) . "' +			WHERE config_name = 'cookie_name'"; + +		// Disable avatars if upload directory is not writable +		if (!$this->filesystem->is_writable($this->phpbb_root_path . 'images/avatars/upload/')) +		{ +			$sql_ary[] = 'UPDATE ' . $this->config_table . " +				SET config_value = '0' +				WHERE config_name = 'allow_avatar'"; + +			$sql_ary[] = 'UPDATE ' . $this->config_table . " +				SET config_value = '0' +				WHERE config_name = 'allow_avatar_upload'"; +		} + +		$i = $this->install_config->get('add_config_settings_index', 0); +		$total = sizeof($sql_ary); +		$sql_ary = array_slice($sql_ary, $i); + +		foreach ($sql_ary as $sql) +		{ +			if (!$this->db->sql_query($sql)) +			{ +				$error = $this->db->sql_error($this->db->get_sql_error_sql()); +				$this->iohandler->add_error_message('INST_ERR_DB', $error['message']); +			} + +			$i++; + +			// Stop execution if resource limit is reached +			if ($this->install_config->get_time_remaining() <= 0 || $this->install_config->get_memory_remaining() <= 0) +			{ +				break; +			} +		} + +		if ($i < $total) +		{ +			$this->install_config->set('add_config_settings_index', $i); +			throw new resource_limit_reached_exception(); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 1; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return 'TASK_ADD_CONFIG_SETTINGS'; +	} +} diff --git a/phpBB/phpbb/install/module/install_database/task/add_default_data.php b/phpBB/phpbb/install/module/install_database/task/add_default_data.php new file mode 100644 index 0000000000..f5157637ee --- /dev/null +++ b/phpBB/phpbb/install/module/install_database/task/add_default_data.php @@ -0,0 +1,184 @@ +<?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\install\module\install_database\task; + +use phpbb\install\exception\resource_limit_reached_exception; + +/** + * Create database schema + */ +class add_default_data extends \phpbb\install\task_base +{ +	/** +	 * @var \phpbb\db\driver\driver_interface +	 */ +	protected $db; + +	/** +	 * @var \phpbb\install\helper\database +	 */ +	protected $database_helper; + +	/** +	 * @var \phpbb\install\helper\config +	 */ +	protected $config; + +	/** +	 * @var \phpbb\install\helper\iohandler\iohandler_interface +	 */ +	protected $iohandler; + +	/** +	 * @var \phpbb\language\language +	 */ +	protected $language; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\install\helper\database						$db_helper	Installer's database helper +	 * @param \phpbb\install\helper\config							$config		Installer config +	 * @param \phpbb\install\helper\iohandler\iohandler_interface	$iohandler	Installer's input-output handler +	 * @param \phpbb\install\helper\container_factory				$container	Installer's DI container +	 * @param \phpbb\language\language								$language	Language service +	 * @param string												$root_path	Root path of phpBB +	 */ +	public function __construct(\phpbb\install\helper\database $db_helper, +								\phpbb\install\helper\config $config, +								\phpbb\install\helper\iohandler\iohandler_interface $iohandler, +								\phpbb\install\helper\container_factory $container, +								\phpbb\language\language $language, +								$root_path) +	{ +		$this->db				= $container->get('dbal.conn.driver'); +		$this->database_helper	= $db_helper; +		$this->config			= $config; +		$this->iohandler		= $iohandler; +		$this->language			= $language; +		$this->phpbb_root_path	= $root_path; + +		parent::__construct(true); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		$this->db->sql_return_on_error(true); + +		$table_prefix = $this->config->get('table_prefix'); +		$dbms = $this->config->get('dbms'); +		$dbms_info = $this->database_helper->get_available_dbms($dbms); + +		// Get schema data from file +		$sql_query = @file_get_contents($this->phpbb_root_path . 'install/schemas/schema_data.sql'); + +		// Clean up SQL +		$sql_query = $this->replace_dbms_specific_sql($sql_query); +		$sql_query = preg_replace('# phpbb_([^\s]*) #i', ' ' . $table_prefix . '\1 ', $sql_query); +		$sql_query = preg_replace_callback('#\{L_([A-Z0-9\-_]*)\}#s', array($this, 'lang_replace_callback'), $sql_query); +		$sql_query = $this->database_helper->remove_comments($sql_query); +		$sql_query = $this->database_helper->split_sql_file($sql_query, $dbms_info[$dbms]['DELIM']); + +		$i = $this->config->get('add_default_data_index', 0); +		$total = sizeof($sql_query); +		$sql_query = array_slice($sql_query, $i); + +		foreach ($sql_query as $sql) +		{ +			if (!$this->db->sql_query($sql)) +			{ +				$error = $this->db->sql_error($this->db->get_sql_error_sql()); +				$this->iohandler->add_error_message('INST_ERR_DB', $error['message']); +			} + +			$i++; + +			// Stop execution if resource limit is reached +			if ($this->config->get_time_remaining() <= 0 || $this->config->get_memory_remaining() <= 0) +			{ +				break; +			} +		} + +		$this->config->set('add_default_data_index', $i); + +		if ($i < $total) +		{ +			throw new resource_limit_reached_exception(); +		} +	} + +	/** +	 * Process DB specific SQL +	 * +	 * @return string +	 */ +	protected function replace_dbms_specific_sql($query) +	{ +		if ($this->db instanceof \phpbb\db\driver\mssql_base || $this->db instanceof \phpbb\db\driver\mssql) +		{ +			$query = preg_replace('#\# MSSQL IDENTITY (phpbb_[a-z_]+) (ON|OFF) \##s', 'SET IDENTITY_INSERT \1 \2;', $query); +		} +		else if ($this->db instanceof \phpbb\db\driver\postgres) +		{ +			$query = preg_replace('#\# POSTGRES (BEGIN|COMMIT) \##s', '\1; ', $query); +		} +		else if ($this->db instanceof \phpbb\db\driver\mysql_base) +		{ +			$query = str_replace('\\', '\\\\', $query); +		} + +		return $query; +	} + +	/** +	 * Callback function for language replacing +	 * +	 * @param array	$matches +	 * @return string +	 */ +	public function lang_replace_callback($matches) +	{ +		if (!empty($matches[1])) +		{ +			return $this->db->sql_escape($this->language->lang($matches[1])); +		} + +		return ''; +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 1; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return 'TASK_ADD_DEFAULT_DATA'; +	} +} diff --git a/phpBB/phpbb/install/module/install_database/task/add_tables.php b/phpBB/phpbb/install/module/install_database/task/add_tables.php new file mode 100644 index 0000000000..f344f91582 --- /dev/null +++ b/phpBB/phpbb/install/module/install_database/task/add_tables.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\install\module\install_database\task; + +use phpbb\install\exception\resource_limit_reached_exception; + +/** + * Create tables + */ +class add_tables extends \phpbb\install\task_base +{ +	/** +	 * @var \phpbb\install\helper\config +	 */ +	protected $config; + +	/** +	 * @var \phpbb\db\driver\driver_interface +	 */ +	protected $db; + +	/** +	 * @var \phpbb\db\tools\tools_interface +	 */ +	protected $db_tools; + +	/** +	 * @var \phpbb\filesystem\filesystem_interface +	 */ +	protected $filesystem; + +	/** +	 * @var string +	 */ +	protected $schema_file_path; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\install\helper\config				$config +	 * @param \phpbb\install\helper\database			$db_helper +	 * @param \phpbb\filesystem\filesystem_interface	$filesystem +	 * @param string									$phpbb_root_path +	 */ +	public function __construct(\phpbb\install\helper\config $config, +								\phpbb\install\helper\database $db_helper, +								\phpbb\filesystem\filesystem_interface $filesystem, +								$phpbb_root_path) +	{ +		$dbms = $db_helper->get_available_dbms($config->get('dbms')); +		$dbms = $dbms[$config->get('dbms')]['DRIVER']; +		$factory = new \phpbb\db\tools\factory(); + +		$this->db				= new $dbms(); +		$this->db->sql_connect( +			$config->get('dbhost'), +			$config->get('dbuser'), +			$config->get('dbpasswd'), +			$config->get('dbname'), +			$config->get('dbport'), +			false, +			false +		); + +		$this->config			= $config; +		$this->db_tools			= $factory->get($this->db); +		$this->filesystem		= $filesystem; +		$this->schema_file_path	= $phpbb_root_path . 'store/schema.json'; + +		parent::__construct(true); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		$this->db->sql_return_on_error(true); + +		$table_prefix = $this->config->get('table_prefix'); +		$change_prefix = $this->config->get('change_table_prefix', true); + +		if (!defined('CONFIG_TABLE')) +		{ +			// CONFIG_TABLE is required by sql_create_index() to check the +			// length of index names. However table_prefix is not defined +			// here yet, so we need to create the constant ourselves. +			define('CONFIG_TABLE', $table_prefix . 'config'); +		} + +		$db_table_schema = @file_get_contents($this->schema_file_path); +		$db_table_schema = json_decode($db_table_schema, true); +		$total = sizeof($db_table_schema); +		$i = $this->config->get('add_table_index', 0); +		$db_table_schema = array_slice($db_table_schema, $i); + +		foreach ($db_table_schema as $table_name => $table_data) +		{ +			$i++; + +			$this->db_tools->sql_create_table( +				( ($change_prefix) ? ($table_prefix . substr($table_name, 6)) : $table_name ), +				$table_data +			); + +			// Stop execution if resource limit is reached +			if ($this->config->get_time_remaining() <= 0 || $this->config->get_memory_remaining() <= 0) +			{ +				break; +			} +		} + +		$this->config->set('add_table_index', $i); + +		if ($i < $total) +		{ +			throw new resource_limit_reached_exception(); +		} +		else +		{ +			@unlink($this->schema_file_path); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 1; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return 'TASK_CREATE_TABLES'; +	} +} diff --git a/phpBB/phpbb/install/module/install_database/task/create_schema.php b/phpBB/phpbb/install/module/install_database/task/create_schema.php new file mode 100644 index 0000000000..a5635d5dbe --- /dev/null +++ b/phpBB/phpbb/install/module/install_database/task/create_schema.php @@ -0,0 +1,234 @@ +<?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\install\module\install_database\task; + +use phpbb\install\exception\resource_limit_reached_exception; + +/** + * Create database schema + */ +class create_schema extends \phpbb\install\task_base +{ +	/** +	 * @var \phpbb\install\helper\config +	 */ +	protected $config; + +	/** +	 * @var \phpbb\db\driver\driver_interface +	 */ +	protected $db; + +	/** +	 * @var \phpbb\db\tools\tools_interface +	 */ +	protected $db_tools; + +	/** +	 * @var \phpbb\install\helper\database +	 */ +	protected $database_helper; + +	/** +	 * @var \phpbb\filesystem\filesystem_interface +	 */ +	protected $filesystem; + +	/** +	 * @var \phpbb\install\helper\iohandler\iohandler_interface +	 */ +	protected $iohandler; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * @var string +	 */ +	protected $php_ext; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\install\helper\config							$config				Installer's config provider +	 * @param \phpbb\install\helper\database						$db_helper			Installer's database helper +	 * @param \phpbb\filesystem\filesystem_interface				$filesystem			Filesystem service +	 * @param \phpbb\install\helper\iohandler\iohandler_interface	$iohandler			Installer's input-output handler +	 * @param string												$phpbb_root_path	Path phpBB's root +	 * @param string												$php_ext			Extension of PHP files +	 */ +	public function __construct(\phpbb\install\helper\config $config, +								\phpbb\install\helper\database $db_helper, +								\phpbb\filesystem\filesystem_interface $filesystem, +								\phpbb\install\helper\iohandler\iohandler_interface $iohandler, +								$phpbb_root_path, +								$php_ext) +	{ +		$dbms = $db_helper->get_available_dbms($config->get('dbms')); +		$dbms = $dbms[$config->get('dbms')]['DRIVER']; +		$factory = new \phpbb\db\tools\factory(); + +		$this->db				= new $dbms(); +		$this->db->sql_connect( +			$config->get('dbhost'), +			$config->get('dbuser'), +			$config->get('dbpasswd'), +			$config->get('dbname'), +			$config->get('dbport'), +			false, +			false +		); + +		$this->config			= $config; +		$this->db_tools			= $factory->get($this->db); +		$this->database_helper	= $db_helper; +		$this->filesystem		= $filesystem; +		$this->iohandler		= $iohandler; +		$this->phpbb_root_path	= $phpbb_root_path; +		$this->php_ext			= $php_ext; + +		parent::__construct(true); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		// As this task may take a large amount of time to complete refreshing the page might be necessary for some +		// server configurations with limited resources +		if (!$this->config->get('pre_schema_forced_refresh')) +		{ +			if ($this->config->get_time_remaining() < 5) +			{ +				$this->config->set('pre_schema_forced_refresh', true); +				throw new resource_limit_reached_exception(); +			} +		} + +		$this->db->sql_return_on_error(true); + +		$dbms = $this->config->get('dbms'); +		$dbms_info = $this->database_helper->get_available_dbms($dbms); +		$schema_name = $dbms_info[$dbms]['SCHEMA']; +		$delimiter = $dbms_info[$dbms]['DELIM']; +		$table_prefix = $this->config->get('table_prefix'); + +		if ($dbms === 'mysql') +		{ +			if (version_compare($this->db->sql_server_info(true), '4.1.3', '>=')) +			{ +				$schema_name .= '_41'; +			} +			else +			{ +				$schema_name .= '_40'; +			} +		} + +		$db_schema_path = $this->phpbb_root_path . 'install/schemas/' . $schema_name . '_schema.sql'; + +		// Load database vendor specific code if there is any +		if ($this->filesystem->exists($db_schema_path)) +		{ +			$sql_query = @file_get_contents($db_schema_path); +			$sql_query = preg_replace('#phpbb_#i', $table_prefix, $sql_query); +			$sql_query = $this->database_helper->remove_comments($sql_query); +			$sql_query = $this->database_helper->split_sql_file($sql_query, $delimiter); + +			foreach ($sql_query as $sql) +			{ +				if (!$this->db->sql_query($sql)) +				{ +					$error = $this->db->sql_error($this->db->get_sql_error_sql()); +					$this->iohandler->add_error_message('INST_ERR_DB', $error['message']); +				} +			} + +			unset($sql_query); +		} + +		$change_prefix = false; + +		// Generate database schema +		if ($this->filesystem->exists($this->phpbb_root_path . 'install/schemas/schema.json')) +		{ +			$db_table_schema = @file_get_contents($this->phpbb_root_path . 'install/schemas/schema.json'); +			$db_table_schema = json_decode($db_table_schema, true); +			$change_prefix = true; +		} +		else +		{ +			global $table_prefix; + +			$table_prefix = $this->config->get('table_prefix'); + +			if (!defined('CONFIG_TABLE')) +			{ +				// We need to include the constants file for the table constants +				// when we generate the schema from the migration files. +				include ($this->phpbb_root_path . 'includes/constants.' . $this->php_ext); +			} + +			$finder = new \phpbb\finder($this->filesystem, $this->phpbb_root_path, null, $this->php_ext); +			$migrator_classes = $finder->core_path('phpbb/db/migration/data/')->get_classes(); +			$factory = new \phpbb\db\tools\factory(); +			$db_tools = $factory->get($this->db, true); +			$schema_generator = new \phpbb\db\migration\schema_generator( +				$migrator_classes, +				new \phpbb\config\config(array()), +				$this->db, +				$db_tools, +				$this->phpbb_root_path, +				$this->php_ext, +				$table_prefix +			); +			$db_table_schema = $schema_generator->get_schema(); +		} + +		if (!defined('CONFIG_TABLE')) +		{ +			// CONFIG_TABLE is required by sql_create_index() to check the +			// length of index names. However table_prefix is not defined +			// here yet, so we need to create the constant ourselves. +			define('CONFIG_TABLE', $table_prefix . 'config'); +		} + +		foreach ($db_table_schema as $table_name => $table_data) +		{ +			$this->db_tools->sql_create_table( +				( ($change_prefix) ? ($table_prefix . substr($table_name, 6)) : $table_name ), +				$table_data +			); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 1; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return 'TASK_CREATE_DATABASE_SCHEMA'; +	} +} diff --git a/phpBB/phpbb/install/module/install_database/task/create_schema_file.php b/phpBB/phpbb/install/module/install_database/task/create_schema_file.php new file mode 100644 index 0000000000..b6d6ece17f --- /dev/null +++ b/phpBB/phpbb/install/module/install_database/task/create_schema_file.php @@ -0,0 +1,164 @@ +<?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\install\module\install_database\task; + +use phpbb\install\exception\resource_limit_reached_exception; + +/** + * Create database schema + */ +class create_schema_file extends \phpbb\install\task_base +{ +	/** +	 * @var \phpbb\install\helper\config +	 */ +	protected $config; + +	/** +	 * @var \phpbb\db\driver\driver_interface +	 */ +	protected $db; + +	/** +	 * @var \phpbb\filesystem\filesystem_interface +	 */ +	protected $filesystem; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * @var string +	 */ +	protected $php_ext; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\install\helper\config							$config				Installer's config provider +	 * @param \phpbb\install\helper\database						$db_helper			Installer's database helper +	 * @param \phpbb\filesystem\filesystem_interface				$filesystem			Filesystem service +	 * @param string												$phpbb_root_path	Path phpBB's root +	 * @param string												$php_ext			Extension of PHP files +	 */ +	public function __construct(\phpbb\install\helper\config $config, +								\phpbb\install\helper\database $db_helper, +								\phpbb\filesystem\filesystem_interface $filesystem, +								$phpbb_root_path, +								$php_ext) +	{ +		$dbms = $db_helper->get_available_dbms($config->get('dbms')); +		$dbms = $dbms[$config->get('dbms')]['DRIVER']; + +		$this->db				= new $dbms(); +		$this->db->sql_connect( +			$config->get('dbhost'), +			$config->get('dbuser'), +			$config->get('dbpasswd'), +			$config->get('dbname'), +			$config->get('dbport'), +			false, +			false +		); + +		$this->config			= $config; +		$this->filesystem		= $filesystem; +		$this->phpbb_root_path	= $phpbb_root_path; +		$this->php_ext			= $php_ext; + +		parent::__construct(true); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		// Generate database schema +		if ($this->filesystem->exists($this->phpbb_root_path . 'install/schemas/schema.json')) +		{ +			$db_table_schema = @file_get_contents($this->phpbb_root_path . 'install/schemas/schema.json'); +			$this->config->set('change_table_prefix', true); +		} +		else +		{ +			global $table_prefix; + +			// As this task may take a large amount of time to complete refreshing the page might be necessary for some +			// server configurations with limited resources +			if (!$this->config->get('pre_schema_forced_refresh', false)) +			{ +				if ($this->config->get_time_remaining() < 5) +				{ +					$this->config->set('pre_schema_forced_refresh', true); +					throw new resource_limit_reached_exception(); +				} +			} + +			$table_prefix = $this->config->get('table_prefix'); + +			if (!defined('CONFIG_TABLE')) +			{ +				// We need to include the constants file for the table constants +				// when we generate the schema from the migration files. +				include ($this->phpbb_root_path . 'includes/constants.' . $this->php_ext); +			} + +			$finder = new \phpbb\finder($this->filesystem, $this->phpbb_root_path, null, $this->php_ext); +			$migrator_classes = $finder->core_path('phpbb/db/migration/data/')->get_classes(); +			$factory = new \phpbb\db\tools\factory(); +			$db_tools = $factory->get($this->db, true); +			$schema_generator = new \phpbb\db\migration\schema_generator( +				$migrator_classes, +				new \phpbb\config\config(array()), +				$this->db, +				$db_tools, +				$this->phpbb_root_path, +				$this->php_ext, +				$table_prefix +			); +			$db_table_schema = $schema_generator->get_schema(); +			$db_table_schema = json_encode($db_table_schema, JSON_PRETTY_PRINT); + +			$this->config->set('change_table_prefix', false); +		} + +		$fp = @fopen($this->phpbb_root_path . 'store/schema.json', 'wb'); +		if (!$fp) +		{ +			throw new \Exception('INST_SCHEMA_FILE_NOT_WRITABLE'); +		} + +		fwrite($fp, $db_table_schema); +		fclose($fp); +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 1; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return 'TASK_CREATE_DATABASE_SCHEMA_FILE'; +	} +} diff --git a/phpBB/phpbb/install/module/install_database/task/set_up_database.php b/phpBB/phpbb/install/module/install_database/task/set_up_database.php new file mode 100644 index 0000000000..49c8ea23ad --- /dev/null +++ b/phpBB/phpbb/install/module/install_database/task/set_up_database.php @@ -0,0 +1,164 @@ +<?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\install\module\install_database\task; + +/** + * Set up database for table generation + */ +class set_up_database extends \phpbb\install\task_base +{ +	/** +	 * @var \phpbb\install\helper\config +	 */ +	protected $config; + +	/** +	 * @var \phpbb\db\driver\driver_interface +	 */ +	protected $db; + +	/** +	 * @var \phpbb\install\helper\database +	 */ +	protected $database_helper; + +	/** +	 * @var \phpbb\filesystem\filesystem_interface +	 */ +	protected $filesystem; + +	/** +	 * @var \phpbb\install\helper\iohandler\iohandler_interface +	 */ +	protected $iohandler; + +	/** +	 * @var string +	 */ +	protected $schema_file_path; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\install\helper\config							$config +	 * @param \phpbb\install\helper\database						$db_helper +	 * @param \phpbb\filesystem\filesystem_interface				$filesystem +	 * @param \phpbb\install\helper\iohandler\iohandler_interface	$iohandler +	 * @param string												$phpbb_root_path +	 */ +	public function __construct(\phpbb\install\helper\config $config, +								\phpbb\install\helper\database $db_helper, +								\phpbb\filesystem\filesystem_interface $filesystem, +								\phpbb\install\helper\iohandler\iohandler_interface $iohandler, +								$phpbb_root_path) +	{ +		$dbms = $db_helper->get_available_dbms($config->get('dbms')); +		$dbms = $dbms[$config->get('dbms')]['DRIVER']; + +		$this->db				= new $dbms(); +		$this->db->sql_connect( +			$config->get('dbhost'), +			$config->get('dbuser'), +			$config->get('dbpasswd'), +			$config->get('dbname'), +			$config->get('dbport'), +			false, +			false +		); + +		$this->config			= $config; +		$this->database_helper	= $db_helper; +		$this->filesystem		= $filesystem; +		$this->iohandler		= $iohandler; +		$this->phpbb_root_path	= $phpbb_root_path; + +		parent::__construct(false); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function check_requirements() +	{ +		$dbms = $this->config->get('dbms'); +		$dbms_info = $this->database_helper->get_available_dbms($dbms); +		$schema_name = $dbms_info[$dbms]['SCHEMA']; + +		if ($dbms === 'mysql') +		{ +			if (version_compare($this->db->sql_server_info(true), '4.1.3', '>=')) +			{ +				$schema_name .= '_41'; +			} +			else +			{ +				$schema_name .= '_40'; +			} +		} + +		$this->schema_file_path = $this->phpbb_root_path . 'install/schemas/' . $schema_name . '_schema.sql'; + +		return $this->filesystem->exists($this->schema_file_path); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		$this->db->sql_return_on_error(true); + +		$dbms = $this->config->get('dbms'); +		$dbms_info = $this->database_helper->get_available_dbms($dbms); +		$delimiter = $dbms_info[$dbms]['DELIM']; +		$table_prefix = $this->config->get('table_prefix'); + +		$sql_query = @file_get_contents($this->schema_file_path); +		$sql_query = preg_replace('#phpbb_#i', $table_prefix, $sql_query); +		$sql_query = $this->database_helper->remove_comments($sql_query); +		$sql_query = $this->database_helper->split_sql_file($sql_query, $delimiter); + +		foreach ($sql_query as $sql) +		{ +			if (!$this->db->sql_query($sql)) +			{ +				$error = $this->db->sql_error($this->db->get_sql_error_sql()); +				$this->iohandler->add_error_message('INST_ERR_DB', $error['message']); +			} +		} + +		unset($sql_query); +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 1; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return 'TASK_SETUP_DATABASE'; +	} +} diff --git a/phpBB/phpbb/install/module/install_filesystem/module.php b/phpBB/phpbb/install/module/install_filesystem/module.php new file mode 100644 index 0000000000..7215449664 --- /dev/null +++ b/phpBB/phpbb/install/module/install_filesystem/module.php @@ -0,0 +1,28 @@ +<?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\install\module\install_filesystem; + +/** + * Installer module for filesystem installation + */ +class module extends \phpbb\install\module_base +{ +	/** +	 * {@inheritdoc} +	 */ +	public function get_navigation_stage_path() +	{ +		return array('install', 0, 'install'); +	} +} diff --git a/phpBB/phpbb/install/module/install_filesystem/task/create_config_file.php b/phpBB/phpbb/install/module/install_filesystem/task/create_config_file.php new file mode 100644 index 0000000000..5bc425b929 --- /dev/null +++ b/phpBB/phpbb/install/module/install_filesystem/task/create_config_file.php @@ -0,0 +1,244 @@ +<?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\install\module\install_filesystem\task; + +use phpbb\install\exception\user_interaction_required_exception; + +/** + * Dumps config file + */ +class create_config_file extends \phpbb\install\task_base +{ +	/** +	 * @var \phpbb\filesystem\filesystem_interface +	 */ +	protected $filesystem; + +	/** +	 * @var \phpbb\install\helper\database +	 */ +	protected $db_helper; + +	/** +	 * @var \phpbb\install\helper\config +	 */ +	protected $install_config; + +	/** +	 * @var \phpbb\install\helper\iohandler\iohandler_interface +	 */ +	protected $iohandler; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * @var string +	 */ +	protected $php_ext; + +	/** +	 * @var array +	 */ +	protected $options; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\filesystem\filesystem_interface				$filesystem +	 * @param \phpbb\install\helper\config							$install_config +	 * @param \phpbb\install\helper\database						$db_helper +	 * @param \phpbb\install\helper\iohandler\iohandler_interface	$iohandler +	 * @param string												$phpbb_root_path +	 * @param string												$php_ext +	 * @param array													$options +	 */ +	public function __construct(\phpbb\filesystem\filesystem_interface $filesystem, +								\phpbb\install\helper\config $install_config, +								\phpbb\install\helper\database $db_helper, +								\phpbb\install\helper\iohandler\iohandler_interface $iohandler, +								$phpbb_root_path, +								$php_ext, +								$options = array()) +	{ +		$this->install_config	= $install_config; +		$this->db_helper		= $db_helper; +		$this->filesystem		= $filesystem; +		$this->iohandler		= $iohandler; +		$this->phpbb_root_path	= $phpbb_root_path; +		$this->php_ext			= $php_ext; +		$this->options			= array_merge(array( +			'debug' => false, +			'debug_container' => false, +			'environment' => null, +		), $options); + +		parent::__construct(true); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		$config_written = true; + +		// Create config.php +		$path_to_config = $this->phpbb_root_path . 'config.' . $this->php_ext; + +		$fp = @fopen($path_to_config, 'w'); +		if (!$fp) +		{ +			$config_written = false; +		} + +		$config_content = $this->get_config_data($this->options['debug'], $this->options['debug_container'], $this->options['environment']); + +		if (!@fwrite($fp, $config_content)) +		{ +			$config_written = false; +		} + +		@fclose($fp); + +		// chmod config.php to be only readable +		if ($config_written) +		{ +			try +			{ +				$this->filesystem->phpbb_chmod($path_to_config, \phpbb\filesystem\filesystem_interface::CHMOD_READ); +			} +			catch (\phpbb\filesystem\exception\filesystem_exception $e) +			{ +				// Do nothing, the user will get a notice later +			} +		} +		else +		{ +			$this->iohandler->add_error_message('UNABLE_TO_WRITE_CONFIG_FILE'); +			throw new user_interaction_required_exception(); +		} + +		// Create a lock file to indicate that there is an install in progress +		$fp = @fopen($this->phpbb_root_path . 'cache/install_lock', 'wb'); +		if ($fp === false) +		{ +			// We were unable to create the lock file - abort +			$this->iohandler->add_error_message('UNABLE_TO_WRITE_LOCK'); +			throw new user_interaction_required_exception(); +		} +		@fclose($fp); + +		try +		{ +			$this->filesystem->phpbb_chmod($this->phpbb_root_path . 'cache/install_lock', 0777); +		} +		catch (\phpbb\filesystem\exception\filesystem_exception $e) +		{ +			// Do nothing, the user will get a notice later +		} +	} + +	/** +	 * Returns the content which should be dumped to config.php +	 * +	 * @param bool		$debug 				If the debug constants should be enabled by default or not +	 * @param bool		$debug_container 	If the container should be compiled on +	 *										every page load or not +	 * @param string	$environment		The environment to use +	 * +	 * @return string	content to be written to the config file +	 */ +	protected function get_config_data($debug = false, $debug_container = false, $environment = null) +	{ +		$config_content = "<?php\n"; +		$config_content .= "// phpBB 3.2.x auto-generated configuration file\n// Do not change anything in this file!\n"; + +		$dbms = $this->install_config->get('dbms'); +		$db_driver = $this->db_helper->get_available_dbms($dbms); +		$db_driver = $db_driver[$dbms]['DRIVER']; + +		$config_data_array = array( +			'dbms'			=> $db_driver, +			'dbhost'		=> $this->install_config->get('dbhost'), +			'dbport'		=> $this->install_config->get('dbport'), +			'dbname'		=> $this->install_config->get('dbname'), +			'dbuser'		=> $this->install_config->get('dbuser'), +			'dbpasswd'		=> $this->install_config->get('dbpasswd'), +			'table_prefix'	=> $this->install_config->get('table_prefix'), + +			'phpbb_adm_relative_path'	=> 'adm/', + +			'acm_type'		=> 'phpbb\cache\driver\file', +		); + +		foreach ($config_data_array as $key => $value) +		{ +			$config_content .= "\${$key} = '" . str_replace("'", "\\'", str_replace('\\', '\\\\', $value)) . "';\n"; +		} + +		$config_content .= "\n@define('PHPBB_INSTALLED', true);\n"; +		$config_content .= "// @define('PHPBB_DISPLAY_LOAD_TIME', true);\n"; + +		if ($environment) +		{ +			$config_content .= "@define('PHPBB_ENVIRONMENT', 'test');\n"; +		} +		else if ($debug) +		{ +			$config_content .= "@define('PHPBB_ENVIRONMENT', 'development');\n"; +		} +		else +		{ +			$config_content .= "@define('PHPBB_ENVIRONMENT', 'production');\n"; +		} + +		if ($debug_container) +		{ +			$config_content .= "@define('DEBUG_CONTAINER', true);\n"; +		} +		else +		{ +			$config_content .= "// @define('DEBUG_CONTAINER', true);\n"; +		} + +		if ($environment === 'test') +		{ +			$config_content .= "@define('DEBUG_TEST', true);\n"; + +			// Mandatory for the functional tests, will be removed by PHPBB3-12623 +			$config_content .= "@define('DEBUG', true);\n"; +		} + +		return $config_content; +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 1; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return 'TASK_CREATE_CONFIG_FILE'; +	} +} diff --git a/phpBB/phpbb/install/module/install_finish/module.php b/phpBB/phpbb/install/module/install_finish/module.php new file mode 100644 index 0000000000..3a7544b84f --- /dev/null +++ b/phpBB/phpbb/install/module/install_finish/module.php @@ -0,0 +1,28 @@ +<?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\install\module\install_finish; + +/** + * Installer module for filesystem installation + */ +class module extends \phpbb\install\module_base +{ +	/** +	 * {@inheritdoc} +	 */ +	public function get_navigation_stage_path() +	{ +		return array('install', 0, 'install'); +	} +} diff --git a/phpBB/phpbb/install/module/install_finish/task/notify_user.php b/phpBB/phpbb/install/module/install_finish/task/notify_user.php new file mode 100644 index 0000000000..292be57f5f --- /dev/null +++ b/phpBB/phpbb/install/module/install_finish/task/notify_user.php @@ -0,0 +1,174 @@ +<?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\install\module\install_finish\task; + +use phpbb\config\db; + +/** + * Logs installation and sends an email to the admin + */ +class notify_user extends \phpbb\install\task_base +{ +	/** +	 * @var \phpbb\install\helper\config +	 */ +	protected $install_config; + +	/** +	 * @var \phpbb\install\helper\iohandler\iohandler_interface +	 */ +	protected $iohandler; + +	/** +	 * @var \phpbb\auth\auth +	 */ +	protected $auth; + +	/** +	 * @var \phpbb\config\db +	 */ +	protected $config; + +	/** +	 * @var \phpbb\language\language +	 */ +	protected $language; + +	/** +	 * @var \phpbb\log\log_interface +	 */ +	protected $log; + +	/** +	 * @var \phpbb\user +	 */ +	protected $user; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * @var string +	 */ +	protected $php_ext; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\install\helper\container_factory				$container +	 * @param \phpbb\install\helper\config							$install_config +	 * @param \phpbb\install\helper\iohandler\iohandler_interface	$iohandler +	 * @param string												$phpbb_root_path +	 * @param string												$php_ext +	 */ +	public function __construct(\phpbb\install\helper\container_factory $container, \phpbb\install\helper\config $install_config, \phpbb\install\helper\iohandler\iohandler_interface $iohandler, $phpbb_root_path, $php_ext) +	{ +		$this->install_config	= $install_config; +		$this->iohandler		= $iohandler; + +		$this->auth				= $container->get('auth'); +		$this->language			= $container->get('language'); +		$this->log				= $container->get('log'); +		$this->user				= $container->get('user'); +		$this->phpbb_root_path	= $phpbb_root_path; +		$this->php_ext			= $php_ext; + +		// We need to reload config for cases when it doesn't have all values +		/** @var \phpbb\cache\driver\driver_interface $cache */ +		$cache = $container->get('cache.driver'); +		$cache->destroy('config'); + +		$this->config = new db( +			$container->get('dbal.conn'), +			$cache, +			$container->get_parameter('tables.config') +		); + +		parent::__construct(true); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		$this->user->session_begin(); +		$this->user->setup('common'); + +		if ($this->config['email_enable']) +		{ +			include ($this->phpbb_root_path . 'includes/functions_messenger.' . $this->php_ext); + +			// functions_messenger.php uses config to determine language paths +			// Remove when able +			global $config; +			$config = $this->config; + +			$messenger = new \messenger(false); +			$messenger->template('installed', $this->install_config->get('user_language', 'en')); +			$messenger->to($this->config['board_email'], $this->install_config->get('admin_name')); +			$messenger->anti_abuse_headers($this->config, $this->user); +			$messenger->assign_vars(array( +					'USERNAME'		=> htmlspecialchars_decode($this->install_config->get('admin_name')), +					'PASSWORD'		=> htmlspecialchars_decode($this->install_config->get('admin_passwd'))) +			); +			$messenger->send(NOTIFY_EMAIL); +		} + +		// Login admin +		// Ugly but works +		$this->auth->login( +			$this->install_config->get('admin_name'), +			$this->install_config->get('admin_passwd'), +			false, +			true, +			true +		); + +		$this->iohandler->set_cookie($this->config['cookie_name'] . '_sid', $this->user->session_id); +		$this->iohandler->set_cookie($this->config['cookie_name'] . '_u', $this->user->cookie_data['u']); +		$this->iohandler->set_cookie($this->config['cookie_name'] . '_k', $this->user->cookie_data['k']); + +		// Create log +		$this->log->add( +			'admin', +			$this->user->data['user_id'], +			$this->user->ip, +			'LOG_INSTALL_INSTALLED', +			false, +			array($this->config['version']) +		); + +		// Remove install_lock +		@unlink($this->phpbb_root_path . 'cache/install_lock'); +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 1; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return 'TASK_NOTIFY_USER'; +	} +} diff --git a/phpBB/phpbb/install/module/install_finish/task/populate_migrations.php b/phpBB/phpbb/install/module/install_finish/task/populate_migrations.php new file mode 100644 index 0000000000..34541c361e --- /dev/null +++ b/phpBB/phpbb/install/module/install_finish/task/populate_migrations.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\install\module\install_finish\task; + +use phpbb\install\exception\resource_limit_reached_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\container_factory; + +/** + * Populates migrations + */ +class populate_migrations extends \phpbb\install\task_base +{ +	/** +	 * @var config +	 */ +	protected $config; + +	/** +	 * @var \phpbb\extension\manager +	 */ +	protected $extension_manager; + +	/** +	 * @var \phpbb\db\migrator +	 */ +	protected $migrator; + +	/** +	 * Constructor +	 * +	 * @param config			$config		Installer's config +	 * @param container_factory	$container	phpBB's DI contianer +	 */ +	public function __construct(config $config, container_factory $container) +	{ +		$this->config				= $config; +		$this->extension_manager	= $container->get('ext.manager'); +		$this->migrator				= $container->get('migrator'); + +		parent::__construct(true); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		if (!$this->config->get('populate_migration_refresh_before', false)) +		{ +			if ($this->config->get_time_remaining() < 1) +			{ +				$this->config->set('populate_migration_refresh_before', true); +				throw new resource_limit_reached_exception(); +			} +		} + +		$finder = $this->extension_manager->get_finder(); + +		$migrations = $finder +			->core_path('phpbb/db/migration/data/') +			->get_classes(); +		$this->migrator->populate_migrations($migrations); +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 1; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return 'TASK_POPULATE_MIGRATIONS'; +	} +} diff --git a/phpBB/phpbb/install/module/obtain_data/install_module.php b/phpBB/phpbb/install/module/obtain_data/install_module.php new file mode 100644 index 0000000000..deb4be90d8 --- /dev/null +++ b/phpBB/phpbb/install/module/obtain_data/install_module.php @@ -0,0 +1,33 @@ +<?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\install\module\obtain_data; + +class install_module extends \phpbb\install\module_base +{ +	/** +	 * {@inheritdoc} +	 */ +	public function get_navigation_stage_path() +	{ +		return array('install', 0, 'obtain_data'); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_step_count() +	{ +		return 0; +	} +} diff --git a/phpBB/phpbb/install/module/obtain_data/task/obtain_admin_data.php b/phpBB/phpbb/install/module/obtain_data/task/obtain_admin_data.php new file mode 100644 index 0000000000..d1f1af6b83 --- /dev/null +++ b/phpBB/phpbb/install/module/obtain_data/task/obtain_admin_data.php @@ -0,0 +1,218 @@ +<?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\install\module\obtain_data\task; + +use phpbb\install\exception\user_interaction_required_exception; + +/** + * This class requests and validates admin account data from the user + */ +class obtain_admin_data extends \phpbb\install\task_base implements \phpbb\install\task_interface +{ +	/** +	 * @var \phpbb\install\helper\config +	 */ +	protected $install_config; + +	/** +	 * @var \phpbb\install\helper\iohandler\iohandler_interface +	 */ +	protected $io_handler; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\install\helper\config							$install_config	Installer's config helper +	 * @param \phpbb\install\helper\iohandler\iohandler_interface	$iohandler		Installer's input-output handler +	 */ +	public function __construct(\phpbb\install\helper\config $install_config, +								\phpbb\install\helper\iohandler\iohandler_interface $iohandler) +	{ +		$this->install_config	= $install_config; +		$this->io_handler		= $iohandler; + +		parent::__construct(true); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		// Check if data is sent +		if ($this->io_handler->get_input('submit_admin', false)) +		{ +			$this->process_form(); +		} +		else +		{ +			$this->request_form_data(); +		} +	} + +	/** +	 * Process form data +	 */ +	protected function process_form() +	{ +		// Admin data +		$admin_name		= $this->io_handler->get_input('admin_name', '', true); +		$admin_pass1	= $this->io_handler->get_input('admin_pass1', '', true); +		$admin_pass2	= $this->io_handler->get_input('admin_pass2', '', true); +		$board_email	= $this->io_handler->get_input('board_email', '', true); + +		$admin_data_valid = $this->check_admin_data($admin_name, $admin_pass1, $admin_pass2, $board_email); + +		if ($admin_data_valid) +		{ +			$this->install_config->set('admin_name', $admin_name); +			$this->install_config->set('admin_passwd', $admin_pass1); +			$this->install_config->set('board_email', $board_email); +		} +		else +		{ +			$this->request_form_data(true); +		} +	} + +	/** +	 * Request data from the user +	 * +	 * @param bool $use_request_data Whether to use submited data +	 * +	 * @throws \phpbb\install\exception\user_interaction_required_exception When the user is required to provide data +	 */ +	protected function request_form_data($use_request_data = false) +	{ +		if ($use_request_data) +		{ +			$admin_username	= $this->io_handler->get_input('admin_name', '', true); +			$admin_email	= $this->io_handler->get_input('board_email', '', true); +		} +		else +		{ +			$admin_username	= ''; +			$admin_email	= ''; +		} + +		$admin_form = array( +			'admin_name'	=> array( +				'label'			=> 'ADMIN_USERNAME', +				'description'	=> 'ADMIN_USERNAME_EXPLAIN', +				'type'			=> 'text', +				'default'		=> $admin_username, +			), +			'board_email'	=> array( +				'label'		=> 'CONTACT_EMAIL', +				'type'		=> 'email', +				'default'	=> $admin_email, +			), +			'admin_pass1'	=> array( +				'label'			=> 'ADMIN_PASSWORD', +				'description'	=> 'ADMIN_PASSWORD_EXPLAIN', +				'type'			=> 'password', +			), +			'admin_pass2'	=> array( +				'label'	=> 'ADMIN_PASSWORD_CONFIRM', +				'type'	=> 'password', +			), +			'submit_admin'	=> array( +				'label'	=> 'SUBMIT', +				'type'	=> 'submit', +			), +		); + +		$this->io_handler->add_user_form_group('ADMIN_CONFIG', $admin_form); + +		// Require user interaction +		throw new user_interaction_required_exception(); +	} + +	/** +	 * Check admin data +	 * +	 * @param string	$username	Admin username +	 * @param string	$pass1		Admin password +	 * @param string	$pass2		Admin password confirmation +	 * @param string	$email		Admin e-mail address +	 * +	 * @return bool	True if data is valid, false otherwise +	 */ +	protected function check_admin_data($username, $pass1, $pass2, $email) +	{ +		$data_valid = true; + +		// Check if none of admin data is empty +		if (in_array('', array($username, $pass1, $pass2, $email), true)) +		{ +			$this->io_handler->add_error_message('INST_ERR_MISSING_DATA'); +			$data_valid = false; +		} + +		if (utf8_strlen($username) < 3) +		{ +			$this->io_handler->add_error_message('INST_ERR_USER_TOO_SHORT'); +			$data_valid = false; +		} + +		if (utf8_strlen($username) > 20) +		{ +			$this->io_handler->add_error_message('INST_ERR_USER_TOO_LONG'); +			$data_valid = false; +		} + +		if ($pass1 !== $pass2 && $pass1 !== '') +		{ +			$this->io_handler->add_error_message('INST_ERR_PASSWORD_MISMATCH'); +			$data_valid = false; +		} + +		// Test against the default password rules +		if (utf8_strlen($pass1) < 6) +		{ +			$this->io_handler->add_error_message('INST_ERR_PASSWORD_TOO_SHORT'); +			$data_valid = false; +		} + +		if (utf8_strlen($pass1) > 30) +		{ +			$this->io_handler->add_error_message('INST_ERR_PASSWORD_TOO_LONG'); +			$data_valid = false; +		} + +		if (!preg_match('/^' . get_preg_expression('email') . '$/i', $email)) +		{ +			$this->io_handler->add_error_message('INST_ERR_EMAIL_INVALID'); +			$data_valid = false; +		} + +		return $data_valid; +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 0; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return ''; +	} +} diff --git a/phpBB/phpbb/install/module/obtain_data/task/obtain_board_data.php b/phpBB/phpbb/install/module/obtain_data/task/obtain_board_data.php new file mode 100644 index 0000000000..ff2a0a2f86 --- /dev/null +++ b/phpBB/phpbb/install/module/obtain_data/task/obtain_board_data.php @@ -0,0 +1,185 @@ +<?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\install\module\obtain_data\task; + +use phpbb\install\exception\user_interaction_required_exception; + +/** + * This class obtains default data from the user related to board (Board name, Board descritpion, etc...) + */ +class obtain_board_data extends \phpbb\install\task_base implements \phpbb\install\task_interface +{ +	/** +	 * @var \phpbb\install\helper\config +	 */ +	protected $install_config; + +	/** +	 * @var \phpbb\install\helper\iohandler\iohandler_interface +	 */ +	protected $io_handler; + +	/** +	 * @var \phpbb\language\language_file_helper +	 */ +	protected $language_helper; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\install\helper\config							$config			Installer's config +	 * @param \phpbb\install\helper\iohandler\iohandler_interface	$iohandler		Installer's input-output handler +	 * @param \phpbb\language\language_file_helper					$lang_helper	Language file helper +	 */ +	public function __construct(\phpbb\install\helper\config $config, +								\phpbb\install\helper\iohandler\iohandler_interface $iohandler, +								\phpbb\language\language_file_helper $lang_helper) +	{ +		$this->install_config	= $config; +		$this->io_handler		= $iohandler; +		$this->language_helper	= $lang_helper; + +		parent::__construct(true); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		// Check if data is sent +		if ($this->io_handler->get_input('submit_board', false)) +		{ +			$this->process_form(); +		} +		else +		{ +			$this->request_form_data(); +		} +	} + +	/** +	 * Process form data +	 */ +	protected function process_form() +	{ +		// Board data +		$default_lang	= $this->io_handler->get_input('default_lang', ''); +		$board_name		= $this->io_handler->get_input('board_name', '', true); +		$board_desc		= $this->io_handler->get_input('board_description', '', true); + +		// Check default lang +		$langs = $this->language_helper->get_available_languages(); +		$lang_valid = false; + +		foreach ($langs as $lang) +		{ +			if ($lang['iso'] === $default_lang) +			{ +				$lang_valid = true; +				break; +			} +		} + +		$this->install_config->set('board_name', $board_name); +		$this->install_config->set('board_description', $board_desc); + +		if ($lang_valid) +		{ +			$this->install_config->set('default_lang', $default_lang); +		} +		else +		{ +			$this->request_form_data(true); +		} +	} + +	/** +	 * Request data from the user +	 * +	 * @param bool $use_request_data Whether to use submited data +	 * +	 * @throws \phpbb\install\exception\user_interaction_required_exception When the user is required to provide data +	 */ +	protected function request_form_data($use_request_data = false) +	{ +		if ($use_request_data) +		{ +			$board_name		= $this->io_handler->get_input('board_name', '', true); +			$board_desc		= $this->io_handler->get_input('board_description', '', true); +		} +		else +		{ +			$board_name		= '{L_CONFIG_SITENAME}'; +			$board_desc		= '{L_CONFIG_SITE_DESC}'; +		} + +		// Use language because we only check this to be valid +		$default_lang	= $this->install_config->get('user_language', 'en'); + +		$langs = $this->language_helper->get_available_languages(); +		$lang_options = array(); + +		foreach ($langs as $lang) +		{ +			$lang_options[] = array( +				'value'		=> $lang['iso'], +				'label'		=> $lang['local_name'], +				'selected'	=> ($default_lang === $lang['iso']), +			); +		} + +		$board_form = array( +			'default_lang' => array( +				'label'		=> 'DEFAULT_LANGUAGE', +				'type'		=> 'select', +				'options'	=> $lang_options, +			), +			'board_name' => array( +				'label'		=> 'BOARD_NAME', +				'type'		=> 'text', +				'default'	=> $board_name, +			), +			'board_description' => array( +				'label'		=> 'BOARD_DESCRIPTION', +				'type'		=> 'text', +				'default'	=> $board_desc, +			), +			'submit_board'	=> array( +				'label'	=> 'SUBMIT', +				'type'	=> 'submit', +			), +		); + +		$this->io_handler->add_user_form_group('BOARD_CONFIG', $board_form); + +		throw new user_interaction_required_exception(); +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 0; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return ''; +	} +} diff --git a/phpBB/phpbb/install/module/obtain_data/task/obtain_database_data.php b/phpBB/phpbb/install/module/obtain_data/task/obtain_database_data.php new file mode 100644 index 0000000000..ce720dbf76 --- /dev/null +++ b/phpBB/phpbb/install/module/obtain_data/task/obtain_database_data.php @@ -0,0 +1,270 @@ +<?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\install\module\obtain_data\task; + +use phpbb\install\exception\user_interaction_required_exception; + +/** + * This class requests and validates database information from the user + */ +class obtain_database_data extends \phpbb\install\task_base implements \phpbb\install\task_interface +{ +	/** +	 * @var \phpbb\install\helper\database +	 */ +	protected $database_helper; + +	/** +	 * @var \phpbb\install\helper\config +	 */ +	protected $install_config; + +	/** +	 * @var \phpbb\install\helper\iohandler\iohandler_interface +	 */ +	protected $io_handler; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\install\helper\database						$database_helper	Installer's database helper +	 * @param \phpbb\install\helper\config							$install_config		Installer's config helper +	 * @param \phpbb\install\helper\iohandler\iohandler_interface	$iohandler			Installer's input-output handler +	 */ +	public function __construct(\phpbb\install\helper\database $database_helper, +								\phpbb\install\helper\config $install_config, +								\phpbb\install\helper\iohandler\iohandler_interface $iohandler) +	{ +		$this->database_helper	= $database_helper; +		$this->install_config	= $install_config; +		$this->io_handler		= $iohandler; + +		parent::__construct(true); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		// Check if data is sent +		if ($this->io_handler->get_input('submit_database', false)) +		{ +			$this->process_form(); +		} +		else +		{ +			$this->request_form_data(); +		} +	} + +	/** +	 * Process form data +	 */ +	protected function process_form() +	{ +		// Collect database data +		$dbms			= $this->io_handler->get_input('dbms', ''); +		$dbhost			= $this->io_handler->get_input('dbhost', '', true); +		$dbport			= $this->io_handler->get_input('dbport', ''); +		$dbuser			= $this->io_handler->get_input('dbuser', ''); +		$dbpasswd		= $this->io_handler->get_input('dbpasswd', '', true); +		$dbname			= $this->io_handler->get_input('dbname', ''); +		$table_prefix	= $this->io_handler->get_input('table_prefix', ''); + +		// Check database data +		$user_data_vaild = $this->check_database_data($dbms, $dbhost, $dbport, $dbuser, $dbpasswd, $dbname, $table_prefix); + +		// Save database data if it is correct +		if ($user_data_vaild) +		{ +			$this->install_config->set('dbms', $dbms); +			$this->install_config->set('dbhost', $dbhost); +			$this->install_config->set('dbport', $dbport); +			$this->install_config->set('dbuser', $dbuser); +			$this->install_config->set('dbpasswd', $dbpasswd); +			$this->install_config->set('dbname', $dbname); +			$this->install_config->set('table_prefix', $table_prefix); +		} +		else +		{ +			$this->request_form_data(true); +		} +	} + +	/** +	 * Request data from the user +	 * +	 * @param bool $use_request_data Whether to use submited data +	 * +	 * @throws \phpbb\install\exception\user_interaction_required_exception When the user is required to provide data +	 */ +	protected function request_form_data($use_request_data = false) +	{ +		if ($use_request_data) +		{ +			$dbms			= $this->io_handler->get_input('dbms', ''); +			$dbhost			= $this->io_handler->get_input('dbhost', '', true); +			$dbport			= $this->io_handler->get_input('dbport', ''); +			$dbuser			= $this->io_handler->get_input('dbuser', ''); +			$dbname			= $this->io_handler->get_input('dbname', ''); +			$table_prefix	= $this->io_handler->get_input('table_prefix', 'phpbb_'); +		} +		else +		{ +			$dbms			= ''; +			$dbhost			= ''; +			$dbport			= ''; +			$dbuser			= ''; +			$dbname			= ''; +			$table_prefix	= 'phpbb_'; +		} + +		$dbms_select = array(); +		foreach ($this->database_helper->get_available_dbms() as $dbms_key => $dbms_array) +		{ +			$dbms_select[] = array( +				'value'		=> $dbms_key, +				'label'		=> 'DB_OPTION_' . strtoupper($dbms_key), +				'selected'	=> ($dbms_key === $dbms), +			); +		} + +		$database_form = array( +			'dbms' => array( +				'label'		=> 'DBMS', +				'type'		=> 'select', +				'options'	=> $dbms_select, +			), +			'dbhost' => array( +				'label'			=> 'DB_HOST', +				'description'	=> 'DB_HOST_EXPLAIN', +				'type'			=> 'text', +				'default'		=> $dbhost, +			), +			'dbport' => array( +				'label'			=> 'DB_PORT', +				'description'	=> 'DB_PORT_EXPLAIN', +				'type'			=> 'text', +				'default'		=> $dbport, +			), +			'dbuser' => array( +				'label'		=> 'DB_USERNAME', +				'type'		=> 'text', +				'default'	=> $dbuser, +			), +			'dbpasswd' => array( +				'label'		=> 'DB_PASSWORD', +				'type'	=> 'password', +			), +			'dbname' => array( +				'label'		=> 'DB_NAME', +				'type'		=> 'text', +				'default'	=> $dbname, +			), +			'table_prefix' => array( +				'label'			=> 'TABLE_PREFIX', +				'description'	=> 'TABLE_PREFIX_EXPLAIN', +				'type'			=> 'text', +				'default'		=> $table_prefix, +			), +			'submit_database' => array( +				'label'	=> 'SUBMIT', +				'type'	=> 'submit', +			), +		); + +		$this->io_handler->add_user_form_group('DB_CONFIG', $database_form); + +		// Require user interaction +		throw new user_interaction_required_exception(); +	} + +	/** +	 * Check database data +	 * +	 * @param string	$dbms			Selected database type +	 * @param string	$dbhost			Database host address +	 * @param int		$dbport			Database port number +	 * @param string	$dbuser			Database username +	 * @param string	$dbpass			Database password +	 * @param string	$dbname			Database name +	 * @param string	$table_prefix	Database table prefix +	 * +	 * @return bool	True if database data is correct, false otherwise +	 */ +	protected function check_database_data($dbms, $dbhost, $dbport, $dbuser, $dbpass, $dbname, $table_prefix) +	{ +		$available_dbms = $this->database_helper->get_available_dbms(); +		$data_valid = true; + +		// Check if PHP has the database extensions for the specified DBMS +		if (!isset($available_dbms[$dbms])) +		{ +			$this->io_handler->add_error_message('INST_ERR_NO_DB'); +			$data_valid = false; +		} + +		// Validate table prefix +		$prefix_valid = $this->database_helper->validate_table_prefix($dbms, $table_prefix); +		if (is_array($prefix_valid)) +		{ +			foreach ($prefix_valid as $error) +			{ +				$this->io_handler->add_error_message( +					$error['title'], +					(isset($error['description'])) ? $error['description'] : false +				); +			} + +			$data_valid = false; +		} + +		// Try to connect to database if all provided data is valid +		if ($data_valid) +		{ +			$connect_test = $this->database_helper->check_database_connection($dbms, $dbhost, $dbport, $dbuser, $dbpass, $dbname, $table_prefix); +			if (is_array($connect_test)) +			{ +				foreach ($connect_test as $error) +				{ +					$this->io_handler->add_error_message( +						$error['title'], +						(isset($error['description'])) ? $error['description'] : false +					); +				} + +				$data_valid = false; +			} +		} + +		return $data_valid; +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 0; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return ''; +	} +} diff --git a/phpBB/phpbb/install/module/obtain_data/task/obtain_email_data.php b/phpBB/phpbb/install/module/obtain_data/task/obtain_email_data.php new file mode 100644 index 0000000000..1cb4f04297 --- /dev/null +++ b/phpBB/phpbb/install/module/obtain_data/task/obtain_email_data.php @@ -0,0 +1,173 @@ +<?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\install\module\obtain_data\task; + +use phpbb\install\exception\user_interaction_required_exception; + +class obtain_email_data extends \phpbb\install\task_base implements \phpbb\install\task_interface +{ +	/** +	 * @var \phpbb\install\helper\config +	 */ +	protected $install_config; + +	/** +	 * @var \phpbb\install\helper\iohandler\iohandler_interface +	 */ +	protected $io_handler; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\install\helper\config							$config		Installer's config +	 * @param \phpbb\install\helper\iohandler\iohandler_interface	$iohandler	Installer's input-output handler +	 */ +	public function __construct(\phpbb\install\helper\config $config, +								\phpbb\install\helper\iohandler\iohandler_interface $iohandler) +	{ +		$this->install_config	= $config; +		$this->io_handler		= $iohandler; + +		parent::__construct(true); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		// E-mail data +		$email_enable	= $this->io_handler->get_input('email_enable', true); +		$smtp_delivery	= $this->io_handler->get_input('smtp_delivery', ''); +		$smtp_host		= $this->io_handler->get_input('smtp_host', ''); +		$smtp_port		= $this->io_handler->get_input('smtp_port', ''); +		$smtp_auth		= $this->io_handler->get_input('smtp_auth', ''); +		$smtp_user		= $this->io_handler->get_input('smtp_user', ''); +		$smtp_passwd	= $this->io_handler->get_input('smtp_pass', ''); + +		$auth_methods = array('PLAIN', 'LOGIN', 'CRAM-MD5', 'DIGEST-MD5', 'POP-BEFORE-SMTP'); + +		// Check if data is sent +		if ($this->io_handler->get_input('submit_email', false)) +		{ +			$this->install_config->set('email_enable', $email_enable); +			$this->install_config->set('smtp_delivery', $smtp_delivery); +			$this->install_config->set('smtp_host', $smtp_host); +			$this->install_config->set('smtp_port', $smtp_port); +			$this->install_config->set('smtp_auth', $smtp_auth); +			$this->install_config->set('smtp_user', $smtp_user); +			$this->install_config->set('smtp_pass', $smtp_passwd); +		} +		else +		{ +			$auth_options = array(); +			foreach ($auth_methods as $method) +			{ +				$auth_options[] = array( +					'value'		=> $method, +					'label'		=> 'SMTP_' . str_replace('-', '_', $method), +					'selected'	=> false, +				); +			} + +			$email_form = array( +				'email_enable' => array( +					'label'			=> 'ENABLE_EMAIL', +					'description'	=> 'COOKIE_SECURE_EXPLAIN', +					'type'			=> 'radio', +					'options'		=> array( +						array( +							'value'		=> 1, +							'label'		=> 'ENABLE', +							'selected'	=> true, +						), +						array( +							'value'		=> 0, +							'label'		=> 'DISABLE', +							'selected'	=> false, +						), +					), +				), +				'smtp_delivery' => array( +					'label'			=> 'USE_SMTP', +					'description'	=> 'USE_SMTP_EXPLAIN', +					'type'			=> 'radio', +					'options'		=> array( +						array( +							'value'		=> 0, +							'label'		=> 'NO', +							'selected'	=> true, +						), +						array( +							'value'		=> 1, +							'label'		=> 'YES', +							'selected'	=> false, +						), +					), +				), +				'smtp_host' => array( +					'label'			=> 'SMTP_SERVER', +					'type'			=> 'text', +					'default'		=> $smtp_host, +				), +				'smtp_port' => array( +					'label'			=> 'SMTP_PORT', +					'type'			=> 'text', +					'default'		=> $smtp_port, +				), +				'smtp_auth' => array( +					'label'			=> 'SMTP_AUTH_METHOD', +					'description'	=> 'SMTP_AUTH_METHOD_EXPLAIN', +					'type'			=> 'select', +					'options'		=> $auth_options, +				), +				'smtp_user' => array( +					'label'			=> 'SMTP_USERNAME', +					'description'	=> 'SMTP_USERNAME_EXPLAIN', +					'type'			=> 'text', +					'default'		=> $smtp_user, +				), +				'smtp_pass' => array( +					'label'			=> 'SMTP_PASSWORD', +					'description'	=> 'SMTP_PASSWORD_EXPLAIN', +					'type'			=> 'password', +				), +				'submit_email' => array( +					'label'	=> 'SUBMIT', +					'type'	=> 'submit', +				), +			); + +			$this->io_handler->add_user_form_group('EMAIL_CONFIG', $email_form); + +			throw new user_interaction_required_exception(); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 0; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return ''; +	} +} diff --git a/phpBB/phpbb/install/module/obtain_data/task/obtain_file_updater_method.php b/phpBB/phpbb/install/module/obtain_data/task/obtain_file_updater_method.php new file mode 100644 index 0000000000..d5a8855c37 --- /dev/null +++ b/phpBB/phpbb/install/module/obtain_data/task/obtain_file_updater_method.php @@ -0,0 +1,167 @@ +<?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\install\module\obtain_data\task; + +use phpbb\install\exception\user_interaction_required_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\install\task_base; + +class obtain_file_updater_method extends task_base +{ +	/** +	 * @var array	Supported compression methods +	 * +	 * Note: .tar is assumed to be supported, but not in the list +	 */ +	protected $available_methods; + +	/** +	 * @var \phpbb\install\helper\config +	 */ +	protected $installer_config; + +	/** +	 * @var \phpbb\install\helper\iohandler\iohandler_interface +	 */ +	protected $iohandler; + +	/** +	 * Constructor +	 * +	 * @param config				$installer_config +	 * @param iohandler_interface	$iohandler +	 */ +	public function __construct(config $installer_config, iohandler_interface $iohandler) +	{ +		$this->installer_config	= $installer_config; +		$this->iohandler		= $iohandler; + +		$this->available_methods = array('.tar.gz' => 'zlib', '.tar.bz2' => 'bz2', '.zip' => 'zlib'); + +		parent::__construct(false); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function check_requirements() +	{ +		return $this->installer_config->get('do_update_files', false); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		// Check if data is sent +		if ($this->iohandler->get_input('submit_update_file', false)) +		{ +			$supported_methods = array('compression', 'ftp', 'direct_file'); +			$method = $this->iohandler->get_input('method', 'compression'); +			$update_method = (in_array($method, $supported_methods, true)) ? $method : 'compression'; +			$this->installer_config->set('file_update_method', $update_method); + +			$compression = $this->iohandler->get_input('compression_method', '.zip'); +			$supported_methods = array_keys($this->available_methods); +			$supported_methods[] = '.tar'; +			$compression = (in_array($compression, $supported_methods, true)) ? $compression : '.zip'; +			$this->installer_config->set('file_update_compression', $compression); +		} +		else +		{ +			$this->iohandler->add_user_form_group('UPDATE_FILE_METHOD_TITLE', array( +				'method' => array( +					'label'		=> 'UPDATE_FILE_METHOD', +					'type'		=> 'select', +					'options'	=> array( +						array( +							'value'		=> 'compression', +							'label'		=> 'UPDATE_FILE_METHOD_DOWNLOAD', +							'selected'	=> true, +						), +						array( +							'value'		=> 'ftp', +							'label'		=> 'UPDATE_FILE_METHOD_FTP', +							'selected'	=> false, +						), +						array( +							'value'		=> 'direct_file', +							'label'		=> 'UPDATE_FILE_METHOD_FILESYSTEM', +							'selected'	=> false, +						), +					), +				), +				'compression_method' => array( +					'label'		=> 'SELECT_DOWNLOAD_FORMAT', +					'type'		=> 'select', +					'options'	=> $this->get_available_compression_methods(), +				), +				'submit_update_file' => array( +					'label'	=> 'SUBMIT', +					'type'	=> 'submit', +				), +			)); + +			throw new user_interaction_required_exception(); +		} +	} + +	/** +	 * Returns form elements in an array of available compression methods +	 * +	 * @return array +	 */ +	protected function get_available_compression_methods() +	{ +		$methods[] = array( +			'value'		=> '.tar', +			'label'		=> '.tar', +			'selected'	=> true, +		); + +		foreach ($this->available_methods as $type => $module) +		{ +			if (!@extension_loaded($module)) +			{ +				continue; +			} + +			$methods[] = array( +				'value'		=> $type, +				'label'		=> $type, +				'selected'	=> false, +			); +		} + +		return $methods; +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 0; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return ''; +	} +} diff --git a/phpBB/phpbb/install/module/obtain_data/task/obtain_imagick_path.php b/phpBB/phpbb/install/module/obtain_data/task/obtain_imagick_path.php new file mode 100644 index 0000000000..377d96ed1a --- /dev/null +++ b/phpBB/phpbb/install/module/obtain_data/task/obtain_imagick_path.php @@ -0,0 +1,89 @@ +<?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\install\module\obtain_data\task; + +class obtain_imagick_path extends \phpbb\install\task_base implements \phpbb\install\task_interface +{ +	/** +	 * @var \phpbb\install\helper\config +	 */ +	protected $config; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\install\helper\config	$config	Installer's config +	 */ +	public function __construct(\phpbb\install\helper\config $config) +	{ +		$this->config = $config; + +		parent::__construct(true); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		// Can we find ImageMagick anywhere on the system? +		$exe = (DIRECTORY_SEPARATOR == '\\') ? '.exe' : ''; + +		$magic_home = getenv('MAGICK_HOME'); +		$img_imagick = ''; +		if (empty($magic_home)) +		{ +			$locations = array('C:/WINDOWS/', 'C:/WINNT/', 'C:/WINDOWS/SYSTEM/', 'C:/WINNT/SYSTEM/', 'C:/WINDOWS/SYSTEM32/', 'C:/WINNT/SYSTEM32/', '/usr/bin/', '/usr/sbin/', '/usr/local/bin/', '/usr/local/sbin/', '/opt/', '/usr/imagemagick/', '/usr/bin/imagemagick/'); +			$path_locations = str_replace('\\', '/', (explode(($exe) ? ';' : ':', getenv('PATH')))); + +			$locations = array_merge($path_locations, $locations); +			foreach ($locations as $location) +			{ +				// The path might not end properly, fudge it +				if (substr($location, -1, 1) !== '/') +				{ +					$location .= '/'; +				} + +				if (@file_exists($location) && @is_readable($location . 'mogrify' . $exe) && @filesize($location . 'mogrify' . $exe) > 3000) +				{ +					$img_imagick = str_replace('\\', '/', $location); +					continue; +				} +			} +		} +		else +		{ +			$img_imagick = str_replace('\\', '/', $magic_home); +		} + +		$this->config->set('img_imagick', $img_imagick); +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 0; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return ''; +	} +} diff --git a/phpBB/phpbb/install/module/obtain_data/task/obtain_server_data.php b/phpBB/phpbb/install/module/obtain_data/task/obtain_server_data.php new file mode 100644 index 0000000000..1ef70eae08 --- /dev/null +++ b/phpBB/phpbb/install/module/obtain_data/task/obtain_server_data.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\install\module\obtain_data\task; + +use phpbb\install\exception\user_interaction_required_exception; + +/** + * This class requests and saves some information about the server + */ +class obtain_server_data extends \phpbb\install\task_base implements \phpbb\install\task_interface +{ +	/** +	 * @var \phpbb\install\helper\config +	 */ +	protected $install_config; + +	/** +	 * @var \phpbb\install\helper\iohandler\iohandler_interface +	 */ +	protected $io_handler; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\install\helper\config							$config		Installer's config +	 * @param \phpbb\install\helper\iohandler\iohandler_interface	$iohandler	Installer's input-output handler +	 */ +	public function __construct(\phpbb\install\helper\config $config, +								\phpbb\install\helper\iohandler\iohandler_interface $iohandler) +	{ +		$this->install_config	= $config; +		$this->io_handler		= $iohandler; + +		parent::__construct(true); +	} +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		$cookie_secure = $this->io_handler->is_secure(); +		$server_protocol = ($this->io_handler->is_secure()) ? 'https://' : 'http://'; +		$server_port = $this->io_handler->get_server_variable('SERVER_PORT', 0); + +		// HTTP_HOST is having the correct browser url in most cases... +		$server_name = strtolower(htmlspecialchars_decode($this->io_handler->get_header_variable( +			'Host', +			$this->io_handler->get_server_variable('SERVER_NAME') +		))); + +		// HTTP HOST can carry a port number... +		if (strpos($server_name, ':') !== false) +		{ +			$server_name = substr($server_name, 0, strpos($server_name, ':')); +		} + +		$script_path = htmlspecialchars_decode($this->io_handler->get_server_variable('PHP_SELF')); + +		if (!$script_path) +		{ +			$script_path = htmlspecialchars_decode($this->io_handler->get_server_variable('REQUEST_URI')); +		} + +		$script_path = str_replace(array('\\', '//'), '/', $script_path); +		$script_path = trim(dirname(dirname(dirname($script_path)))); // Because we are in install/app.php/route_name + +		// Server data +		$cookie_secure		= $this->io_handler->get_input('cookie_secure', $cookie_secure); +		$server_protocol	= $this->io_handler->get_input('server_protocol', $server_protocol); +		$force_server_vars	= $this->io_handler->get_input('force_server_vars', 0); +		$server_name		= $this->io_handler->get_input('server_name', $server_name); +		$server_port		= $this->io_handler->get_input('server_port', $server_port); +		$script_path		= $this->io_handler->get_input('script_path', $script_path); + +		// Clean up script path +		if ($script_path !== '/') +		{ +			// Adjust destination path (no trailing slash) +			if (substr($script_path, -1) === '/') +			{ +				$script_path = substr($script_path, 0, -1); +			} + +			$script_path = str_replace(array('../', './'), '', $script_path); + +			if ($script_path[0] !== '/') +			{ +				$script_path = '/' . $script_path; +			} +		} + +		// Check if data is sent +		if ($this->io_handler->get_input('submit_server', false)) +		{ +			$this->install_config->set('cookie_secure', $cookie_secure); +			$this->install_config->set('server_protocol', $server_protocol); +			$this->install_config->set('force_server_vars', $force_server_vars); +			$this->install_config->set('server_name', $server_name); +			$this->install_config->set('server_port', $server_port); +			$this->install_config->set('script_path', $script_path); +		} +		else +		{ +			// Render form +			$server_form = array( +				'cookie_secure' => array( +					'label'			=> 'COOKIE_SECURE', +					'description'	=> 'COOKIE_SECURE_EXPLAIN', +					'type'			=> 'radio', +					'options'		=> array( +						array( +							'value'		=> 0, +							'label'		=> 'NO', +							'selected'	=> (!$cookie_secure), +						), +						array( +							'value'		=> 1, +							'label'		=> 'YES', +							'selected'	=> ($cookie_secure), +						), +					), +				), +				'force_server_vars' => array( +					'label'			=> 'FORCE_SERVER_VARS', +					'description'	=> 'FORCE_SERVER_VARS_EXPLAIN', +					'type'			=> 'radio', +					'options'		=> array( +						array( +							'value'		=> 0, +							'label'		=> 'NO', +							'selected'	=> true, +						), +						array( +							'value'		=> 1, +							'label'		=> 'YES', +							'selected'	=> false, +						), +					), +				), +				'server_protocol' => array( +					'label'			=> 'SERVER_PROTOCOL', +					'description'	=> 'SERVER_PROTOCOL_EXPLAIN', +					'type'			=> 'text', +					'default'		=> $server_protocol, +				), +				'server_name' => array( +					'label'			=> 'SERVER_NAME', +					'description'	=> 'SERVER_NAME_EXPLAIN', +					'type'			=> 'text', +					'default'		=> $server_name, +				), +				'server_port' => array( +					'label'			=> 'SERVER_PORT', +					'description'	=> 'SERVER_PORT_EXPLAIN', +					'type'			=> 'text', +					'default'		=> $server_port, +				), +				'script_path' => array( +					'label'			=> 'SCRIPT_PATH', +					'description'	=> 'SCRIPT_PATH_EXPLAIN', +					'type'			=> 'text', +					'default'		=> $script_path, +				), +				'submit_server' => array( +					'label'	=> 'SUBMIT', +					'type'	=> 'submit', +				) +			); + +			$this->io_handler->add_user_form_group('SERVER_CONFIG', $server_form); + +			throw new user_interaction_required_exception(); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 0; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return ''; +	} +} diff --git a/phpBB/phpbb/install/module/obtain_data/task/obtain_update_files.php b/phpBB/phpbb/install/module/obtain_data/task/obtain_update_files.php new file mode 100644 index 0000000000..0cb809154e --- /dev/null +++ b/phpBB/phpbb/install/module/obtain_data/task/obtain_update_files.php @@ -0,0 +1,113 @@ +<?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\install\module\obtain_data\task; + +use phpbb\install\exception\user_interaction_required_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\install\task_base; + +class obtain_update_files extends task_base +{ +	/** +	 * @var config +	 */ +	protected $installer_config; + +	/** +	 * @var iohandler_interface +	 */ +	protected $iohandler; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * @var string +	 */ +	protected $php_ext; + +	/** +	 * Constructor +	 * +	 * @param config				$config +	 * @param iohandler_interface	$iohandler +	 * @param string				$phpbb_root_path +	 * @param string				$php_ext +	 */ +	public function __construct(config $config, iohandler_interface $iohandler, $phpbb_root_path, $php_ext) +	{ +		$this->installer_config	= $config; +		$this->iohandler		= $iohandler; +		$this->phpbb_root_path	= $phpbb_root_path; +		$this->php_ext			= $php_ext; + +		parent::__construct(false); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function check_requirements() +	{ +		return $this->installer_config->get('do_update_files', false); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		// Load update info file +		// The file should be checked in the requirements, so we assume that it exists +		$update_info_file = $this->phpbb_root_path . 'install/update/index.' . $this->php_ext; +		include($update_info_file); +		$info = (empty($update_info) || !is_array($update_info)) ? false : $update_info; + +		// If the file is invalid, abort mission +		if (!$info) +		{ +			$this->iohandler->add_error_message('WRONG_INFO_FILE_FORMAT'); +			throw new user_interaction_required_exception(); +		} + +		// Replace .php with $this->php_ext if needed +		if ($this->php_ext !== 'php') +		{ +			$custom_extension = '.' . $this->php_ext; +			$info['files'] = preg_replace('#\.php$#i', $custom_extension, $info['files']); +		} + +		// Save update info +		$this->installer_config->set('update_info_unprocessed', $info); +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 0; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return ''; +	} +} diff --git a/phpBB/phpbb/install/module/obtain_data/task/obtain_update_ftp_data.php b/phpBB/phpbb/install/module/obtain_data/task/obtain_update_ftp_data.php new file mode 100644 index 0000000000..f31472fc58 --- /dev/null +++ b/phpBB/phpbb/install/module/obtain_data/task/obtain_update_ftp_data.php @@ -0,0 +1,163 @@ +<?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\install\module\obtain_data\task; + +use phpbb\install\exception\user_interaction_required_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\install\helper\update_helper; +use phpbb\install\task_base; + +class obtain_update_ftp_data extends task_base +{ +	/** +	 * @var \phpbb\install\helper\config +	 */ +	protected $installer_config; + +	/** +	 * @var \phpbb\install\helper\iohandler\iohandler_interface +	 */ +	protected $iohandler; + +	/** +	 * @var update_helper +	 */ +	protected $update_helper; + +	/** +	 * @var string +	 */ +	protected $php_ext; + +	/** +	 * Constructor +	 * +	 * @param config				$installer_config +	 * @param iohandler_interface	$iohandler +	 * @param update_helper			$update_helper +	 * @param string				$php_ext +	 */ +	public function __construct(config $installer_config, iohandler_interface $iohandler, update_helper $update_helper, $php_ext) +	{ +		$this->installer_config	= $installer_config; +		$this->iohandler		= $iohandler; +		$this->update_helper	= $update_helper; +		$this->php_ext			= $php_ext; + +		parent::__construct(false); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function check_requirements() +	{ +		return ($this->installer_config->get('do_update_files', false) && +			($this->installer_config->get('file_update_method', '') === 'ftp') +		); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		if ($this->iohandler->get_input('submit_ftp', false)) +		{ +			$this->update_helper->include_file('includes/functions_transfer.' . $this->php_ext); + +			$method = 'ftp'; +			$methods = \transfer::methods(); +			if (!in_array($method, $methods, true)) +			{ +				$method = $methods[0]; +			} + +			$ftp_host = $this->iohandler->get_input('ftp_host', ''); +			$ftp_user = $this->iohandler->get_input('ftp_user', ''); +			$ftp_pass = htmlspecialchars_decode($this->iohandler->get_input('ftp_pass', '')); +			$ftp_path = $this->iohandler->get_input('ftp_path', ''); +			$ftp_port = $this->iohandler->get_input('ftp_port', 21); +			$ftp_time = $this->iohandler->get_input('ftp_timeout', 10); + +			$this->installer_config->set('ftp_host', $ftp_host); +			$this->installer_config->set('ftp_user', $ftp_user); +			$this->installer_config->set('ftp_pass', $ftp_pass); +			$this->installer_config->set('ftp_path', $ftp_path); +			$this->installer_config->set('ftp_port', (int) $ftp_port); +			$this->installer_config->set('ftp_timeout', (int) $ftp_time); +			$this->installer_config->set('ftp_method', $method); +		} +		else +		{ +			$this->iohandler->add_user_form_group('FTP_SETTINGS', array( +				'ftp_host'	=> array( +					'label'			=> 'FTP_HOST', +					'description'	=> 'FTP_HOST_EXPLAIN', +					'type'			=> 'text', +				), +				'ftp_user'	=> array( +					'label'			=> 'FTP_USERNAME', +					'description'	=> 'FTP_USERNAME_EXPLAIN', +					'type'			=> 'text', +				), +				'ftp_pass'	=> array( +					'label'			=> 'FTP_PASSWORD', +					'description'	=> 'FTP_PASSWORD_EXPLAIN', +					'type'			=> 'password', +				), +				'ftp_path'	=> array( +					'label'			=> 'FTP_ROOT_PATH', +					'description'	=> 'FTP_ROOT_PATH_EXPLAIN', +					'type'			=> 'text', +				), +				'ftp_port'	=> array( +					'label'			=> 'FTP_PORT', +					'description'	=> 'FTP_PORT_EXPLAIN', +					'type'			=> 'text', +					'default'		=> 21, +				), +				'ftp_timeout'	=> array( +					'label'			=> 'FTP_TIMEOUT', +					'description'	=> 'FTP_TIMEOUT_EXPLAIN', +					'type'			=> 'text', +					'default'		=> 10, +				), +				'submit_ftp'	=> array( +					'label'	=> 'SUBMIT', +					'type'	=> 'submit', +				), +			)); + +			throw new user_interaction_required_exception(); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 0; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return ''; +	} +} diff --git a/phpBB/phpbb/install/module/obtain_data/task/obtain_update_settings.php b/phpBB/phpbb/install/module/obtain_data/task/obtain_update_settings.php new file mode 100644 index 0000000000..3b24e8ba40 --- /dev/null +++ b/phpBB/phpbb/install/module/obtain_data/task/obtain_update_settings.php @@ -0,0 +1,123 @@ +<?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\install\module\obtain_data\task; + +use phpbb\install\exception\user_interaction_required_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\install\task_base; + +class obtain_update_settings extends task_base +{ +	/** +	 * @var \phpbb\install\helper\config +	 */ +	protected $installer_config; + +	/** +	 * @var \phpbb\install\helper\iohandler\iohandler_interface +	 */ +	protected $iohandler; + +	/** +	 * Constructor +	 * +	 * @param config				$installer_config +	 * @param iohandler_interface	$iohandler +	 */ +	public function __construct(config $installer_config, iohandler_interface $iohandler) +	{ +		$this->installer_config	= $installer_config; +		$this->iohandler		= $iohandler; + +		parent::__construct(true); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		// Check if data is sent +		if ($this->iohandler->get_input('submit_update', false)) +		{ +			$update_files = $this->iohandler->get_input('update_type', 'all') === 'all'; + +			if ($this->installer_config->get('disable_filesystem_update', false) && $update_files) +			{ +				$this->iohandler->add_error_message('UPDATE_FILES_NOT_FOUND'); + +				throw new user_interaction_required_exception(); +			} + +			$this->installer_config->set('do_update_files', $update_files); +		} +		else +		{ +			if ($this->installer_config->get('disable_filesystem_update', false)) +			{ +				$options[] = array( +					'value'		=> 'db_only', +					'label'		=> 'UPDATE_TYPE_DB_ONLY', +					'selected'	=> true, +				); +			} +			else +			{ +				$options = array( +					array( +						'value'		=> 'all', +						'label'		=> 'UPDATE_TYPE_ALL', +						'selected'	=> true, +					), +					array( +						'value'		=> 'db_only', +						'label'		=> 'UPDATE_TYPE_DB_ONLY', +						'selected'	=> false, +					), +				); +			} + +			$this->iohandler->add_user_form_group('UPDATE_TYPE', array( +				'update_type' => array( +					'label'		=> 'UPDATE_TYPE', +					'type'		=> 'radio', +					'options'	=> $options, +				), +				'submit_update' => array( +					'label'	=> 'SUBMIT', +					'type'	=> 'submit', +				), +			)); + +			throw new user_interaction_required_exception(); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 0; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return ''; +	} +} diff --git a/phpBB/phpbb/install/module/obtain_data/update_module.php b/phpBB/phpbb/install/module/obtain_data/update_module.php new file mode 100644 index 0000000000..c2f9019d34 --- /dev/null +++ b/phpBB/phpbb/install/module/obtain_data/update_module.php @@ -0,0 +1,33 @@ +<?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\install\module\obtain_data; + +class update_module extends \phpbb\install\module_base +{ +	/** +	 * {@inheritdoc} +	 */ +	public function get_navigation_stage_path() +	{ +		return array('update', 0, 'obtain_data'); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_step_count() +	{ +		return 0; +	} +} diff --git a/phpBB/phpbb/install/module/requirements/abstract_requirements_module.php b/phpBB/phpbb/install/module/requirements/abstract_requirements_module.php new file mode 100644 index 0000000000..121b4ff4e5 --- /dev/null +++ b/phpBB/phpbb/install/module/requirements/abstract_requirements_module.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\install\module\requirements; + +use phpbb\install\exception\user_interaction_required_exception; +use phpbb\install\module_base; + +/** + * Base class for requirements installer module + */ +abstract class abstract_requirements_module extends module_base +{ +	public function run() +	{ +		$tests_passed = true; +		foreach ($this->task_collection as $name => $task) +		{ +			// Check if we can run the task +			if (!$task->is_essential() && !$task->check_requirements()) +			{ +				continue; +			} + +			if ($this->allow_progress_bar) +			{ +				$this->install_config->increment_current_task_progress(); +			} + +			$test_result = $task->run(); +			$tests_passed = ($tests_passed) ? $test_result : false; +		} + +		// Module finished, so clear task progress +		$this->install_config->set_finished_task(0); + +		// Check if tests have failed +		if (!$tests_passed) +		{ +			// If requirements are not met, exit form installer +			// Set up UI for retesting +			$this->iohandler->add_user_form_group('', array( +				'install'	=> array( +					'label'	=> 'RETEST_REQUIREMENTS', +					'type'	=> 'submit', +				), +			)); + +			// Send the response and quit +			throw new user_interaction_required_exception(); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_step_count() +	{ +		return 0; +	} +} diff --git a/phpBB/phpbb/install/module/requirements/install_module.php b/phpBB/phpbb/install/module/requirements/install_module.php new file mode 100644 index 0000000000..ed0c5fbd94 --- /dev/null +++ b/phpBB/phpbb/install/module/requirements/install_module.php @@ -0,0 +1,25 @@ +<?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\install\module\requirements; + +class install_module extends abstract_requirements_module +{ +	/** +	 * {@inheritdoc} +	 */ +	public function get_navigation_stage_path() +	{ +		return array('install', 0, 'requirements'); +	} +} diff --git a/phpBB/phpbb/install/module/requirements/task/check_filesystem.php b/phpBB/phpbb/install/module/requirements/task/check_filesystem.php new file mode 100644 index 0000000000..868af39433 --- /dev/null +++ b/phpBB/phpbb/install/module/requirements/task/check_filesystem.php @@ -0,0 +1,279 @@ +<?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\install\module\requirements\task; + +/** + * Checks filesystem requirements + */ +class check_filesystem extends \phpbb\install\task_base +{ +	/** +	 * @var \phpbb\filesystem\filesystem_interface +	 */ +	protected $filesystem; + +	/** +	 * @var array +	 */ +	protected $files_to_check; + +	/** +	 * @var bool +	 */ +	protected $tests_passed; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * @var \phpbb\install\helper\iohandler\iohandler_interface +	 */ +	protected $response; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\filesystem\filesystem_interface				$filesystem			filesystem handler +	 * @param \phpbb\install\helper\iohandler\iohandler_interface	$response			response helper +	 * @param string												$phpbb_root_path	relative path to phpBB's root +	 * @param string												$php_ext			extension of php files +	 * @param bool													$check_config_php	Whether or not to check if config.php is writable +	 */ +	public function __construct(\phpbb\filesystem\filesystem_interface $filesystem, \phpbb\install\helper\iohandler\iohandler_interface $response, $phpbb_root_path, $php_ext, $check_config_php = true) +	{ +		parent::__construct(true); + +		$this->filesystem		= $filesystem; +		$this->response			= $response; +		$this->phpbb_root_path	= $phpbb_root_path; + +		$this->tests_passed = false; + +		// Files/Directories to check +		// All file/directory names must be relative to phpBB's root path +		$this->files_to_check = array( +			array( +				'path' => 'cache/', +				'failable' => false, +				'is_file' => false, +			), +			array( +				'path' => 'store/', +				'failable' => false, +				'is_file' => false, +			), +			array( +				'path' => 'files/', +				'failable' => false, +				'is_file' => false, +			), +			array( +				'path' => 'images/avatars/upload/', +				'failable' => true, +				'is_file' => false, +			), +		); + +		if ($check_config_php) +		{ +			$this->files_to_check[] = array( +				'path' => "config.$php_ext", +				'failable' => false, +				'is_file' => true, +			); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		$this->tests_passed = true; + +		// Check files/directories to be writable +		foreach ($this->files_to_check as $file) +		{ +			if ($file['is_file']) +			{ +				$this->check_file($file['path'], $file['failable']); +			} +			else +			{ +				$this->check_dir($file['path'], $file['failable']); +			} +		} + +		return $this->tests_passed; +	} + +	/** +	 * Sets $this->tests_passed +	 * +	 * @param	bool	$is_passed +	 */ +	protected function set_test_passed($is_passed) +	{ +		// If one test failed, tests_passed should be false +		$this->tests_passed = (!$this->tests_passed) ? false : $is_passed; +	} + +	/** +	 * Check if a file is readable and writable +	 * +	 * @param string	$file		Filename +	 * @param bool		$failable	Whether failing test should interrupt installation process +	 */ +	protected function check_file($file, $failable = false) +	{ +		$path = $this->phpbb_root_path . $file; +		$exists = $writable = true; + +		// Try to create file if it does not exists +		if (!file_exists($path)) +		{ +			$fp = @fopen($path, 'w'); +			@fclose($fp); +			try +			{ +				$this->filesystem->phpbb_chmod($path, +					\phpbb\filesystem\filesystem_interface::CHMOD_READ | \phpbb\filesystem\filesystem_interface::CHMOD_WRITE +				); +				$exists = true; +			} +			catch (\phpbb\filesystem\exception\filesystem_exception $e) +			{ +				// Do nothing +			} +		} + +		if (file_exists($path)) +		{ +			if (!$this->filesystem->is_writable($path)) +			{ +				$writable = false; +			} +		} +		else +		{ +			$exists = $writable = false; +		} + +		$this->set_test_passed(($exists && $writable) || $failable); + +		if (!($exists && $writable)) +		{ +			$title = ($exists) ? 'FILE_NOT_WRITABLE' : 'FILE_NOT_EXISTS'; +			$lang_suffix = '_EXPLAIN'; +			$lang_suffix .= ($failable) ? '_OPTIONAL' : ''; +			$description = array($title . $lang_suffix, $file); + +			if ($failable) +			{ +				$this->response->add_warning_message($title, $description); +			} +			else +			{ +				$this->response->add_error_message($title, $description); +			} +		} +	} + +	/** +	 * Check if a directory is readable and writable +	 * +	 * @param string	$dir		Filename +	 * @param bool		$failable	Whether failing test should abort the installation process +	 */ +	protected function check_dir($dir, $failable = false) +	{ +		$path = $this->phpbb_root_path . $dir; +		$exists = $writable = false; + +		// Try to create the directory if it does not exist +		if (!file_exists($path)) +		{ +			try +			{ +				$this->filesystem->mkdir($path, 0777); +				$this->filesystem->phpbb_chmod($path, +					\phpbb\filesystem\filesystem_interface::CHMOD_READ | \phpbb\filesystem\filesystem_interface::CHMOD_WRITE +				); +				$exists = true; +			} +			catch (\phpbb\filesystem\exception\filesystem_exception $e) +			{ +				// Do nothing +			} +		} + +		// Now really check +		if (file_exists($path) && is_dir($path)) +		{ +			try +			{ +				$exists = true; +				$this->filesystem->phpbb_chmod($path, +					\phpbb\filesystem\filesystem_interface::CHMOD_READ | \phpbb\filesystem\filesystem_interface::CHMOD_WRITE +				); +			} +			catch (\phpbb\filesystem\exception\filesystem_exception $e) +			{ +				// Do nothing +			} +		} + +		if ($this->filesystem->is_writable($path)) +		{ +			$writable = true; +		} + +		$this->set_test_passed(($exists && $writable) || $failable); + +		if (!($exists && $writable)) +		{ +			$title = ($exists) ? 'DIRECTORY_NOT_WRITABLE' : 'DIRECTORY_NOT_EXISTS'; +			$lang_suffix = '_EXPLAIN'; +			$lang_suffix .= ($failable) ? '_OPTIONAL' : ''; +			$description = array($title . $lang_suffix, $dir); + +			if ($failable) +			{ +				$this->response->add_warning_message($title, $description); +			} +			else +			{ +				$this->response->add_error_message($title, $description); +			} +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 0; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return ''; +	} +} diff --git a/phpBB/phpbb/install/module/requirements/task/check_server_environment.php b/phpBB/phpbb/install/module/requirements/task/check_server_environment.php new file mode 100644 index 0000000000..29f9777747 --- /dev/null +++ b/phpBB/phpbb/install/module/requirements/task/check_server_environment.php @@ -0,0 +1,209 @@ +<?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\install\module\requirements\task; + +/** + * Installer task that checks if the server meats phpBB requirements + */ +class check_server_environment extends \phpbb\install\task_base +{ +	/** +	 * @var \phpbb\install\helper\database +	 */ +	protected $database_helper; + +	/** +	 * @var \phpbb\install\helper\iohandler\iohandler_interface +	 */ +	protected $response_helper; + +	/** +	 * @var bool +	 */ +	protected $tests_passed; + +	/** +	 * Constructor +	 * +	 * @param	\phpbb\install\helper\database	$database_helper +	 * @param	\phpbb\install\helper\iohandler\iohandler_interface	$response +	 */ +	public function __construct(\phpbb\install\helper\database $database_helper, +								\phpbb\install\helper\iohandler\iohandler_interface $response) +	{ +		$this->database_helper	= $database_helper; +		$this->response_helper	= $response; +		$this->tests_passed		= true; + +		parent::__construct(true); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		// +		// Check requirements +		// The error messages should be set in the check_ functions +		// + +		// Check PHP version +		$this->check_php_version(); + +		// Check for getimagesize() +		$this->check_image_size(); + +		// Check for PCRE support +		$this->check_pcre(); + +		// Check for JSON support +		$this->check_json(); + +		// XML extension support check +		$this->check_xml(); + +		// Check for dbms support +		$this->check_available_dbms(); + +		return $this->tests_passed; +	} + +	/** +	 * Sets $this->tests_passed +	 * +	 * @param	bool	$is_passed +	 */ +	protected function set_test_passed($is_passed) +	{ +		// If one test failed, tests_passed should be false +		$this->tests_passed = (!$this->tests_passed) ? false : $is_passed; +	} + +	/** +	 * Check if the requirements for PHP version is met +	 */ +	protected function check_php_version() +	{ +		$php_version = PHP_VERSION; + +		if (version_compare($php_version, '5.4') < 0) +		{ +			$this->response_helper->add_error_message('PHP_VERSION_REQD', 'PHP_VERSION_REQD_EXPLAIN'); + +			$this->set_test_passed(false); +			return; +		} + +		$this->set_test_passed(true); +	} + +	/** +	 * Checks if the installed PHP has getimagesize() available +	 */ +	protected function check_image_size() +	{ +		if (!@function_exists('getimagesize')) +		{ +			$this->response_helper->add_error_message('PHP_GETIMAGESIZE_SUPPORT', 'PHP_GETIMAGESIZE_SUPPORT_EXPLAIN'); + +			$this->set_test_passed(false); +			return; +		} + +		$this->set_test_passed(true); +	} + +	/** +	 * Checks if the installed PHP supports PCRE +	 */ +	protected function check_pcre() +	{ +		if (@preg_match('//u', '')) +		{ +			$this->set_test_passed(true); +			return; +		} + +		$this->response_helper->add_error_message('PCRE_UTF_SUPPORT', 'PCRE_UTF_SUPPORT_EXPLAIN'); + +		$this->set_test_passed(false); +	} + +	/** +	 * Checks whether PHP's JSON extension is available or not +	 */ +	protected function check_json() +	{ +		if (@extension_loaded('json')) +		{ +			$this->set_test_passed(true); +			return; +		} + +		$this->response_helper->add_error_message('PHP_JSON_SUPPORT', 'PHP_JSON_SUPPORT_EXPLAIN'); + +		$this->set_test_passed(false); +	} + +	/** +	 * Checks whether or not the XML PHP extension is available (Required by the text formatter) +	 */ +	protected function check_xml() +	{ +		if (class_exists('DOMDocument')) +		{ +			$this->set_test_passed(true); +			return; +		} + +		$this->response_helper->add_error_message('PHP_XML_SUPPORT', 'PHP_XML_SUPPORT_EXPLAIN'); + +		$this->set_test_passed(false); +	} + +	/** +	 * Check if any supported DBMS is available +	 */ +	protected function check_available_dbms() +	{ +		$available_dbms = $this->database_helper->get_available_dbms(false, true); + +		if ($available_dbms['ANY_DB_SUPPORT']) +		{ +			$this->set_test_passed(true); +			return; +		} + +		$this->response_helper->add_error_message('PHP_SUPPORTED_DB', 'PHP_SUPPORTED_DB_EXPLAIN'); + +		$this->set_test_passed(false); +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 0; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return ''; +	} +} diff --git a/phpBB/phpbb/install/module/requirements/task/check_update.php b/phpBB/phpbb/install/module/requirements/task/check_update.php new file mode 100644 index 0000000000..cd66ffc8f9 --- /dev/null +++ b/phpBB/phpbb/install/module/requirements/task/check_update.php @@ -0,0 +1,195 @@ +<?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\install\module\requirements\task; + +use phpbb\filesystem\filesystem; +use phpbb\install\helper\config; +use phpbb\install\helper\container_factory; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\install\helper\update_helper; +use phpbb\install\task_base; + +/** + * Check the availability of updater files and update version + */ +class check_update extends task_base +{ +	/** +	 * @var \phpbb\config\db +	 */ +	protected $config; + +	/** +	 * @var filesystem +	 */ +	protected $filesystem; + +	/** +	 * @var config +	 */ +	protected $installer_config; + +	/** +	 * @var iohandler_interface +	 */ +	protected $iohandler; + +	/** +	 * @var update_helper +	 */ +	protected $update_helper; + +	/** +	 * @var \phpbb\version_helper +	 */ +	protected $version_helper; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * @var string +	 */ +	protected $php_ext; + +	/** +	 * @var bool +	 */ +	protected $tests_passed; + +	/** +	 * Constructor +	 * +	 * @param container_factory		$container +	 * @param filesystem			$filesystem +	 * @param config				$config +	 * @param iohandler_interface	$iohandler +	 * @param update_helper			$update_helper +	 * @param string				$phpbb_root_path +	 * @param string				$php_ext +	 */ +	public function __construct(container_factory $container, filesystem $filesystem, config $config, iohandler_interface $iohandler, update_helper $update_helper, $phpbb_root_path, $php_ext) +	{ +		$this->filesystem		= $filesystem; +		$this->installer_config	= $config; +		$this->iohandler		= $iohandler; +		$this->update_helper	= $update_helper; +		$this->phpbb_root_path	= $phpbb_root_path; +		$this->php_ext			= $php_ext; +		$this->tests_passed		= true; + +		$this->config			= $container->get('config'); +		$this->version_helper	= $container->get('version_helper'); + +		parent::__construct(true); +	} + +	/** +	 * Sets $this->tests_passed +	 * +	 * @param	bool	$is_passed +	 */ +	protected function set_test_passed($is_passed) +	{ +		// If one test failed, tests_passed should be false +		$this->tests_passed = $this->tests_passed && $is_passed; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		// Array of update files +		$update_files = array( +			$this->phpbb_root_path . 'install/update', +			$this->phpbb_root_path . 'install/update/index.' . $this->php_ext, +		); + +		// Check for a valid update directory +		if (!$this->filesystem->exists($update_files) || !$this->filesystem->is_readable($update_files)) +		{ +			$this->iohandler->add_warning_message('UPDATE_FILES_NOT_FOUND'); +			$this->set_test_passed(false); + +			// If there are no update files, we can't check the version etc +			// However, we can let the users run migrations if they really want to... +			$this->installer_config->set('disable_filesystem_update', true); +			return true; +		} + +		// Recover version numbers +		$update_info = array(); +		@include($this->phpbb_root_path . 'install/update/index.' . $this->php_ext); +		$info = (empty($update_info) || !is_array($update_info)) ? false : $update_info; +		$update_version = false; + +		if ($info !== false) +		{ +			$update_version = (!empty($info['version']['to'])) ? trim($info['version']['to']) : false; +		} + +		// Get current and latest version +		try +		{ +			$latest_version = $this->version_helper->get_latest_on_current_branch(true); +		} +		catch (\RuntimeException $e) +		{ +			$latest_version = $update_version; +		} + +		$current_version = (!empty($this->config['version_update_from'])) ? $this->config['version_update_from'] : $this->config['version']; + +		// Check if the update package +		if (!$this->update_helper->phpbb_version_compare($current_version, $update_version, '<')) +		{ +			$this->iohandler->add_error_message('NO_UPDATE_FILES_UP_TO_DATE'); +			$this->tests_passed = false; +		} + +		// Check if the update package works with the installed version +		if (empty($info['version']['from']) || $info['version']['from'] !== $current_version) +		{ +			$this->iohandler->add_error_message(array('INCOMPATIBLE_UPDATE_FILES', $current_version, $info['version']['from'], $update_version)); +			$this->tests_passed = false; +		} + +		// check if this is the latest update package +		if ($this->update_helper->phpbb_version_compare($update_version, $latest_version, '<')) +		{ +			$this->iohandler->add_warning_message(array('OLD_UPDATE_FILES', $info['version']['from'], $update_version, $latest_version)); +		} + +		return $this->tests_passed; +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 0; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return ''; +	} +} diff --git a/phpBB/phpbb/install/module/requirements/update_module.php b/phpBB/phpbb/install/module/requirements/update_module.php new file mode 100644 index 0000000000..223d12faf3 --- /dev/null +++ b/phpBB/phpbb/install/module/requirements/update_module.php @@ -0,0 +1,25 @@ +<?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\install\module\requirements; + +class update_module extends abstract_requirements_module +{ +	/** +	 * {@inheritdoc} +	 */ +	public function get_navigation_stage_path() +	{ +		return array('update', 0, 'requirements'); +	} +} diff --git a/phpBB/phpbb/install/module/update_database/module.php b/phpBB/phpbb/install/module/update_database/module.php new file mode 100644 index 0000000000..ee38afe17d --- /dev/null +++ b/phpBB/phpbb/install/module/update_database/module.php @@ -0,0 +1,33 @@ +<?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\install\module\update_database; + +class module extends \phpbb\install\module_base +{ +	/** +	 * {@inheritdoc} +	 */ +	public function get_navigation_stage_path() +	{ +		return array('update', 0, 'update_database'); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_step_count() +	{ +		return 0; +	} +} diff --git a/phpBB/phpbb/install/module/update_database/task/update.php b/phpBB/phpbb/install/module/update_database/task/update.php new file mode 100644 index 0000000000..9fed2317e9 --- /dev/null +++ b/phpBB/phpbb/install/module/update_database/task/update.php @@ -0,0 +1,211 @@ +<?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\install\module\update_database\task; + +use phpbb\db\migration\exception; +use phpbb\db\output_handler\installer_migrator_output_handler; +use phpbb\db\output_handler\log_wrapper_migrator_output_handler; +use phpbb\install\exception\resource_limit_reached_exception; +use phpbb\install\exception\user_interaction_required_exception; +use phpbb\install\task_base; + +/** + * Database updater task + */ +class update extends task_base +{ +	/** +	 * @var \phpbb\cache\driver\driver_interface +	 */ +	protected $cache; + +	/** +	 * @var \phpbb\config\config +	 */ +	protected $config; + +	/** +	 * @var \phpbb\extension\manager +	 */ +	protected $extension_manager; + +	/** +	 * @var \phpbb\filesystem\filesystem +	 */ +	protected $filesystem; + +	/** +	 * @var \phpbb\install\helper\config +	 */ +	protected $installer_config; + +	/** +	 * @var \phpbb\install\helper\iohandler\iohandler_interface +	 */ +	protected $iohandler; + +	/** +	 * @var \phpbb\language\language +	 */ +	protected $language; + +	/** +	 * @var \phpbb\log\log +	 */ +	protected $log; + +	/** +	 * @var \phpbb\db\migrator +	 */ +	protected $migrator; + +	/** +	 * @var \phpbb\user +	 */ +	protected $user; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\install\helper\container_factory				$container +	 * @param \phpbb\filesystem\filesystem							$filesystem +	 * @param \phpbb\install\helper\config							$installer_config +	 * @param \phpbb\install\helper\iohandler\iohandler_interface	$iohandler +	 * @param \phpbb\language\language								$language +	 * @param string												$phpbb_root_path +	 */ +	public function __construct(\phpbb\install\helper\container_factory $container, \phpbb\filesystem\filesystem $filesystem, \phpbb\install\helper\config $installer_config, \phpbb\install\helper\iohandler\iohandler_interface $iohandler, \phpbb\language\language $language, $phpbb_root_path) +	{ +		$this->filesystem			= $filesystem; +		$this->installer_config		= $installer_config; +		$this->iohandler			= $iohandler; +		$this->language				= $language; +		$this->phpbb_root_path		= $phpbb_root_path; + +		$this->cache				= $container->get('cache.driver'); +		$this->config				= $container->get('config'); +		$this->extension_manager	= $container->get('ext.manager'); +		$this->log					= $container->get('log'); +		$this->migrator				= $container->get('migrator'); +		$this->user					= $container->get('user'); + +		parent::__construct(true); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		$this->language->add_lang('migrator'); + +		if (!isset($this->config['version_update_from'])) +		{ +			$this->config->set('version_update_from', $this->config['version']); +		} + +		$original_version = $this->config['version_update_from']; + +		$this->migrator->set_output_handler( +			new log_wrapper_migrator_output_handler( +				$this->language, +				new installer_migrator_output_handler($this->iohandler), +				$this->phpbb_root_path . 'store/migrations_' . time() . '.log', +				$this->filesystem +			) +		); + +		$this->migrator->create_migrations_table(); + +		$migrations = $this->extension_manager +			->get_finder() +			->core_path('phpbb/db/migration/data/') +			->extension_directory('/migrations') +			->get_classes(); + +		$this->migrator->set_migrations($migrations); +		$migration_count = count($this->migrator->get_migrations()); +		$this->iohandler->set_task_count($migration_count, true); +		$this->installer_config->set_task_progress_count($migration_count); +		$progress_count = $this->installer_config->get('database_update_count', 0); + +		while (!$this->migrator->finished()) +		{ +			try +			{ +				$this->migrator->update(); +				$progress_count++; +				$this->iohandler->set_progress('STAGE_UPDATE_DATABASE', $progress_count); +			} +			catch (exception $e) +			{ +				$msg = $e->getParameters(); +				array_unshift($msg, $e->getMessage()); + +				$this->iohandler->add_error_message($msg); +				throw new user_interaction_required_exception(); +			} + +			if ($this->installer_config->get_time_remaining() <= 0 || $this->installer_config->get_memory_remaining() <= 0) +			{ +				$this->installer_config->set('database_update_count', $progress_count); +				throw new resource_limit_reached_exception(); +			} +		} + +		if ($original_version !== $this->config['version']) +		{ +			$this->log->add( +				'admin', +				(isset($this->user->data['user_id'])) ? $this->user->data['user_id'] : ANONYMOUS, +				$this->user->ip, +				'LOG_UPDATE_DATABASE', +				false, +				array( +					$original_version, +					$this->config['version'] +				) +			); +		} + +		$this->iohandler->add_success_message('INLINE_UPDATE_SUCCESSFUL'); + +		$this->config->delete('version_update_from'); + +		$this->cache->purge(); + +		$this->config->increment('assets_version', 1); +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 0; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return ''; +	} +} diff --git a/phpBB/phpbb/install/module/update_filesystem/module.php b/phpBB/phpbb/install/module/update_filesystem/module.php new file mode 100644 index 0000000000..157c78a1ac --- /dev/null +++ b/phpBB/phpbb/install/module/update_filesystem/module.php @@ -0,0 +1,33 @@ +<?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\install\module\update_filesystem; + +class module extends \phpbb\install\module_base +{ +	/** +	 * {@inheritdoc} +	 */ +	public function get_navigation_stage_path() +	{ +		return array('update', 0, 'update_files'); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_step_count() +	{ +		return 0; +	} +} diff --git a/phpBB/phpbb/install/module/update_filesystem/task/diff_files.php b/phpBB/phpbb/install/module/update_filesystem/task/diff_files.php new file mode 100644 index 0000000000..e3e6db6263 --- /dev/null +++ b/phpBB/phpbb/install/module/update_filesystem/task/diff_files.php @@ -0,0 +1,205 @@ +<?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\install\module\update_filesystem\task; + +use phpbb\install\exception\resource_limit_reached_exception; +use phpbb\install\exception\user_interaction_required_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\container_factory; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\install\helper\update_helper; +use phpbb\install\task_base; + +/** + * Merges user made changes into the files + */ +class diff_files extends task_base +{ +	/** +	 * @var \phpbb\cache\driver\driver_interface +	 */ +	protected $cache; + +	/** +	 * @var config +	 */ +	protected $installer_config; + +	/** +	 * @var iohandler_interface +	 */ +	protected $iohandler; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * @var string +	 */ +	protected $php_ext; + +	/** +	 * @var update_helper +	 */ +	protected $update_helper; + +	/** +	 * Constructor +	 * +	 * @param container_factory		$container +	 * @param config				$config +	 * @param iohandler_interface	$iohandler +	 * @param update_helper			$update_helper +	 * @param string				$phpbb_root_path +	 * @param string				$php_ext +	 */ +	public function __construct(container_factory $container, config $config, iohandler_interface $iohandler, update_helper $update_helper, $phpbb_root_path, $php_ext) +	{ +		$this->installer_config	= $config; +		$this->iohandler		= $iohandler; +		$this->update_helper	= $update_helper; +		$this->phpbb_root_path	= $phpbb_root_path; +		$this->php_ext			= $php_ext; + +		$this->cache			= $container->get('cache.driver'); + +		parent::__construct(false); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function check_requirements() +	{ +		$files_to_diff = $this->installer_config->get('update_files', array()); +		$files_to_diff = (isset($files_to_diff['update_with_diff'])) ? $files_to_diff['update_with_diff'] : array(); + +		return $this->installer_config->get('do_update_files', false) && count($files_to_diff) > 0; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		// Include diff engine +		$this->update_helper->include_file('includes/diff/diff.' . $this->php_ext); +		$this->update_helper->include_file('includes/diff/engine.' . $this->php_ext); + +		// Set up basic vars +		$old_path = $this->update_helper->get_path_to_old_update_files(); +		$new_path = $this->update_helper->get_path_to_new_update_files(); + +		$files_to_diff = $this->installer_config->get('update_files', array()); +		$files_to_diff = $files_to_diff['update_with_diff']; + +		// Set progress bar +		$this->iohandler->set_task_count(count($files_to_diff), true); +		$this->iohandler->set_progress('UPDATE_FILE_DIFF', 0); +		$progress_count = $this->installer_config->get('file_diff_update_count', 0); + +		// Recover progress +		$progress_key = $this->installer_config->get('differ_progress_key', -1); +		$progress_recovered = ($progress_key === -1); +		$merge_conflicts = $this->installer_config->get('merge_conflict_list', array()); + +		foreach ($files_to_diff as $key => $filename) +		{ +			if ($progress_recovered === false) +			{ +				if ($progress_key === $key) +				{ +					$progress_recovered = true; +				} + +				continue; +			} + +			// Read in files' content +			$file_contents = array(); + +			// Handle the special case when user created a file with the filename that is now new in the core +			$file_contents[0] = (file_exists($old_path . $filename)) ? file_get_contents($old_path . $filename) : ''; + +			$filenames = array( +				$this->phpbb_root_path . $filename, +				$new_path . $filename +			); + +			foreach ($filenames as $file_to_diff) +			{ +				$file_contents[] = file_get_contents($file_to_diff); + +				if ($file_contents[sizeof($file_contents) - 1] === false) +				{ +					$this->iohandler->add_error_message(array('FILE_DIFFER_ERROR_FILE_CANNOT_BE_READ', $files_to_diff)); +					unset($file_contents); +					throw new user_interaction_required_exception(); +				} +			} + +			$diff = new \diff3($file_contents[0], $file_contents[1], $file_contents[2]); +			unset($file_contents); + +			// Handle conflicts +			if ($diff->get_num_conflicts() !== 0) +			{ +				$merge_conflicts[] = $filename; +			} + +			// Save merged output +			$this->cache->put( +				'_file_' . md5($filename), +				base64_encode(implode("\n", $diff->merged_output())) +			); + +			unset($diff); + +			$progress_count++; +			$this->iohandler->set_progress('UPDATE_FILE_DIFF', $progress_count); + +			if ($this->installer_config->get_time_remaining() <= 0 || $this->installer_config->get_memory_remaining() <= 0) +			{ +				// Save differ progress +				$this->installer_config->set('differ_progress_key', $key); +				$this->installer_config->set('merge_conflict_list', $merge_conflicts); +				$this->installer_config->set('file_diff_update_count', $progress_count); + +				// Request refresh +				throw new resource_limit_reached_exception(); +			} +		} + +		$this->iohandler->finish_progress('ALL_FILES_DIFFED'); +		$this->installer_config->set('merge_conflict_list', $merge_conflicts); +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 0; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return ''; +	} +} diff --git a/phpBB/phpbb/install/module/update_filesystem/task/download_updated_files.php b/phpBB/phpbb/install/module/update_filesystem/task/download_updated_files.php new file mode 100644 index 0000000000..f911b7ac62 --- /dev/null +++ b/phpBB/phpbb/install/module/update_filesystem/task/download_updated_files.php @@ -0,0 +1,123 @@ +<?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\install\module\update_filesystem\task; + +use phpbb\filesystem\filesystem; +use phpbb\install\exception\jump_to_restart_point_exception; +use phpbb\install\exception\user_interaction_required_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\install\task_base; + +class download_updated_files extends task_base +{ +	/** +	 * @var config +	 */ +	protected $installer_config; + +	/** +	 * @var filesystem +	 */ +	protected $filesystem; + +	/** +	 * @var iohandler_interface +	 */ +	protected $iohandler; + +	/** +	 * Constructor +	 * +	 * @param config				$config +	 * @param iohandler_interface	$iohandler +	 * @param filesystem			$filesystem +	 */ +	public function __construct(config $config, iohandler_interface $iohandler, filesystem $filesystem) +	{ +		$this->installer_config	= $config; +		$this->iohandler		= $iohandler; +		$this->filesystem		= $filesystem; + +		parent::__construct(false); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function check_requirements() +	{ +		return $this->installer_config->get('do_update_files', false) +			&& $this->installer_config->get('file_update_method', '') === 'compression'; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		if ($this->iohandler->get_input('database_update_submit', false)) +		{ +			// Remove archive +			$this->filesystem->remove( +				$this->installer_config->get('update_file_archive', null) +			); + +			$this->installer_config->set('update_file_archive', null); +		} +		else if ($this->iohandler->get_input('update_recheck_files_submit', false)) +		{ +			throw new jump_to_restart_point_exception('check_update_files'); +		} +		else +		{ +			// Render download box +			$this->iohandler->add_download_link( +				'phpbb_installer_update_file_download', +				'DOWNLOAD_UPDATE_METHOD', +				'DOWNLOAD_UPDATE_METHOD_EXPLAIN' +			); + +			// Add form to continue update +			$this->iohandler->add_user_form_group('UPDATE_CONTINUE_UPDATE_PROCESS', array( +				'update_recheck_files_submit'	=> array( +					'label'	=> 'UPDATE_RECHECK_UPDATE_FILES', +					'type'	=> 'submit', +				), +				'database_update_submit'	=> array( +					'label'	=> 'UPDATE_CONTINUE_UPDATE_PROCESS', +					'type'	=> 'submit', +				), +			)); + +			throw new user_interaction_required_exception(); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 0; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return ''; +	} +} diff --git a/phpBB/phpbb/install/module/update_filesystem/task/file_check.php b/phpBB/phpbb/install/module/update_filesystem/task/file_check.php new file mode 100644 index 0000000000..f4b3870148 --- /dev/null +++ b/phpBB/phpbb/install/module/update_filesystem/task/file_check.php @@ -0,0 +1,187 @@ +<?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\install\module\update_filesystem\task; + +use phpbb\filesystem\filesystem; +use phpbb\install\exception\resource_limit_reached_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\install\helper\update_helper; +use phpbb\install\task_base; + +/** + * Updater task performing file checking + */ +class file_check extends task_base +{ +	/** +	 * @var filesystem +	 */ +	protected $filesystem; + +	/** +	 * @var config +	 */ +	protected $installer_config; + +	/** +	 * @var iohandler_interface +	 */ +	protected $iohandler; + +	/** +	 * @var update_helper +	 */ +	protected $update_helper; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * Construct +	 * +	 * @param filesystem			$filesystem +	 * @param config				$config +	 * @param iohandler_interface	$iohandler +	 * @param update_helper			$update_helper +	 * @param string				$phpbb_root_path +	 */ +	public function __construct(filesystem $filesystem, config $config, iohandler_interface $iohandler, update_helper $update_helper, $phpbb_root_path) +	{ +		$this->filesystem		= $filesystem; +		$this->installer_config	= $config; +		$this->iohandler		= $iohandler; +		$this->update_helper	= $update_helper; +		$this->phpbb_root_path	= $phpbb_root_path; + +		parent::__construct(false); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function check_requirements() +	{ +		return $this->installer_config->get('do_update_files', false); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		if (!$this->installer_config->has_restart_point('check_update_files')) +		{ +			$this->installer_config->create_progress_restart_point('check_update_files'); +		} + +		$old_path = $this->update_helper->get_path_to_old_update_files(); +		$new_path = $this->update_helper->get_path_to_new_update_files(); + +		$update_info = $this->installer_config->get('update_info', array()); +		$file_update_info = $this->installer_config->get('update_files', array()); + +		if (empty($update_info)) +		{ +			$root_path = $this->phpbb_root_path; + +			$update_info = $this->installer_config->get('update_info_unprocessed', array()); + +			$file_update_info = array(); +			$file_update_info['update_without_diff'] = array_diff($update_info['binary'], $update_info['deleted']); + +			// Filter out files that are already deleted +			$file_update_info['delete'] = array_filter( +				$update_info['deleted'], +				function ($filename) use ($root_path) +				{ +					return file_exists($root_path . $filename); +				} +			); +		} + +		$progress_count = $this->installer_config->get('file_check_progress_count', 0); +		$task_count = count($update_info['files']); +		$this->iohandler->set_task_count($task_count); +		$this->iohandler->set_progress('UPDATE_CHECK_FILES', 0); + +		foreach ($update_info['files'] as $key => $filename) +		{ +			$old_file = $old_path . $filename; +			$new_file = $new_path . $filename; +			$file = $this->phpbb_root_path . $filename; + +			if ($this->installer_config->get_time_remaining() <= 0 || $this->installer_config->get_memory_remaining() <= 0) +			{ +				// Save progress +				$this->installer_config->set('update_info', $update_info); +				$this->installer_config->set('file_check_progress_count', $progress_count); +				$this->installer_config->set('update_files', $file_update_info); + +				// Request refresh +				throw new resource_limit_reached_exception(); +			} + +			$progress_count++; +			$this->iohandler->set_progress('UPDATE_CHECK_FILES', $progress_count); + +			if (!$this->filesystem->exists($file)) +			{ +				$file_update_info['new'][] = $filename; +			} +			else +			{ +				$file_checksum = md5_file($file); + +				if ($file_checksum === md5_file($new_file)) +				{ +					// File already up to date +					continue; +				} +				else if ($this->filesystem->exists($old_file) && $file_checksum === md5_file($old_file)) +				{ +					// No need to diff the file +					$file_update_info['update_without_diff'][] = $filename; +				} +				else +				{ +					$file_update_info['update_with_diff'][] = $filename; +				} +			} + +			unset($update_info['files'][$key]); +		} + +		$this->installer_config->set('update_files', $file_update_info); +		$this->installer_config->set('update_info', array()); +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 0; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return ''; +	} +} diff --git a/phpBB/phpbb/install/module/update_filesystem/task/show_file_status.php b/phpBB/phpbb/install/module/update_filesystem/task/show_file_status.php new file mode 100644 index 0000000000..7f18950cf6 --- /dev/null +++ b/phpBB/phpbb/install/module/update_filesystem/task/show_file_status.php @@ -0,0 +1,170 @@ +<?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\install\module\update_filesystem\task; + +use phpbb\filesystem\filesystem; +use phpbb\install\exception\user_interaction_required_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\container_factory; +use phpbb\install\helper\file_updater\factory; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\install\task_base; + +class show_file_status extends task_base +{ +	/** +	 * @var \phpbb\cache\driver\driver_interface +	 */ +	protected $cache; + +	/** +	 * @var filesystem +	 */ +	protected $filesystem; + +	/** +	 * @var config +	 */ +	protected $installer_config; + +	/** +	 * @var iohandler_interface +	 */ +	protected $iohandler; + +	/** +	 * @var \phpbb\install\helper\file_updater\compression_file_updater +	 */ +	protected $file_updater; + +	/** +	 * Constructor +	 * +	 * @param container_factory		$container +	 * @param config				$config +	 * @param iohandler_interface	$iohandler +	 * @param filesystem			$filesystem +	 * @param factory				$file_updater_factory +	 */ +	public function __construct(container_factory $container, config $config, iohandler_interface $iohandler, filesystem $filesystem, factory $file_updater_factory) +	{ +		$this->installer_config	= $config; +		$this->iohandler		= $iohandler; +		$this->filesystem		= $filesystem; + +		$this->cache = $container->get('cache.driver'); + +		// Initialize compression file updater +		$this->file_updater = $file_updater_factory->get('compression'); + +		parent::__construct(false); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function check_requirements() +	{ +		return $this->installer_config->get('do_update_files', false); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		if (!$this->iohandler->get_input('submit_continue_file_update', false)) +		{ +			// Handle merge conflicts +			$merge_conflicts = $this->installer_config->get('merge_conflict_list', array()); + +			// Create archive for merge conflicts +			if (!empty($merge_conflicts)) +			{ +				$compression_method = $this->installer_config->get('compression_method', ''); +				$conflict_archive = $this->file_updater->init($compression_method); +				$this->installer_config->set('update_file_conflict_archive', $conflict_archive); + +				foreach ($merge_conflicts as $filename) +				{ +					$this->file_updater->create_new_file( +						$filename, +						base64_decode($this->cache->get('_file_' . md5($filename))), +						true +					); +				} + +				// Render download box +				$this->iohandler->add_download_link( +					'phpbb_installer_update_conflict_download', +					'DOWNLOAD_CONFLICTS', +					'DOWNLOAD_CONFLICTS_EXPLAIN' +				); + +				$this->file_updater->close(); +			} + +			// Render update file statuses +			$file_update_info = $this->installer_config->get('update_files', array()); +			$file_status = array( +				'deleted'		=> (!isset($file_update_info['delete'])) ? array() : $file_update_info['delete'], +				'new'			=> (!isset($file_update_info['new'])) ? array() : $file_update_info['new'], +				'conflict'		=> $this->installer_config->get('merge_conflict_list', array()), +				'modified'		=> (!isset($file_update_info['update_with_diff'])) ? array() : $file_update_info['update_with_diff'], +				'not_modified'	=> (!isset($file_update_info['update_without_diff'])) ? array() : $file_update_info['update_without_diff'], +			); + +			$this->iohandler->render_update_file_status($file_status); + +			// Add form to continue update +			$this->iohandler->add_user_form_group('UPDATE_CONTINUE_FILE_UPDATE', array( +				'submit_continue_file_update'	=> array( +					'label'	=> 'UPDATE_CONTINUE_FILE_UPDATE', +					'type'	=> 'submit', +				), +			)); + +			// Show results to the user +			throw new user_interaction_required_exception(); +		} +		else +		{ +			$conflict_archive_path = $this->installer_config->get('update_file_conflict_archive', null); + +			// Remove archive +			if ($conflict_archive_path !== null && $this->filesystem->exists($conflict_archive_path)) +			{ +				$this->filesystem->remove($conflict_archive_path); +			} + +			$this->installer_config->set('update_file_conflict_archive', null); +		} +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 0; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return ''; +	} +} diff --git a/phpBB/phpbb/install/module/update_filesystem/task/update_files.php b/phpBB/phpbb/install/module/update_filesystem/task/update_files.php new file mode 100644 index 0000000000..fbb465cc66 --- /dev/null +++ b/phpBB/phpbb/install/module/update_filesystem/task/update_files.php @@ -0,0 +1,294 @@ +<?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\install\module\update_filesystem\task; + +use phpbb\exception\runtime_exception; +use phpbb\install\exception\resource_limit_reached_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\container_factory; +use phpbb\install\helper\file_updater\factory; +use phpbb\install\helper\file_updater\file_updater_interface; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\install\helper\update_helper; +use phpbb\install\task_base; + +/** + * File updater task + */ +class update_files extends task_base +{ +	/** +	 * @var \phpbb\cache\driver\driver_interface +	 */ +	protected $cache; + +	/** +	 * @var config +	 */ +	protected $installer_config; + +	/** +	 * @var iohandler_interface +	 */ +	protected $iohandler; + +	/** +	 * @var factory +	 */ +	protected $factory; + +	/** +	 * @var file_updater_interface +	 */ +	protected $file_updater; + +	/** +	 * @var update_helper +	 */ +	protected $update_helper; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * Constructor +	 * +	 * @param container_factory		$container +	 * @param config				$config +	 * @param iohandler_interface	$iohandler +	 * @param factory				$file_updater_factory +	 * @param update_helper			$update_helper +	 * @param string				$phpbb_root_path +	 */ +	public function __construct(container_factory $container, config $config, iohandler_interface $iohandler, factory $file_updater_factory, update_helper $update_helper, $phpbb_root_path) +	{ +		$this->factory			= $file_updater_factory; +		$this->installer_config	= $config; +		$this->iohandler		= $iohandler; +		$this->update_helper	= $update_helper; +		$this->phpbb_root_path	= $phpbb_root_path; + +		$this->cache			= $container->get('cache.driver'); +		$this->file_updater		= null; + +		parent::__construct(false); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function check_requirements() +	{ +		return $this->installer_config->get('do_update_files', false); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		$new_path = $this->update_helper->get_path_to_new_update_files(); + +		$file_update_info = $this->installer_config->get('update_files', array()); + +		$update_type_progress = $this->installer_config->get('file_updater_type_progress', ''); +		$update_elem_progress = $this->installer_config->get('file_updater_elem_progress', ''); +		$type_progress_found = false; +		$elem_progress_found = false; + +		// Progress bar +		$task_count = 0; +		foreach ($file_update_info as $sub_array) +		{ +			$task_count += count($sub_array); +		} + +		// Everything is up to date, so just continue +		if ($task_count === 0) +		{ +			return; +		} + +		$progress_count = $this->installer_config->get('file_update_progress_count', 0); +		$this->iohandler->set_task_count($task_count, true); +		$this->iohandler->set_progress('UPDATE_UPDATING_FILES', 0); + +		$this->file_updater = $this->get_file_updater(); + +		// File updater fallback logic +		try +		{ +			// Update files +			foreach ($file_update_info as $type => $file_update_vector) +			{ +				if (!$type_progress_found) +				{ +					if ($type === $update_type_progress || empty($update_elem_progress)) +					{ +						$type_progress_found = true; +					} +					else +					{ +						continue; +					} +				} + +				foreach ($file_update_vector as $path) +				{ +					if (!$elem_progress_found) +					{ +						if ($path === $update_elem_progress || empty($update_elem_progress)) +						{ +							$elem_progress_found = true; +						} +						else +						{ +							continue; +						} +					} + +					switch ($type) +					{ +						case 'delete': +							$this->file_updater->delete_file($path); +						break; +						case 'new': +							$this->file_updater->create_new_file($path, $new_path . $path); +						break; +						case 'update_without_diff': +							$this->file_updater->update_file($path, $new_path . $path); +						break; +						case 'update_with_diff': +							$this->file_updater->update_file( +								$path, +								base64_decode($this->cache->get('_file_' . md5($path))), +								true +							); +						break; +					} + +					// Save progress +					$this->installer_config->set('file_updater_type_progress', $type); +					$this->installer_config->set('file_updater_elem_progress', $path); +					$progress_count++; +					$this->iohandler->set_progress('UPDATE_UPDATING_FILES', $progress_count); + +					if ($this->installer_config->get_time_remaining() <= 0 || $this->installer_config->get_memory_remaining() <= 0) +					{ +						// Request refresh +						throw new resource_limit_reached_exception(); +					} +				} +			} + +			$this->iohandler->finish_progress('UPDATE_UPDATING_FILES'); +		} +		catch (runtime_exception $e) +		{ +			if ($e instanceof resource_limit_reached_exception) +			{ +				throw new resource_limit_reached_exception(); +			} + +			$current_method = $this->installer_config->get('file_update_method', ''); + +			// File updater failed, try to fallback to download file update mode +			if ($current_method !== 'compression') +			{ +				$this->iohandler->add_warning_message(array( +					'UPDATE_FILE_UPDATER_HAS_FAILED', +					$current_method, +					'compression' +				)); +				$this->installer_config->set('file_update_method', 'compression'); + +				// We only want a simple refresh here +				throw new resource_limit_reached_exception(); +			} +			else +			{ +				// Nowhere to fallback to :( +				// Due to the way the installer handles fatal errors, we need to throw a low level exception +				throw new runtime_exception('UPDATE_FILE_UPDATERS_HAVE_FAILED'); +			} +		} + +		$file_updater_method = $this->installer_config->get('file_update_method', ''); +		if ($file_updater_method === 'compression' || $file_updater_method === 'ftp') +		{ +			$this->file_updater->close(); +		} +	} + +	/** +	 * Get file updater +	 * +	 * @param null|string	$file_updater_method	Name of the file updater to use +	 * +	 * @return file_updater_interface	File updater +	 */ +	protected function get_file_updater($file_updater_method = null) +	{ +		$file_updater_method = ($file_updater_method === null) ? $this->installer_config->get('file_update_method', '') : $file_updater_method; + +		if ($file_updater_method === 'compression') +		{ +			$compression_method = $this->installer_config->get('file_update_compression', ''); + +			/** @var \phpbb\install\helper\file_updater\compression_file_updater $file_updater */ +			$file_updater = $this->factory->get('compression'); +			$archive_path = $file_updater->init($compression_method); +			$this->installer_config->set('update_file_archive', $archive_path); +		} +		else if ($file_updater_method === 'ftp') +		{ +			/** @var \phpbb\install\helper\file_updater\ftp_file_updater $file_updater */ +			$file_updater = $this->factory->get('ftp'); +			$file_updater->init( +				$this->installer_config->get('ftp_method', ''), +				$this->installer_config->get('ftp_host', ''), +				$this->installer_config->get('ftp_user', ''), +				$this->installer_config->get('ftp_pass', ''), +				$this->installer_config->get('ftp_path', ''), +				$this->installer_config->get('ftp_port', 0), +				$this->installer_config->get('ftp_timeout', 10) +			); +		} +		else +		{ +			/** @var file_updater_interface $file_updater */ +			$file_updater = $this->factory->get('direct_file'); +		} + +		return $file_updater; +	} + +	/** +	 * {@inheritdoc} +	 */ +	static public function get_step_count() +	{ +		return 0; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_task_lang_name() +	{ +		return ''; +	} +} diff --git a/phpBB/phpbb/install/module_base.php b/phpBB/phpbb/install/module_base.php new file mode 100644 index 0000000000..93c10bd656 --- /dev/null +++ b/phpBB/phpbb/install/module_base.php @@ -0,0 +1,213 @@ +<?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\install; + +use phpbb\di\ordered_service_collection; +use phpbb\install\exception\resource_limit_reached_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\iohandler\iohandler_interface; + +/** + * Base class for installer module + */ +abstract class module_base implements module_interface +{ +	/** +	 * @var config +	 */ +	protected $install_config; + +	/** +	 * @var iohandler_interface +	 */ +	protected $iohandler; + +	/** +	 * @var bool +	 */ +	protected $is_essential; + +	/** +	 * Array of tasks for installer module +	 * +	 * @var ordered_service_collection +	 */ +	protected $task_collection; + +	/** +	 * @var array +	 */ +	protected $task_step_count; + +	/** +	 * @var bool +	 */ +	protected $allow_progress_bar; + +	/** +	 * Installer module constructor +	 * +	 * @param ordered_service_collection	$tasks				array of installer tasks for installer module +	 * @param bool							$essential			flag indicating whether the module is essential or not +	 * @param bool							$allow_progress_bar	flag indicating whether or not to send progress information from within the module +	 */ +	public function __construct(ordered_service_collection $tasks, $essential = true, $allow_progress_bar = true) +	{ +		$this->task_collection		= $tasks; +		$this->is_essential			= $essential; +		$this->allow_progress_bar	= $allow_progress_bar; +	} + +	/** +	 * Dependency getter +	 * +	 * @param config				$config +	 * @param iohandler_interface	$iohandler +	 */ +	public function setup(config $config, iohandler_interface $iohandler) +	{ +		$this->install_config	= $config; +		$this->iohandler		= $iohandler; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function is_essential() +	{ +		return $this->is_essential; +	} + +	/** +	 * {@inheritdoc} +	 * +	 * Overwrite this method if your task is non-essential! +	 */ +	public function check_requirements() +	{ +		return true; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function run() +	{ +		// Recover install progress +		$task_index	= $this->recover_progress(); +		$iterator	= $this->task_collection->getIterator(); + +		if ($task_index < $iterator->count()) +		{ +			$iterator->seek($task_index); +		} +		else +		{ +			$this->install_config->set_finished_task(0); +			return; +		} + +		while ($iterator->valid()) +		{ +			$task = $iterator->current(); +			$name = $iterator->key(); + +			// Check if we can run the task +			if (!$task->is_essential() && !$task->check_requirements()) +			{ +				$this->iohandler->add_log_message(array( +					'SKIP_TASK', +					$name, +				)); + +				$this->install_config->increment_current_task_progress($this->task_step_count[$name]); +			} +			else +			{ +				// Send progress information +				if ($this->allow_progress_bar) +				{ +					$this->iohandler->set_progress( +						$task->get_task_lang_name(), +						$this->install_config->get_current_task_progress() +					); + +					$this->iohandler->send_response(); +				} + +				$task->run(); + +				if ($this->allow_progress_bar) +				{ +					// Only increment progress by one, as if a task has more than one steps +					// then that should be incremented in the task itself +					$this->install_config->increment_current_task_progress(); +				} +			} + +			$task_index++; +			$this->install_config->set_finished_task($task_index); +			$iterator->next(); + +			// Send progress information +			if ($this->allow_progress_bar) +			{ +				$this->iohandler->set_progress( +					$task->get_task_lang_name(), +					$this->install_config->get_current_task_progress() +				); +			} + +			$this->iohandler->send_response(); + +			// Stop execution if resource limit is reached +			if ($iterator->valid() && ($this->install_config->get_time_remaining() <= 0 || $this->install_config->get_memory_remaining() <= 0)) +			{ +				throw new resource_limit_reached_exception(); +			} +		} + +		// Module finished, so clear task progress +		$this->install_config->set_finished_task(0); +	} + +	/** +	 * Returns the next task's name +	 * +	 * @return string	Index of the array element of the next task +	 */ +	protected function recover_progress() +	{ +		$progress_array = $this->install_config->get_progress_data(); +		return $progress_array['last_task_index']; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function get_step_count() +	{ +		$task_step_count = 0; +		$task_class_names = $this->task_collection->get_service_classes(); + +		foreach ($task_class_names as $name => $task_class) +		{ +			$step_count = $task_class::get_step_count(); +			$task_step_count += $step_count; +			$this->task_step_count[$name] = $step_count; +		} + +		return $task_step_count; +	} +} diff --git a/phpBB/phpbb/install/module_interface.php b/phpBB/phpbb/install/module_interface.php new file mode 100644 index 0000000000..a2d61e3958 --- /dev/null +++ b/phpBB/phpbb/install/module_interface.php @@ -0,0 +1,63 @@ +<?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\install; + +/** + * Interface for installer modules + * + * An installer module is a task collection which executes installer tasks. + */ +interface module_interface +{ +	/** +	 * Checks if the execution of the module is essential to install phpBB or it can be skipped +	 * +	 * Note: Please note that all the non-essential modules have to implement check_requirements() +	 * method. +	 * +	 * @return	bool	true if the module is essential, false otherwise +	 */ +	public function is_essential(); + +	/** +	 * Checks requirements for the tasks +	 * +	 * Note: Only need to be implemented for non-essential tasks, as essential tasks +	 * requirements should be checked in the requirements install module. +	 * +	 * @return bool	true if the task's requirements are met +	 */ +	public function check_requirements(); + +	/** +	 * Executes the task +	 * +	 * @return	null +	 */ +	public function run(); + +	/** +	 * Returns the number of tasks in the module +	 * +	 * @return int +	 */ +	public function get_step_count(); + +	/** +	 * Returns an array to the correct navigation stage +	 * +	 * @return array +	 */ +	public function get_navigation_stage_path(); +} diff --git a/phpBB/phpbb/install/task_base.php b/phpBB/phpbb/install/task_base.php new file mode 100644 index 0000000000..b5199be4af --- /dev/null +++ b/phpBB/phpbb/install/task_base.php @@ -0,0 +1,53 @@ +<?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\install; + +/** + * Base class for installer task + */ +abstract class task_base implements task_interface +{ +	/** +	 * @var bool +	 */ +	protected $is_essential; + +	/** +	 * Constructor +	 * +	 * @param bool $essential +	 */ +	public function __construct($essential = true) +	{ +		$this->is_essential = $essential; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function is_essential() +	{ +		return $this->is_essential; +	} + +	/** +	 * {@inheritdoc} +	 * +	 * Note: Overwrite this method if your task is non-essential! +	 */ +	public function check_requirements() +	{ +		return true; +	} +} diff --git a/phpBB/phpbb/install/task_interface.php b/phpBB/phpbb/install/task_interface.php new file mode 100644 index 0000000000..794cb16482 --- /dev/null +++ b/phpBB/phpbb/install/task_interface.php @@ -0,0 +1,61 @@ +<?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\install; + +/** + * Interface for installer tasks + */ +interface task_interface +{ +	/** +	 * Returns the number of steps the task contains +	 * +	 * This is a helper method to provide a better progress bar for the front-end. +	 * +	 * @return int	The number of steps that the task contains +	 */ +	static public function get_step_count(); + +	/** +	 * Checks if the task is essential to install phpBB or it can be skipped +	 * +	 * Note: Please note that all the non-essential modules have to implement check_requirements() +	 * method. +	 * +	 * @return	bool	true if the task is essential, false otherwise +	 */ +	public function is_essential(); + +	/** +	 * Checks requirements for the tasks +	 * +	 * Note: Only need to be implemented for non-essential tasks, as essential tasks +	 * requirements should be checked in the requirements install module. +	 * +	 * @return bool	true if the task's requirements are met +	 */ +	public function check_requirements(); + +	/** +	 * Executes the task +	 */ +	public function run(); + +	/** +	 * Returns the language key of the name of the task +	 * +	 * @return string +	 */ +	public function get_task_lang_name(); +} diff --git a/phpBB/phpbb/install/updater_configuration.php b/phpBB/phpbb/install/updater_configuration.php new file mode 100644 index 0000000000..e992356290 --- /dev/null +++ b/phpBB/phpbb/install/updater_configuration.php @@ -0,0 +1,40 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\install; + +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\ConfigurationInterface; + +class updater_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('updater'); +		$rootNode +			->addDefaultsIfNotSet() +			->children() +				->enumNode('type')->values(['all','db_only'])->defaultValue('all')->end() +			->end() +		; + +		return $treeBuilder; +	} +} 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..42429c2c07 --- /dev/null +++ b/phpBB/phpbb/language/language.php @@ -0,0 +1,673 @@ +<?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(); +		$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 +	 * @param bool		$reload				Whether or not to reload language files +	 */ +	public function set_user_language($user_lang_iso, $reload = false) +	{ +		$this->user_language = $user_lang_iso; + +		$this->set_fallback_array($reload); +	} + +	/** +	 * Function to set the board's default language to display. +	 * +	 * @param string	$default_lang_iso	ISO code of the board's default language +	 * @param bool		$reload				Whether or not to reload language files +	 */ +	public function set_default_language($default_lang_iso, $reload = false) +	{ +		$this->default_language = $default_lang_iso; + +		$this->set_fallback_array($reload); +	} + +	/** +	 * 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 +	 * +	 * 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); +			} +		} +	} + +	/** +	 * @param $key array|string		The language key we want to know more about. Can be string or array. +	 * +	 * @return bool		Returns whether the language key is set. +	 */ +	public function is_set($key) +	{ +		// Load common language files if they not loaded yet +		if (!$this->common_language_files_loaded) +		{ +			$this->load_common_language_files(); +		} + +		if (is_array($key)) +		{ +			$lang = &$this->lang[array_shift($key)]; + +			foreach ($key as $_key) +			{ +				$lang = &$lang[$_key]; +			} +		} +		else +		{ +			$lang = &$this->lang[$key]; +		} + +		return isset($lang); +	} + +	/** +	 * 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() +	{ +		$args = func_get_args(); +		$key = array_shift($args); + +		return $this->lang_array($key, $args); +	} + +	/** +	 * Returns the raw value associated to a language key or the language key no translation is available. +	 * No parameter substitution is performed, can be a string or an array. +	 * +	 * @param string|array	$key	Language key +	 * +	 * @return array|string +	 */ +	public function lang_raw($key) +	{ +		// Load common language files if they not loaded yet +		if (!$this->common_language_files_loaded) +		{ +			$this->load_common_language_files(); +		} + +		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; +		} + +		return $lang; +	} + +	/** +	 * Act like lang() but takes a key and an array of parameters instead of using variadic +	 * +	 * @param string|array	$key	Language key +	 * @param array			$args	Parameters +	 * +	 * @return string +	 */ +	public function lang_array($key, $args = array()) +	{ +		$lang = $this->lang_raw($key); + +		if ($lang === $key) +		{ +			return $key; +		} + +		// If the language entry is a string, we simply mimic sprintf() behaviour +		if (is_string($lang)) +		{ +			if (count($args) === 0) +			{ +				return $lang; +			} + +			// Replace key with language entry and simply pass along... +			return vsprintf($lang, $args); +		} +		else if (sizeof($lang) == 0) +		{ +			// If the language entry is an empty array, we just return the language key +			return $key; +		} + +		// 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 = 0, $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() +		return vsprintf($lang[$key_found], $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 the ISO code of the used language +	 * +	 * @return string	The ISO code of the currently used language +	 */ +	public function get_used_language() +	{ +		return $this->language_fallback[0]; +	} + +	/** +	 * Returns language fallback data +	 * +	 * @param bool	$reload	Whether or not to reload language files +	 * +	 * @return array +	 */ +	protected function set_fallback_array($reload = false) +	{ +		$fallback_array = array(); + +		if ($this->user_language) +		{ +			$fallback_array[] = $this->user_language; +		} + +		if ($this->default_language) +		{ +			$fallback_array[] = $this->default_language; +		} + +		$fallback_array[] = self::FALLBACK_LANGUAGE; + +		$this->language_fallback = $fallback_array; + +		if ($reload) +		{ +			$this->reload_language_files(); +		} +	} + +	/** +	 * 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; +	} + +	/** +	 * Reload language files +	 */ +	protected function reload_language_files() +	{ +		$loaded_files = $this->loaded_language_sets; +		$this->loaded_language_sets	= array( +			'core'	=> array(), +			'ext'	=> array(), +		); + +		// Reload core files +		foreach ($loaded_files['core'] as $component => $value) +		{ +			$this->load_core_file($component); +		} + +		// Reload extension files +		foreach ($loaded_files['ext'] as $ext_name => $ext_info) +		{ +			foreach ($ext_info as $ext_component => $value) +			{ +				$this->load_extension($ext_name, $ext_component); +			} +		} +	} +} diff --git a/phpBB/phpbb/language/language_file_helper.php b/phpBB/phpbb/language/language_file_helper.php new file mode 100644 index 0000000000..85de034fb8 --- /dev/null +++ b/phpBB/phpbb/language/language_file_helper.php @@ -0,0 +1,72 @@ +<?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') +			->followLinks() +			->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..359202fd63 --- /dev/null +++ b/phpBB/phpbb/language/language_file_loader.php @@ -0,0 +1,206 @@ +<?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 language_file_not_found	When the path to the file cannot be resolved +	 */ +	protected function get_language_file_path($path, $filename, $locales) +	{ +		$language_file_path = $filename; + +		// 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) +	{ +		// Do not suppress error if in DEBUG mode +		if (defined('DEBUG')) +		{ +			include $path; +		} +		else +		{ +			@include $path; +		} +	} +} 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 d46e3d1f3f..ee002c560a 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,7 +223,7 @@ class log implements \phpbb\log\log_interface  			return false;  		} -		if ($log_time == false) +		if ($log_time === false)  		{  			$log_time = time();  		} @@ -249,10 +249,13 @@ class log implements \phpbb\log\log_interface  				unset($additional_data['forum_id']);  				$topic_id = isset($additional_data['topic_id']) ? (int) $additional_data['topic_id'] : 0;  				unset($additional_data['topic_id']); +				$post_id = isset($additional_data['post_id']) ? (int) $additional_data['post_id'] : 0; +				unset($additional_data['post_id']);  				$sql_ary += array(  					'log_type'		=> LOG_MOD,  					'forum_id'		=> $forum_id,  					'topic_id'		=> $topic_id, +					'post_id'		=> $post_id,  					'log_data'		=> (!empty($additional_data)) ? serialize($additional_data) : '',  				);  			break; @@ -643,6 +646,7 @@ class log implements \phpbb\log\log_interface  				'time'				=> (int) $row['log_time'],  				'forum_id'			=> (int) $row['forum_id'],  				'topic_id'			=> (int) $row['topic_id'], +				'post_id'			=> (int) $row['post_id'],  				'viewforum'			=> ($row['forum_id'] && $this->auth->acl_get('f_read', $row['forum_id'])) ? append_sid("{$this->phpbb_root_path}viewforum.{$this->php_ext}", 'f=' . $row['forum_id']) : false,  				'action'			=> (isset($this->user->lang[$row['log_operation']])) ? $row['log_operation'] : '{' . ucfirst(str_replace('_', ' ', $row['log_operation'])) . '}', @@ -743,6 +747,7 @@ class log implements \phpbb\log\log_interface  			foreach ($log as $key => $row)  			{  				$log[$key]['viewtopic'] = (isset($topic_auth['f_read'][$row['topic_id']])) ? append_sid("{$this->phpbb_root_path}viewtopic.{$this->php_ext}", 'f=' . $topic_auth['f_read'][$row['topic_id']] . '&t=' . $row['topic_id']) : false; +				$log[$key]['viewpost'] = (isset($topic_auth['f_read'][$row['topic_id']]) && $row['post_id']) ? append_sid("{$this->phpbb_root_path}viewtopic.{$this->php_ext}", 'f=' . $topic_auth['f_read'][$row['topic_id']] . '&t=' . $row['topic_id'] . '&p=' . $row['post_id']) : false;  				$log[$key]['viewlogs'] = (isset($topic_auth['m_'][$row['topic_id']])) ? append_sid("{$this->phpbb_root_path}mcp.{$this->php_ext}", 'i=logs&mode=topic_logs&t=' . $row['topic_id'], true, $this->user->session_id) : false;  			}  		} 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/module/exception/module_exception.php b/phpBB/phpbb/module/exception/module_exception.php new file mode 100644 index 0000000000..8ad75112bc --- /dev/null +++ b/phpBB/phpbb/module/exception/module_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\module\exception; + +class module_exception extends \phpbb\exception\runtime_exception +{ + +} diff --git a/phpBB/phpbb/module/exception/module_not_found_exception.php b/phpBB/phpbb/module/exception/module_not_found_exception.php new file mode 100644 index 0000000000..2d485e7b35 --- /dev/null +++ b/phpBB/phpbb/module/exception/module_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\module\exception; + +class module_not_found_exception extends module_exception +{ + +} diff --git a/phpBB/phpbb/module/module_manager.php b/phpBB/phpbb/module/module_manager.php new file mode 100644 index 0000000000..7ae16cdb61 --- /dev/null +++ b/phpBB/phpbb/module/module_manager.php @@ -0,0 +1,564 @@ +<?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\module; + +use phpbb\module\exception\module_exception; +use phpbb\module\exception\module_not_found_exception; + +class module_manager +{ +	/** +	 * @var \phpbb\cache\driver\driver_interface +	 */ +	protected $cache; + +	/** +	 * @var \phpbb\db\driver\driver_interface +	 */ +	protected $db; + +	/** +	 * @var \phpbb\extension\manager +	 */ +	protected $extension_manager; + +	/** +	 * @var string +	 */ +	protected $modules_table; + +	/** +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * @var string +	 */ +	protected $php_ext; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\cache\driver\driver_interface	$cache				Cache driver +	 * @param \phpbb\db\driver\driver_interface		$db					Database driver +	 * @param \phpbb\extension\manager				$ext_manager		Extension manager +	 * @param string								$modules_table		Module database table's name +	 * @param string								$phpbb_root_path	Path to phpBB's root +	 * @param string								$php_ext			Extension of PHP files +	 */ +	public function __construct(\phpbb\cache\driver\driver_interface $cache, \phpbb\db\driver\driver_interface $db, \phpbb\extension\manager $ext_manager, $modules_table, $phpbb_root_path, $php_ext) +	{ +		$this->cache				= $cache; +		$this->db					= $db; +		$this->extension_manager	= $ext_manager; +		$this->modules_table		= $modules_table; +		$this->phpbb_root_path		= $phpbb_root_path; +		$this->php_ext				= $php_ext; +	} + +	/** +	 * Get row for specified module +	 * +	 * @param int		$module_id		ID of the module +	 * @param string	$module_class	Class of the module (acp, ucp, mcp etc...) +	 * +	 * @return array	Array of data fetched from the database +	 * +	 * @throws \phpbb\module\exception\module_not_found_exception	When there is no module with $module_id +	 */ +	public function get_module_row($module_id, $module_class) +	{ +		$module_id = (int) $module_id; + +		$sql = 'SELECT * +			FROM ' . $this->modules_table . " +			WHERE module_class = '" . $this->db->sql_escape($module_class) . "' +				AND module_id = $module_id"; +		$result = $this->db->sql_query($sql); +		$row = $this->db->sql_fetchrow($result); +		$this->db->sql_freeresult($result); + +		if (!$row) +		{ +			throw new module_not_found_exception('NO_MODULE'); +		} + +		return $row; +	} + +	/** +	 * Get available module information from module files +	 * +	 * @param string	$module_class		Class of the module (acp, ucp, mcp etc...) +	 * @param string	$module				ID of module +	 * @param bool		$use_all_available	Use all available instead of just all +	 *										enabled extensions +	 * +	 * @return array	Array with module information gathered from module info files. +	 */ +	public function get_module_infos($module_class, $module = '', $use_all_available = false) +	{ +		$directory = $this->phpbb_root_path . 'includes/' . $module_class . '/info/'; +		$fileinfo = array(); + +		$finder = $this->extension_manager->get_finder($use_all_available); + +		$modules = $finder +			->extension_suffix('_module') +			->extension_directory("/$module_class") +			->core_path("includes/$module_class/info/") +			->core_prefix($module_class . '_') +			->get_classes(true); + +		foreach ($modules as $cur_module) +		{ +			// Skip entries we do not need if we know the module we are +			// looking for +			if ($module && strpos(str_replace('\\', '_', $cur_module), $module) === false && $module !== $cur_module) +			{ +				continue; +			} + +			$info_class = preg_replace('/_module$/', '_info', $cur_module); + +			// If the class does not exist it might be following the old +			// format. phpbb_acp_info_acp_foo needs to be turned into +			// acp_foo_info and the respective file has to be included +			// manually because it does not support auto loading +			$old_info_class_file = str_replace("phpbb_{$module_class}_info_", '', $cur_module); +			$old_info_class = $old_info_class_file . '_info'; + +			if (class_exists($old_info_class)) +			{ +				$info_class = $old_info_class; +			} +			else if (!class_exists($info_class)) +			{ +				$info_class = $old_info_class; + +				// need to check class exists again because previous checks triggered autoloading +				if (!class_exists($info_class) && file_exists($directory . $old_info_class_file . '.' . $this->php_ext)) +				{ +					include($directory . $old_info_class_file . '.' . $this->php_ext); +				} +			} + +			if (class_exists($info_class)) +			{ +				$info = new $info_class(); +				$module_info = $info->module(); + +				$main_class = (isset($module_info['filename'])) ? $module_info['filename'] : $cur_module; + +				$fileinfo[$main_class] = $module_info; +			} +		} + +		ksort($fileinfo); + +		return $fileinfo; +	} + +	/** +	 * Get module branch +	 * +	 * @param int		$module_id		ID of the module +	 * @param string	$module_class	Class of the module (acp, ucp, mcp etc...) +	 * @param string	$type			Type of branch (Expected values: all, parents or children) +	 * @param bool		$include_module	Whether or not to include the specified module with $module_id +	 * +	 * @return array	Returns an array containing the modules in the specified branch type. +	 */ +	public function get_module_branch($module_id, $module_class, $type = 'all', $include_module = true) +	{ +		$module_id = (int) $module_id; + +		switch ($type) +		{ +			case 'parents': +				$condition = 'm1.left_id BETWEEN m2.left_id AND m2.right_id'; +			break; + +			case 'children': +				$condition = 'm2.left_id BETWEEN m1.left_id AND m1.right_id'; +			break; + +			default: +				$condition = 'm2.left_id BETWEEN m1.left_id AND m1.right_id OR m1.left_id BETWEEN m2.left_id AND m2.right_id'; +			break; +		} + +		$rows = array(); + +		$sql = 'SELECT m2.* +			FROM ' . $this->modules_table . ' m1 +			LEFT JOIN ' . $this->modules_table . " m2 ON ($condition) +			WHERE m1.module_class = '" . $this->db->sql_escape($module_class) . "' +				AND m2.module_class = '" . $this->db->sql_escape($module_class) . "' +				AND m1.module_id = $module_id +			ORDER BY m2.left_id DESC"; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			if (!$include_module && $row['module_id'] == $module_id) +			{ +				continue; +			} + +			$rows[] = $row; +		} +		$this->db->sql_freeresult($result); + +		return $rows; +	} + +	/** +	 * Remove modules cache file +	 * +	 * @param string	$module_class	Class of the module (acp, ucp, mcp etc...) +	 */ +	public function remove_cache_file($module_class) +	{ +		// Sanitise for future path use, it's escaped as appropriate for queries +		$cache_class = str_replace(array('.', '/', '\\'), '', basename($module_class)); +		$this->cache->destroy('_modules_' . $cache_class); +		$this->cache->destroy('sql', $this->modules_table); +	} + +	/** +	 * Update/Add module +	 * +	 * @param array	&$module_data	The module data +	 * +	 * @throws \phpbb\module\exception\module_not_found_exception	When parent module or the category is not exist +	 */ +	public function update_module_data(&$module_data) +	{ +		if (!isset($module_data['module_id'])) +		{ +			// no module_id means we're creating a new category/module +			if ($module_data['parent_id']) +			{ +				$sql = 'SELECT left_id, right_id +					FROM ' . $this->modules_table . " +					WHERE module_class = '" . $this->db->sql_escape($module_data['module_class']) . "' +						AND module_id = " . (int) $module_data['parent_id']; +				$result = $this->db->sql_query($sql); +				$row = $this->db->sql_fetchrow($result); +				$this->db->sql_freeresult($result); + +				if (!$row) +				{ +					throw new module_not_found_exception('PARENT_NOT_EXIST'); +				} + +				// Workaround +				$row['left_id'] = (int) $row['left_id']; +				$row['right_id'] = (int) $row['right_id']; + +				$sql = 'UPDATE ' . $this->modules_table . " +					SET left_id = left_id + 2, right_id = right_id + 2 +					WHERE module_class = '" . $this->db->sql_escape($module_data['module_class']) . "' +						AND left_id > {$row['right_id']}"; +				$this->db->sql_query($sql); + +				$sql = 'UPDATE ' . $this->modules_table . " +					SET right_id = right_id + 2 +					WHERE module_class = '" . $this->db->sql_escape($module_data['module_class']) . "' +						AND {$row['left_id']} BETWEEN left_id AND right_id"; +				$this->db->sql_query($sql); + +				$module_data['left_id'] = (int) $row['right_id']; +				$module_data['right_id'] = (int) $row['right_id'] + 1; +			} +			else +			{ +				$sql = 'SELECT MAX(right_id) AS right_id +					FROM ' . $this->modules_table . " +					WHERE module_class = '" . $this->db->sql_escape($module_data['module_class']) . "'"; +				$result = $this->db->sql_query($sql); +				$row = $this->db->sql_fetchrow($result); +				$this->db->sql_freeresult($result); + +				$module_data['left_id'] = (int) $row['right_id'] + 1; +				$module_data['right_id'] = (int) $row['right_id'] + 2; +			} + +			$sql = 'INSERT INTO ' . $this->modules_table . ' ' . $this->db->sql_build_array('INSERT', $module_data); +			$this->db->sql_query($sql); + +			$module_data['module_id'] = $this->db->sql_nextid(); +		} +		else +		{ +			$row = $this->get_module_row($module_data['module_id'], $module_data['module_class']); + +			if ($module_data['module_basename'] && !$row['module_basename']) +			{ +				// we're turning a category into a module +				$branch = $this->get_module_branch($module_data['module_id'], $module_data['module_class'], 'children', false); + +				if (sizeof($branch)) +				{ +					throw new module_not_found_exception('NO_CATEGORY_TO_MODULE'); +				} +			} + +			if ($row['parent_id'] != $module_data['parent_id']) +			{ +				$this->move_module($module_data['module_id'], $module_data['parent_id'], $module_data['module_class']); +			} + +			$update_ary = $module_data; +			unset($update_ary['module_id']); + +			$sql = 'UPDATE ' . $this->modules_table . ' +				SET ' . $this->db->sql_build_array('UPDATE', $update_ary) . " +				WHERE module_class = '" . $this->db->sql_escape($module_data['module_class']) . "' +					AND module_id = " . (int) $module_data['module_id']; +			$this->db->sql_query($sql); +		} +	} + +	/** +	 * Move module around the tree +	 * +	 * @param int		$from_module_id	ID of the current parent module +	 * @param int		$to_parent_id	ID of the target parent module +	 * @param string	$module_class	Class of the module (acp, ucp, mcp etc...) +	 * +	 * @throws \phpbb\module\exception\module_not_found_exception	If the module specified to move modules from does not +	 * 																have any children. +	 */ +	public function move_module($from_module_id, $to_parent_id, $module_class) +	{ +		$moved_modules = $this->get_module_branch($from_module_id, $module_class, 'children'); + +		if (empty($moved_modules)) +		{ +			throw new module_not_found_exception(); +		} + +		$from_data = $moved_modules[0]; +		$diff = sizeof($moved_modules) * 2; + +		$moved_ids = array(); +		for ($i = 0, $size = sizeof($moved_modules); $i < $size; ++$i) +		{ +			$moved_ids[] = $moved_modules[$i]['module_id']; +		} + +		// Resync parents +		$sql = 'UPDATE ' . $this->modules_table . " +			SET right_id = right_id - $diff +			WHERE module_class = '" . $this->db->sql_escape($module_class) . "' +				AND left_id < " . (int) $from_data['right_id'] . ' +				AND right_id > ' . (int) $from_data['right_id']; +		$this->db->sql_query($sql); + +		// Resync righthand side of tree +		$sql = 'UPDATE ' . $this->modules_table . " +			SET left_id = left_id - $diff, right_id = right_id - $diff +			WHERE module_class = '" . $this->db->sql_escape($module_class) . "' +				AND left_id > " . (int) $from_data['right_id']; +		$this->db->sql_query($sql); + +		if ($to_parent_id > 0) +		{ +			$to_data = $this->get_module_row($to_parent_id, $module_class); + +			// Resync new parents +			$sql = 'UPDATE ' . $this->modules_table . " +				SET right_id = right_id + $diff +				WHERE module_class = '" . $this->db->sql_escape($module_class) . "' +					AND " . (int) $to_data['right_id'] . ' BETWEEN left_id AND right_id +					AND ' . $this->db->sql_in_set('module_id', $moved_ids, true); +			$this->db->sql_query($sql); + +			// Resync the righthand side of the tree +			$sql = 'UPDATE ' . $this->modules_table . " +				SET left_id = left_id + $diff, right_id = right_id + $diff +				WHERE module_class = '" . $this->db->sql_escape($module_class) . "' +					AND left_id > " . (int) $to_data['right_id'] . ' +					AND ' . $this->db->sql_in_set('module_id', $moved_ids, true); +			$this->db->sql_query($sql); + +			// Resync moved branch +			$to_data['right_id'] += $diff; +			if ($to_data['right_id'] > $from_data['right_id']) +			{ +				$diff = '+ ' . ($to_data['right_id'] - $from_data['right_id'] - 1); +			} +			else +			{ +				$diff = '- ' . abs($to_data['right_id'] - $from_data['right_id'] - 1); +			} +		} +		else +		{ +			$sql = 'SELECT MAX(right_id) AS right_id +				FROM ' . $this->modules_table . " +				WHERE module_class = '" . $this->db->sql_escape($module_class) . "' +					AND " . $this->db->sql_in_set('module_id', $moved_ids, true); +			$result = $this->db->sql_query($sql); +			$row = $this->db->sql_fetchrow($result); +			$this->db->sql_freeresult($result); + +			$diff = '+ ' . (int) ($row['right_id'] - $from_data['left_id'] + 1); +		} + +		$sql = 'UPDATE ' . $this->modules_table . " +			SET left_id = left_id $diff, right_id = right_id $diff +			WHERE module_class = '" . $this->db->sql_escape($module_class) . "' +				AND " . $this->db->sql_in_set('module_id', $moved_ids); +		$this->db->sql_query($sql); +	} + +	/** +	 * Remove module from tree +	 * +	 * @param int		$module_id		ID of the module to delete +	 * @param string	$module_class	Class of the module (acp, ucp, mcp etc...) +	 * +	 * @throws \phpbb\module\exception\module_exception	When the specified module cannot be removed +	 */ +	public function delete_module($module_id, $module_class) +	{ +		$module_id = (int) $module_id; + +		$row = $this->get_module_row($module_id, $module_class); + +		$branch = $this->get_module_branch($module_id, $module_class, 'children', false); + +		if (sizeof($branch)) +		{ +			throw new module_exception('CANNOT_REMOVE_MODULE'); +		} + +		// If not move +		$diff = 2; +		$sql = 'DELETE FROM ' . $this->modules_table . " +			WHERE module_class = '" . $this->db->sql_escape($module_class) . "' +				AND module_id = $module_id"; +		$this->db->sql_query($sql); + +		$row['right_id'] = (int) $row['right_id']; +		$row['left_id'] = (int) $row['left_id']; + +		// Resync tree +		$sql = 'UPDATE ' . $this->modules_table . " +			SET right_id = right_id - $diff +			WHERE module_class = '" . $this->db->sql_escape($module_class) . "' +				AND left_id < {$row['right_id']} AND right_id > {$row['right_id']}"; +		$this->db->sql_query($sql); + +		$sql = 'UPDATE ' . $this->modules_table . " +			SET left_id = left_id - $diff, right_id = right_id - $diff +			WHERE module_class = '" . $this->db->sql_escape($module_class) . "' +				AND left_id > {$row['right_id']}"; +		$this->db->sql_query($sql); +	} + +	/** +	 * Move module position by $steps up/down +	 * +	 * @param array		$module_row		Array of module data +	 * @param string	$module_class	Class of the module (acp, ucp, mcp etc...) +	 * @param string	$action			Direction of moving (valid values: move_up or move_down) +	 * @param int		$steps			Number of steps to move module +	 * +	 * @return string	Returns the language name of the module +	 * +	 * @throws \phpbb\module\exception\module_not_found_exception	When the specified module does not exists +	 */ +	public function move_module_by($module_row, $module_class, $action = 'move_up', $steps = 1) +	{ +		/** +		 * Fetch all the siblings between the module's current spot +		 * and where we want to move it to. If there are less than $steps +		 * siblings between the current spot and the target then the +		 * module will move as far as possible +		 */ +		$sql = 'SELECT module_id, left_id, right_id, module_langname +			FROM ' . $this->modules_table . " +			WHERE module_class = '" . $this->db->sql_escape($module_class) . "' +				AND parent_id = " . (int) $module_row['parent_id'] . ' +				AND ' . (($action == 'move_up') ? 'right_id < ' . (int) $module_row['right_id'] . ' ORDER BY right_id DESC' : 'left_id > ' . (int) $module_row['left_id'] . ' ORDER BY left_id ASC'); +		$result = $this->db->sql_query_limit($sql, $steps); + +		$target = array(); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$target = $row; +		} +		$this->db->sql_freeresult($result); + +		if (!sizeof($target)) +		{ +			// The module is already on top or bottom +			throw new module_not_found_exception(); +		} + +		/** +		 * $left_id and $right_id define the scope of the nodes that are affected by the move. +		 * $diff_up and $diff_down are the values to substract or add to each node's left_id +		 * and right_id in order to move them up or down. +		 * $move_up_left and $move_up_right define the scope of the nodes that are moving +		 * up. Other nodes in the scope of ($left_id, $right_id) are considered to move down. +		 */ +		if ($action == 'move_up') +		{ +			$left_id = (int) $target['left_id']; +			$right_id = (int) $module_row['right_id']; + +			$diff_up = (int) ($module_row['left_id'] - $target['left_id']); +			$diff_down = (int) ($module_row['right_id'] + 1 - $module_row['left_id']); + +			$move_up_left = (int) $module_row['left_id']; +			$move_up_right = (int) $module_row['right_id']; +		} +		else +		{ +			$left_id = (int) $module_row['left_id']; +			$right_id = (int) $target['right_id']; + +			$diff_up = (int) ($module_row['right_id'] + 1 - $module_row['left_id']); +			$diff_down = (int) ($target['right_id'] - $module_row['right_id']); + +			$move_up_left = (int) ($module_row['right_id'] + 1); +			$move_up_right = (int) $target['right_id']; +		} + +		// Now do the dirty job +		$sql = 'UPDATE ' . $this->modules_table . " +			SET left_id = left_id + CASE +				WHEN left_id BETWEEN {$move_up_left} AND {$move_up_right} THEN -{$diff_up} +				ELSE {$diff_down} +			END, +			right_id = right_id + CASE +				WHEN right_id BETWEEN {$move_up_left} AND {$move_up_right} THEN -{$diff_up} +				ELSE {$diff_down} +			END +			WHERE module_class = '" . $this->db->sql_escape($module_class) . "' +				AND left_id BETWEEN {$left_id} AND {$right_id} +				AND right_id BETWEEN {$left_id} AND {$right_id}"; +		$this->db->sql_query($sql); + +		$this->remove_cache_file($module_class); + +		return $target['module_langname']; +	} +} 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 ecba8938f2..023e1610f0 100644 --- a/phpBB/phpbb/notification/manager.php +++ b/phpBB/phpbb/notification/manager.php @@ -26,7 +26,7 @@ class manager  	/** @var array */  	protected $subscription_types; -	/** @var array */ +	/** @var method\method_interface[] */  	protected $notification_methods;  	/** @var ContainerInterface */ @@ -35,9 +35,6 @@ class manager  	/** @var \phpbb\user_loader */  	protected $user_loader; -	/** @var \phpbb\config\config */ -	protected $config; -  	/** @var \phpbb\event\dispatcher_interface */  	protected $phpbb_dispatcher; @@ -47,22 +44,16 @@ class manager  	/** @var \phpbb\cache\service */  	protected $cache; +	/** @var \phpbb\language\language */ +	protected $language; +  	/** @var \phpbb\user */  	protected $user;  	/** @var string */ -	protected $phpbb_root_path; - -	/** @var string */ -	protected $php_ext; - -	/** @var string */  	protected $notification_types_table;  	/** @var string */ -	protected $notifications_table; - -	/** @var string */  	protected $user_notifications_table;  	/** @@ -72,43 +63,37 @@ class manager  	* @param array $notification_methods  	* @param ContainerInterface $phpbb_container  	* @param \phpbb\user_loader $user_loader -	* @param \phpbb\config\config $config  	* @param \phpbb\event\dispatcher_interface $phpbb_dispatcher  	* @param \phpbb\db\driver\driver_interface $db  	* @param \phpbb\cache\service $cache +	* @param \phpbb\language\language $language  	* @param \phpbb\user $user -	* @param string $phpbb_root_path -	* @param string $php_ext  	* @param string $notification_types_table -	* @param string $notifications_table  	* @param string $user_notifications_table  	*  	* @return \phpbb\notification\manager  	*/ -	public function __construct($notification_types, $notification_methods, ContainerInterface $phpbb_container, \phpbb\user_loader $user_loader, \phpbb\config\config $config, \phpbb\event\dispatcher_interface $phpbb_dispatcher, \phpbb\db\driver\driver_interface $db, \phpbb\cache\service $cache, $user, $phpbb_root_path, $php_ext, $notification_types_table, $notifications_table, $user_notifications_table) +	public function __construct($notification_types, $notification_methods, ContainerInterface $phpbb_container, \phpbb\user_loader $user_loader, \phpbb\event\dispatcher_interface $phpbb_dispatcher, \phpbb\db\driver\driver_interface $db, \phpbb\cache\service $cache, \phpbb\language\language $language, \phpbb\user $user, $notification_types_table, $user_notifications_table)  	{  		$this->notification_types = $notification_types;  		$this->notification_methods = $notification_methods;  		$this->phpbb_container = $phpbb_container;  		$this->user_loader = $user_loader; -		$this->config = $config;  		$this->phpbb_dispatcher = $phpbb_dispatcher;  		$this->db = $db;  		$this->cache = $cache; +		$this->language = $language;  		$this->user = $user; -		$this->phpbb_root_path = $phpbb_root_path; -		$this->php_ext = $php_ext; -  		$this->notification_types_table = $notification_types_table; -		$this->notifications_table = $notifications_table;  		$this->user_notifications_table = $user_notifications_table;  	}  	/** -	* Load the user's notifications +	* Load the user's notifications for a given method  	* +	* @param string $method_name  	* @param array $options Optional options to control what notifications are loaded  	*				notification_id		Notification id to load (or array of notification ids)  	*				user_id				User id to load notifications for (Default: $user->data['user_id']) @@ -123,27 +108,21 @@ class manager  	*	'notifications'		array of notification type objects  	*	'unread_count'		number of unread notifications the user has if count_unread is true in the options  	*	'total_count'		number of notifications the user has if count_total is true in the options +	* @throws \phpbb\notification\exception when the method doesn't refer to a class extending \phpbb\notification\method\method_interface  	*/ -	public function load_notifications(array $options = array()) +	public function load_notifications($method_name, array $options = array())  	{ -		// Merge default options -		$options = array_merge(array( -			'notification_id'	=> false, -			'user_id'			=> $this->user->data['user_id'], -			'order_by'			=> 'notification_time', -			'order_dir'			=> 'DESC', -			'limit'				=> 0, -			'start'				=> 0, -			'all_unread'		=> false, -			'count_unread'		=> false, -			'count_total'		=> false, -		), $options); - -		// If all_unread, count_unread must be true -		$options['count_unread'] = ($options['all_unread']) ? true : $options['count_unread']; +		$method = $this->get_method_class($method_name); -		// Anonymous users and bots never receive notifications -		if ($options['user_id'] == $this->user->data['user_id'] && ($this->user->data['user_id'] == ANONYMOUS || $this->user->data['user_type'] == USER_IGNORE)) +		if (! $method instanceof \phpbb\notification\method\method_interface) +		{ +			throw new \phpbb\notification\exception($this->language->lang('NOTIFICATION_METHOD_INVALID', $method_name)); +		} +		else if ($method->is_available()) +		{ +			return $method->load_notifications($options); +		} +		else  		{  			return array(  				'notifications'		=> array(), @@ -151,172 +130,112 @@ class manager  				'total_count'		=> 0,  			);  		} +	} -		$notifications = $user_ids = array(); -		$load_special = array(); -		$total_count = $unread_count = 0; +	/** +	 * Mark notifications read or unread for all available methods +	 * +	 * @param bool|string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types). False to mark read for all item types +	 * @param bool|int|array $item_id Item id or array of item ids. False to mark read for all item ids +	 * @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids +	 * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) +	 * +	 * @deprecated since 3.2 +	 */ +	public function mark_notifications_read($notification_type_name, $item_id, $user_id, $time = false) +	{ +		$this->mark_notifications($notification_type_name, $item_id, $user_id, $time); +	} -		if ($options['count_unread']) +	/** +	* Mark notifications read or unread for all available methods +	* +	* @param bool|string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types). False to mark read for all item types +	* @param bool|int|array $item_id Item id or array of item ids. False to mark read for all item ids +	* @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids +	* @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) +	* @param bool $mark_read Define if the notification as to be set to True or False. (Default: True) +	*/ +	public function mark_notifications($notification_type_name, $item_id, $user_id, $time = false, $mark_read = true) +	{ +		if (is_array($notification_type_name))  		{ -			// Get the total number of unread notifications -			$sql = 'SELECT COUNT(n.notification_id) AS unread_count -				FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt -				WHERE n.user_id = ' . (int) $options['user_id'] . ' -					AND n.notification_read = 0 -					AND nt.notification_type_id = n.notification_type_id -					AND nt.notification_type_enabled = 1'; -			$result = $this->db->sql_query($sql); -			$unread_count = (int) $this->db->sql_fetchfield('unread_count'); -			$this->db->sql_freeresult($result); +			$notification_type_id = $this->get_notification_type_ids($notification_type_name);  		} - -		if ($options['count_total']) +		else if ($notification_type_name !== false)  		{ -			// Get the total number of notifications -			$sql = 'SELECT COUNT(n.notification_id) AS total_count -				FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt -				WHERE n.user_id = ' . (int) $options['user_id'] . ' -					AND nt.notification_type_id = n.notification_type_id -					AND nt.notification_type_enabled = 1'; -			$result = $this->db->sql_query($sql); -			$total_count = (int) $this->db->sql_fetchfield('total_count'); -			$this->db->sql_freeresult($result); +			$notification_type_id = $this->get_notification_type_id($notification_type_name);  		} - -		if (!$options['count_total'] || $total_count) +		else  		{ -			$rowset = array(); -			$selected_unread_count = 0; - -			// Get the main notifications -			$sql = 'SELECT n.*, nt.notification_type_name -				FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt -				WHERE n.user_id = ' . (int) $options['user_id'] . -					(($options['notification_id']) ? ' AND ' . $this->db->sql_in_set('n.notification_id', $options['notification_id']) : '') . ' -					AND nt.notification_type_id = n.notification_type_id -					AND nt.notification_type_enabled = 1 -				ORDER BY n.' . $this->db->sql_escape($options['order_by']) . ' ' . $this->db->sql_escape($options['order_dir']); -			$result = $this->db->sql_query_limit($sql, $options['limit'], $options['start']); - -			while ($row = $this->db->sql_fetchrow($result)) -			{ -				$rowset[$row['notification_id']] = $row; -				$selected_unread_count += (int) !$row['notification_read']; -			} -			$this->db->sql_freeresult($result); - -			// Get all unread notifications -			if ($selected_unread_count < $unread_count && $options['all_unread'] && !empty($rowset)) -			{ -				$sql = 'SELECT n.*, nt.notification_type_name -				FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt -					WHERE n.user_id = ' . (int) $options['user_id'] . ' -						AND n.notification_read = 0 -						AND ' . $this->db->sql_in_set('n.notification_id', array_keys($rowset), true) . ' -						AND nt.notification_type_id = n.notification_type_id -						AND nt.notification_type_enabled = 1 -					ORDER BY n.' . $this->db->sql_escape($options['order_by']) . ' ' . $this->db->sql_escape($options['order_dir']); -				$result = $this->db->sql_query_limit($sql, $options['limit'], $options['start']); - -				while ($row = $this->db->sql_fetchrow($result)) -				{ -					$rowset[$row['notification_id']] = $row; -				} -				$this->db->sql_freeresult($result); -			} - -			foreach ($rowset as $row) -			{ -				$notification = $this->get_item_type_class($row['notification_type_name'], $row); - -				// Array of user_ids to query all at once -				$user_ids = array_merge($user_ids, $notification->users_to_query()); - -				// Some notification types also require querying additional tables themselves -				if (!isset($load_special[$row['notification_type_name']])) -				{ -					$load_special[$row['notification_type_name']] = array(); -				} -				$load_special[$row['notification_type_name']] = array_merge($load_special[$row['notification_type_name']], $notification->get_load_special()); - -				$notifications[$row['notification_id']] = $notification; -			} - -			$this->user_loader->load_users($user_ids); - -			// Allow each type to load its own special items -			foreach ($load_special as $item_type => $data) -			{ -				$item_class = $this->get_item_type_class($item_type); - -				$item_class->load_special($data, $notifications); -			} +			$notification_type_id = false;  		} -		return array( -			'notifications'		=> $notifications, -			'unread_count'		=> $unread_count, -			'total_count'		=> $total_count, -		); +		/** @var method\method_interface $method */ +		foreach ($this->get_available_subscription_methods() as $method) +		{ +			$method->mark_notifications($notification_type_id, $item_id, $user_id, $time, $mark_read); +		}  	}  	/** -	* Mark notifications read -	* -	* @param bool|string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types). False to mark read for all item types -	* @param bool|int|array $item_id Item id or array of item ids. False to mark read for all item ids -	* @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids -	* @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) -	*/ -	public function mark_notifications_read($notification_type_name, $item_id, $user_id, $time = false) +	 * Mark notifications read or unread from a parent identifier for all available methods +	 * +	 * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types) +	 * @param bool|int|array $item_parent_id Item parent id or array of item parent ids. False to mark read for all item parent ids +	 * @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids +	 * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) +	 * +	 * @deprecated since 3.2 +	 */ +	public function mark_notifications_read_by_parent($notification_type_name, $item_parent_id, $user_id, $time = false)  	{ -		$time = ($time !== false) ? $time : time(); - -		$sql = 'UPDATE ' . $this->notifications_table . " -			SET notification_read = 1 -			WHERE notification_time <= " . (int) $time . -				(($notification_type_name !== false) ? ' AND ' . $this->db->sql_in_set('notification_type_id', $this->get_notification_type_ids($notification_type_name)) : '') . -				(($user_id !== false) ? ' AND ' . $this->db->sql_in_set('user_id', $user_id) : '') . -				(($item_id !== false) ? ' AND ' . $this->db->sql_in_set('item_id', $item_id) : ''); -		$this->db->sql_query($sql); +		$this->mark_notifications_by_parent($notification_type_name, $item_parent_id, $user_id, $time);  	}  	/** -	* Mark notifications read from a parent identifier +	* Mark notifications read or unread from a parent identifier for all available methods  	*  	* @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types)  	* @param bool|int|array $item_parent_id Item parent id or array of item parent ids. False to mark read for all item parent ids  	* @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids  	* @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) +	* @param bool $mark_read Define if the notification as to be set to True or False. (Default: True)  	*/ -	public function mark_notifications_read_by_parent($notification_type_name, $item_parent_id, $user_id, $time = false) +	public function mark_notifications_by_parent($notification_type_name, $item_parent_id, $user_id, $time = false, $mark_read = true)  	{ -		$time = ($time !== false) ? $time : time(); - -		$sql = 'UPDATE ' . $this->notifications_table . " -			SET notification_read = 1 -			WHERE notification_time <= " . (int) $time . -				(($notification_type_name !== false) ? ' AND ' . $this->db->sql_in_set('notification_type_id', $this->get_notification_type_ids($notification_type_name)) : '') . -				(($item_parent_id !== false) ? ' AND ' . $this->db->sql_in_set('item_parent_id', $item_parent_id, false, true) : '') . -				(($user_id !== false) ? ' AND ' . $this->db->sql_in_set('user_id', $user_id) : ''); -		$this->db->sql_query($sql); +		if (is_array($notification_type_name)) +		{ +			$notification_type_id = $this->get_notification_type_ids($notification_type_name); +		} +		else +		{ +			$notification_type_id = $this->get_notification_type_id($notification_type_name); +		} + +		/** @var method\method_interface $method */ +		foreach ($this->get_available_subscription_methods() as $method) +		{ +			$method->mark_notifications_by_parent($notification_type_id, $item_parent_id, $user_id, $time, $mark_read); +		}  	}  	/** -	* Mark notifications read +	* Mark notifications read or unread for a given method  	* +	* @param string $method_name  	* @param int|array $notification_id Notification id or array of notification ids.  	* @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) +	* @param bool $mark_read Define if the notification as to be set to True or False. (Default: True)  	*/ -	public function mark_notifications_read_by_id($notification_id, $time = false) +	public function mark_notifications_by_id($method_name, $notification_id, $time = false, $mark_read = true)  	{ -		$time = ($time !== false) ? $time : time(); +		$method = $this->get_method_class($method_name); -		$sql = 'UPDATE ' . $this->notifications_table . " -			SET notification_read = 1 -			WHERE notification_time <= " . (int) $time . ' -				AND ' . $this->db->sql_in_set('notification_id', $notification_id); -		$this->db->sql_query($sql); +		if ($method instanceof \phpbb\notification\method\method_interface && $method->is_available()) +		{ +			$method->mark_notifications_by_id($notification_id, $time, $mark_read); +		}  	}  	/** @@ -350,8 +269,6 @@ class manager  			return $notified_users;  		} -		$item_id = $this->get_item_type_class($notification_type_name)->get_item_id($data); -  		// find out which users want to receive this type of notification  		$notify_users = $this->get_item_type_class($notification_type_name)->find_users_for_notification($data, $options); @@ -404,25 +321,23 @@ class manager  		$item_id = $this->get_item_type_class($notification_type_name)->get_item_id($data);  		$user_ids = array(); -		$notification_objects = $notification_methods = array(); +		$notification_methods = array();  		// Never send notifications to the anonymous user!  		unset($notify_users[ANONYMOUS]);  		// Make sure not to send new notifications to users who've already been notified about this item  		// This may happen when an item was added, but now new users are able to see the item -		$sql = 'SELECT n.user_id -			FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt -			WHERE n.notification_type_id = ' . (int) $notification_type_id . ' -				AND n.item_id = ' . (int) $item_id . ' -				AND nt.notification_type_id = n.notification_type_id -				AND nt.notification_type_enabled = 1'; -		$result = $this->db->sql_query($sql); -		while ($row = $this->db->sql_fetchrow($result)) +		// We remove each user which was already notified by at least one method. +		/** @var method\method_interface $method */ +		foreach ($this->get_subscription_methods_instances() as $method)  		{ -			unset($notify_users[$row['user_id']]); +			$notified_users = $method->get_notified_users($notification_type_id, array('item_id' => $item_id)); +			foreach ($notified_users as $user => $notifications) +			{ +				unset($notify_users[$user]); +			}  		} -		$this->db->sql_freeresult($result);  		if (!sizeof($notify_users))  		{ @@ -434,8 +349,6 @@ class manager  		$pre_create_data = $notification->pre_create_insert_array($data, $notify_users);  		unset($notification); -		$insert_buffer = new \phpbb\db\sql_insert_buffer($this->db, $this->notifications_table); -  		// Go through each user so we can insert a row in the DB and then notify them by their desired means  		foreach ($notify_users as $user => $methods)  		{ @@ -443,8 +356,8 @@ class manager  			$notification->user_id = (int) $user; -			// Insert notification row using buffer. -			$insert_buffer->insert($notification->create_insert_array($data, $pre_create_data)); +			// Generate the insert_array +			$notification->create_insert_array($data, $pre_create_data);  			// Users are needed to send notifications  			$user_ids = array_merge($user_ids, $notification->users_to_query()); @@ -452,20 +365,15 @@ class manager  			foreach ($methods as $method)  			{  				// setup the notification methods and add the notification to the queue -				if ($method) // blank means we just insert it as a notification, but do not notify them by any other means +				if (!isset($notification_methods[$method]))  				{ -					if (!isset($notification_methods[$method])) -					{ -						$notification_methods[$method] = $this->get_method_class($method); -					} - -					$notification_methods[$method]->add_to_queue($notification); +					$notification_methods[$method] = $this->get_method_class($method);  				} + +				$notification_methods[$method]->add_to_queue($notification);  			}  		} -		$insert_buffer->flush(); -  		// We need to load all of the users to send notifications  		$this->user_loader->load_users($user_ids); @@ -477,12 +385,13 @@ class manager  	}  	/** -	* Update a notification +	* Update notification  	*  	* @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types)  	* @param array $data Data specific for this type that will be updated +	* @param array $options  	*/ -	public function update_notifications($notification_type_name, $data) +	public function update_notifications($notification_type_name, array $data, array $options = array())  	{  		if (is_array($notification_type_name))  		{ @@ -494,27 +403,28 @@ class manager  			return;  		} -		$notification = $this->get_item_type_class($notification_type_name); +		$this->update_notification($this->get_item_type_class($notification_type_name), $data, $options); +	} -		// Allow the notifications class to over-ride the update_notifications functionality -		if (method_exists($notification, 'update_notifications')) +	/** +	* Update a notification +	* +	* @param \phpbb\notification\type\type_interface $notification The notification +	* @param array $data Data specific for this type that will be updated +	* @param array $options +	*/ +	public function update_notification(\phpbb\notification\type\type_interface $notification, array $data, array $options = array()) +	{ +		if (empty($options))  		{ -			// Return False to over-ride the rest of the update -			if ($notification->update_notifications($data) === false) -			{ -				return; -			} +			$options['item_id'] = $notification->get_item_id($data);  		} -		$notification_type_id = $this->get_notification_type_id($notification_type_name); -		$item_id = $notification->get_item_id($data); -		$update_array = $notification->create_update_array($data); - -		$sql = 'UPDATE ' . $this->notifications_table . ' -			SET ' . $this->db->sql_build_array('UPDATE', $update_array) . ' -			WHERE notification_type_id = ' . (int) $notification_type_id . ' -				AND item_id = ' . (int) $item_id; -		$this->db->sql_query($sql); +		/** @var method\method_interface $method */ +		foreach ($this->get_available_subscription_methods() as $method) +		{ +			$method->update_notification($notification, $data, $options); +		}  	}  	/** @@ -523,14 +433,15 @@ class manager  	* @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $item_id is identical for the specified types)  	* @param int|array $item_id Identifier within the type (or array of ids)  	* @param mixed $parent_id Parent identifier within the type (or array of ids), used in combination with item_id if specified (Default: false; not checked) +	* @param mixed $user_id User id (Default: false; not checked)  	*/ -	public function delete_notifications($notification_type_name, $item_id, $parent_id = false) +	public function delete_notifications($notification_type_name, $item_id, $parent_id = false, $user_id = false)  	{  		if (is_array($notification_type_name))  		{  			foreach ($notification_type_name as $type)  			{ -				$this->delete_notifications($type, $item_id, $parent_id); +				$this->delete_notifications($type, $item_id, $parent_id, $user_id);  			}  			return; @@ -538,11 +449,11 @@ class manager  		$notification_type_id = $this->get_notification_type_id($notification_type_name); -		$sql = 'DELETE FROM ' . $this->notifications_table . ' -			WHERE notification_type_id = ' . (int) $notification_type_id . ' -				AND ' . $this->db->sql_in_set('item_id', $item_id) . -				(($parent_id !== false) ? ' AND ' . $this->db->sql_in_set('item_parent_id', $parent_id) : ''); -		$this->db->sql_query($sql); +		/** @var method\method_interface $method */ +		foreach ($this->get_available_subscription_methods() as $method) +		{ +			$method->delete_notifications($notification_type_id, $item_id, $parent_id, $user_id); +		}  	}  	/** @@ -558,6 +469,7 @@ class manager  			foreach ($this->notification_types as $type_name => $data)  			{ +				/** @var type\base $type */  				$type = $this->get_item_type_class($type_name);  				if ($type instanceof \phpbb\notification\type\type_interface && $type->is_available()) @@ -593,16 +505,55 @@ class manager  	{  		$subscription_methods = array(); +		/** @var method\method_interface $method */ +		foreach ($this->get_available_subscription_methods() as $method_name => $method) +		{ +			$subscription_methods[$method_name] = array( +				'id'		=> $method->get_type(), +				'lang'		=> str_replace('.', '_', strtoupper($method->get_type())), +			); +		} + +		return $subscription_methods; +	} + +	/** +	* Get all of the subscription methods +	* +	* @return array Array of method's instances +	*/ +	private function get_subscription_methods_instances() +	{ +		$subscription_methods = array(); +  		foreach ($this->notification_methods as $method_name => $data)  		{  			$method = $this->get_method_class($method_name); -			if ($method instanceof \phpbb\notification\method\method_interface && $method->is_available()) +			if ($method instanceof \phpbb\notification\method\method_interface)  			{ -				$subscription_methods[$method_name] = array( -					'id'		=> $method->get_type(), -					'lang'		=> str_replace('.', '_', strtoupper($method->get_type())), -				); +				$subscription_methods[$method_name] = $method; +			} +		} + +		return $subscription_methods; +	} + +	/** +	* Get all of the available subscription methods +	* +	* @return array Array of method's instances +	*/ +	private function get_available_subscription_methods() +	{ +		$subscription_methods = array(); + +		/** @var method\method_interface $method */ +		foreach ($this->get_subscription_methods_instances() as $method_name => $method) +		{ +			if ($method->is_available()) +			{ +				$subscription_methods[$method_name] = $method;  			}  		} @@ -646,9 +597,10 @@ class manager  	*/  	public function get_global_subscriptions($user_id = false)  	{ -		$user_id = ($user_id === false) ? $this->user->data['user_id'] : $user_id; +		$user_id = $user_id ?: $this->user->data['user_id'];  		$subscriptions = array(); +		$default_methods = $this->get_default_methods();  		$user_notifications = $this->get_user_notifications($user_id); @@ -656,29 +608,32 @@ class manager  		{  			foreach ($types as $id => $type)  			{ - -				if (empty($user_notifications[$id])) -				{ -					// No rows at all, default to '' -					$subscriptions[$id] = array(''); -				} -				else +				$type_subscriptions = $default_methods; +				if (!empty($user_notifications[$id]))  				{  					foreach ($user_notifications[$id] as $user_notification)  					{ +						$key = array_search($user_notification['method'], $type_subscriptions, true);  						if (!$user_notification['notify'])  						{ +							if ($key !== false) +							{ +								unset($type_subscriptions[$key]); +							} +  							continue;  						} - -						if (!isset($subscriptions[$id])) +						else if ($key === false)  						{ -							$subscriptions[$id] = array(); +							$type_subscriptions[] = $user_notification['method'];  						} - -						$subscriptions[$id][] = $user_notification['method'];  					}  				} + +				if (!empty($type_subscriptions)) +				{ +					$subscriptions[$id] = $type_subscriptions; +				}  			}  		} @@ -690,15 +645,20 @@ class manager  	*  	* @param string $item_type Type identifier of the subscription  	* @param int $item_id The id of the item -	* @param string $method The method of the notification e.g. '', 'email', or 'jabber' +	* @param string $method The method of the notification e.g. 'board', 'email', or 'jabber' +	*                       (if null a subscription will be added for all the defaults methods)  	* @param bool|int $user_id The user_id to add the subscription for (bool false for current user)  	*/ -	public function add_subscription($item_type, $item_id = 0, $method = '', $user_id = false) +	public function add_subscription($item_type, $item_id = 0, $method = null, $user_id = false)  	{ -		if ($method !== '') +		if ($method === null)  		{ -			// Make sure to subscribe them to the base subscription -			$this->add_subscription($item_type, $item_id, '', $user_id); +			foreach ($this->get_default_methods() as $method_name) +			{ +				$this->add_subscription($item_type, $item_id, $method_name, $user_id); +			} + +			return;  		}  		$user_id = ($user_id === false) ? $this->user->data['user_id'] : $user_id; @@ -742,33 +702,23 @@ class manager  	*  	* @param string $item_type Type identifier of the subscription  	* @param int $item_id The id of the item -	* @param string $method The method of the notification e.g. '', 'email', or 'jabber' +	* @param string $method The method of the notification e.g. 'board', 'email', or 'jabber'  	* @param bool|int $user_id The user_id to add the subscription for (bool false for current user)  	*/ -	public function delete_subscription($item_type, $item_id = 0, $method = '', $user_id = false) +	public function delete_subscription($item_type, $item_id = 0, $method = null, $user_id = false)  	{ -		$user_id = ($user_id === false) ? $this->user->data['user_id'] : $user_id; - -		// If no method, make sure that no other notification methods for this item are selected before deleting -		if ($method === '') +		if ($method === null)  		{ -			$sql = 'SELECT COUNT(*) as num_notifications -				FROM ' . $this->user_notifications_table . " -				WHERE item_type = '" . $this->db->sql_escape($item_type) . "' -					AND item_id = " . (int) $item_id . ' -					AND user_id = ' .(int) $user_id . " -					AND method <> '' -					AND notify = 1"; -			$this->db->sql_query($sql); -			$num_notifications = $this->db->sql_fetchfield('num_notifications'); -			$this->db->sql_freeresult(); - -			if ($num_notifications) +			foreach ($this->get_default_methods() as $method_name)  			{ -				return; +				$this->delete_subscription($item_type, $item_id, $method_name, $user_id);  			} + +			return;  		} +		$user_id = $user_id ?: $this->user->data['user_id']; +  		$sql = 'UPDATE ' . $this->user_notifications_table . "  			SET notify = 0  			WHERE item_type = '" . $this->db->sql_escape($item_type) . "' @@ -828,15 +778,12 @@ class manager  		{  			$notification_type_id = $this->get_notification_type_id($notification_type_name); -			$sql = 'DELETE FROM ' . $this->notifications_table . ' -				WHERE notification_type_id = ' . (int) $notification_type_id; -			$this->db->sql_query($sql); - -			$sql = 'DELETE FROM ' . $this->notification_types_table . ' -				WHERE notification_type_id = ' . (int) $notification_type_id; -			$this->db->sql_query($sql); +			/** @var method\method_interface $method */ +			foreach ($this->get_available_subscription_methods() as $method) +			{ +				$method->purge_notifications($notification_type_id); +			} -			$this->cache->destroy('notification_type_ids');  		}  		catch (\phpbb\notification\exception $e)  		{ @@ -869,17 +816,40 @@ class manager  	*/  	public function prune_notifications($timestamp, $only_read = true)  	{ -		$sql = 'DELETE FROM ' . $this->notifications_table . ' -			WHERE notification_time < ' . (int) $timestamp . -				(($only_read) ? ' AND notification_read = 1' : ''); -		$this->db->sql_query($sql); +		/** @var method\method_interface $method */ +		foreach ($this->get_available_subscription_methods() as $method) +		{ +			$method->prune_notifications($timestamp, $only_read); +		} +	} -		$this->config->set('read_notification_last_gc', time(), false); +	/** +	 * Helper to get the list of methods enabled by default +	 * +	 * @return method\method_interface[] +	 */ +	public function get_default_methods() +	{ +		$default_methods = array(); + +		foreach ($this->notification_methods as $method) +		{ +			if ($method->is_enabled_by_default() && $method->is_available()) +			{ +				$default_methods[] = $method->get_type(); +			} +		} + +		return $default_methods;  	}  	/** -	* Helper to get the notifications item type class and set it up -	*/ +	 * Helper to get the notifications item type class and set it up +	 * +	 * @param string $notification_type_name +	 * @param array  $data +	 * @return type\type_interface +	 */  	public function get_item_type_class($notification_type_name, $data = array())  	{  		$item = $this->load_object($notification_type_name); @@ -890,16 +860,22 @@ class manager  	}  	/** -	* Helper to get the notifications method class and set it up -	*/ +	 * Helper to get the notifications method class and set it up +	 * +	 * @param string $method_name +	 * @return method\method_interface +	 */  	public function get_method_class($method_name)  	{  		return $this->load_object($method_name);  	}  	/** -	* Helper to load objects (notification types/methods) -	*/ +	 * Helper to load objects (notification types/methods) +	 * +	 * @param string $object_name +	 * @return method\method_interface|type\type_interface +	 */  	protected function load_object($object_name)  	{  		$object = $this->phpbb_container->get($object_name); @@ -945,7 +921,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( @@ -986,4 +962,26 @@ class manager  		return $notification_type_ids;  	} + +	/** +	* Find the users which are already notified +	* +	* @param bool|string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types). False to retrieve all item types +	* @param array $options +	* @return array The list of the notified users +	*/ +	public function get_notified_users($notification_type_name, array $options) +	{ +		$notification_type_id = $this->get_notification_type_id($notification_type_name); + +		$notified_users = array(); + +		/** @var method\method_interface $method */ +		foreach ($this->get_available_subscription_methods() as $method) +		{ +			$notified_users = $notified_users + $method->get_notified_users($notification_type_id, $options); +		} + +		return $notified_users; +	}  } diff --git a/phpBB/phpbb/notification/method/base.php b/phpBB/phpbb/notification/method/base.php index 6ee1d2984a..4a183ca508 100644 --- a/phpBB/phpbb/notification/method/base.php +++ b/phpBB/phpbb/notification/method/base.php @@ -21,36 +21,6 @@ abstract class base implements \phpbb\notification\method\method_interface  	/** @var \phpbb\notification\manager */  	protected $notification_manager; -	/** @var \phpbb\user_loader */ -	protected $user_loader; - -	/** @var \phpbb\db\driver\driver_interface */ -	protected $db; - -	/** @var \phpbb\cache\driver\driver_interface */ -	protected $cache; - -	/** @var \phpbb\template\template */ -	protected $template; - -	/** @var \phpbb\extension\manager */ -	protected $extension_manager; - -	/** @var \phpbb\user */ -	protected $user; - -	/** @var \phpbb\auth\auth */ -	protected $auth; - -	/** @var \phpbb\config\config */ -	protected $config; - -	/** @var string */ -	protected $phpbb_root_path; - -	/** @var string */ -	protected $php_ext; -  	/**  	* Queue of messages to be sent  	* @@ -59,38 +29,43 @@ abstract class base implements \phpbb\notification\method\method_interface  	protected $queue = array();  	/** -	* Notification Method Base Constructor +	* Set notification manager (required)  	* -	* @param \phpbb\user_loader $user_loader -	* @param \phpbb\db\driver\driver_interface $db -	* @param \phpbb\cache\driver\driver_interface $cache -	* @param \phpbb\user $user -	* @param \phpbb\auth\auth $auth -	* @param \phpbb\config\config $config -	* @param string $phpbb_root_path -	* @param string $php_ext -	* @return \phpbb\notification\method\base +	* @param \phpbb\notification\manager $notification_manager  	*/ -	public function __construct(\phpbb\user_loader $user_loader, \phpbb\db\driver\driver_interface $db, \phpbb\cache\driver\driver_interface $cache, $user, \phpbb\auth\auth $auth, \phpbb\config\config $config, $phpbb_root_path, $php_ext) +	public function set_notification_manager(\phpbb\notification\manager $notification_manager)  	{ -		$this->user_loader = $user_loader; -		$this->db = $db; -		$this->cache = $cache; -		$this->user = $user; -		$this->auth = $auth; -		$this->config = $config; -		$this->phpbb_root_path = $phpbb_root_path; -		$this->php_ext = $php_ext; +		$this->notification_manager = $notification_manager;  	}  	/** -	* Set notification manager (required) +	* Is the method enable by default?  	* -	* @param \phpbb\notification\manager $notification_manager +	* @return bool  	*/ -	public function set_notification_manager(\phpbb\notification\manager $notification_manager) +	public function is_enabled_by_default()  	{ -		$this->notification_manager = $notification_manager; +		return false; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function get_notified_users($notification_type_id, array $options) +	{ +		return array(); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function load_notifications(array $options = array()) +	{ +		return array( +			'notifications'		=> array(), +			'unread_count'		=> 0, +			'total_count'		=> 0, +		);  	}  	/** @@ -104,6 +79,55 @@ abstract class base implements \phpbb\notification\method\method_interface  	}  	/** +	* {@inheritdoc} +	*/ +	public function update_notification($notification, array $data, array $options) +	{ +	} + +	/** +	* {@inheritdoc +	*/ +	public function mark_notifications($notification_type_id, $item_id, $user_id, $time = false, $mark_read = true) +	{ +	} + +	/** +	* {@inheritdoc} +	*/ +	public function mark_notifications_by_parent($notification_type_id, $item_parent_id, $user_id, $time = false, $mark_read = true) +	{ +	} + +	/** +	* {@inheritdoc} +	*/ +	public function mark_notifications_by_id($notification_id, $time = false, $mark_read = true) +	{ +	} + +	/** +	* {@inheritdoc} +	*/ +	public function delete_notifications($notification_type_id, $item_id, $parent_id = false, $user_id = false) +	{ +	} + +	/** +	* {@inheritdoc} +	*/ +	public function prune_notifications($timestamp, $only_read = true) +	{ +	} + +	/** +	* {@inheritdoc} +	*/ +	public function purge_notifications($notification_type_id) +	{ +	} + +	/**  	* Empty the queue  	*/  	protected function empty_queue() diff --git a/phpBB/phpbb/notification/method/board.php b/phpBB/phpbb/notification/method/board.php new file mode 100644 index 0000000000..931b252daa --- /dev/null +++ b/phpBB/phpbb/notification/method/board.php @@ -0,0 +1,399 @@ +<?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\notification\method; + +/** +* In Board notification method class +* This class handles in board notifications. This method is enabled by default. +* +* @package notifications +*/ +class board extends \phpbb\notification\method\base +{ +	/** @var \phpbb\user_loader */ +	protected $user_loader; + +	/** @var \phpbb\db\driver\driver_interface */ +	protected $db; + +	/** @var \phpbb\cache\driver\driver_interface */ +	protected $cache; + +	/** @var \phpbb\user */ +	protected $user; + +	/** @var \phpbb\config\config */ +	protected $config; + +	/** @var string */ +	protected $notification_types_table; + +	/** @var string */ +	protected $notifications_table; + +	/** +	* Notification Method Board Constructor +	* +	* @param \phpbb\user_loader $user_loader +	* @param \phpbb\db\driver\driver_interface $db +	* @param \phpbb\cache\driver\driver_interface $cache +	* @param \phpbb\user $user +	* @param \phpbb\config\config $config +	* @param string $notification_types_table +	* @param string $notifications_table +	*/ +	public function __construct(\phpbb\user_loader $user_loader, \phpbb\db\driver\driver_interface $db, \phpbb\cache\driver\driver_interface $cache, \phpbb\user $user, \phpbb\config\config $config, $notification_types_table, $notifications_table) +	{ +		$this->user_loader = $user_loader; +		$this->db = $db; +		$this->cache = $cache; +		$this->user = $user; +		$this->config = $config; +		$this->notification_types_table = $notification_types_table; +		$this->notifications_table = $notifications_table; + +	} + +	/** +	* {@inheritdoc} +	*/ +	public function add_to_queue(\phpbb\notification\type\type_interface $notification) +	{ +		$this->queue[] = $notification; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function get_type() +	{ +		return 'notification.method.board'; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function is_available() +	{ +		return $this->config['allow_board_notifications']; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function is_enabled_by_default() +	{ +		return true; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function get_notified_users($notification_type_id, array $options) +	{ +		$notified_users = array(); +		$sql = 'SELECT n.* +			FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt +			WHERE n.notification_type_id = ' . (int) $notification_type_id . +			(isset($options['item_id']) ? ' AND n.item_id = ' . (int) $options['item_id'] : '') . +			(isset($options['item_parent_id']) ? ' AND n.item_parent_id = ' . (int) $options['item_parent_id'] : '') . +			(isset($options['user_id']) ? ' AND n.user_id = ' . (int) $options['user_id'] : '') . +			(isset($options['read']) ? ' AND n.notification_read = ' . (int) $options['read'] : '') .' +				AND nt.notification_type_id = n.notification_type_id +				AND nt.notification_type_enabled = 1'; +		$result = $this->db->sql_query($sql); +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$notified_users[$row['user_id']] = $row; +		} +		$this->db->sql_freeresult($result); + +		return $notified_users; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function load_notifications(array $options = array()) +	{ +		// Merge default options +		$options = array_merge(array( +			'notification_id'	=> false, +			'user_id'			=> $this->user->data['user_id'], +			'order_by'			=> 'notification_time', +			'order_dir'			=> 'DESC', +			'limit'				=> 0, +			'start'				=> 0, +			'all_unread'		=> false, +			'count_unread'		=> false, +			'count_total'		=> false, +		), $options); + +		// If all_unread, count_unread must be true +		$options['count_unread'] = ($options['all_unread']) ? true : $options['count_unread']; + +		// Anonymous users and bots never receive notifications +		if ($options['user_id'] == $this->user->data['user_id'] && ($this->user->data['user_id'] == ANONYMOUS || $this->user->data['user_type'] == USER_IGNORE)) +		{ +			return array( +				'notifications'		=> array(), +				'unread_count'		=> 0, +				'total_count'		=> 0, +			); +		} + +		$notifications = $user_ids = array(); +		$load_special = array(); +		$total_count = $unread_count = 0; + +		if ($options['count_unread']) +		{ +			// Get the total number of unread notifications +			$sql = 'SELECT COUNT(n.notification_id) AS unread_count +				FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt +				WHERE n.user_id = ' . (int) $options['user_id'] . ' +					AND n.notification_read = 0 +					AND nt.notification_type_id = n.notification_type_id +					AND nt.notification_type_enabled = 1'; +			$result = $this->db->sql_query($sql); +			$unread_count = (int) $this->db->sql_fetchfield('unread_count'); +			$this->db->sql_freeresult($result); +		} + +		if ($options['count_total']) +		{ +			// Get the total number of notifications +			$sql = 'SELECT COUNT(n.notification_id) AS total_count +				FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt +				WHERE n.user_id = ' . (int) $options['user_id'] . ' +					AND nt.notification_type_id = n.notification_type_id +					AND nt.notification_type_enabled = 1'; +			$result = $this->db->sql_query($sql); +			$total_count = (int) $this->db->sql_fetchfield('total_count'); +			$this->db->sql_freeresult($result); +		} + +		if (!$options['count_total'] || $total_count) +		{ +			$rowset = array(); + +			// Get the main notifications +			$sql = 'SELECT n.*, nt.notification_type_name +				FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt +				WHERE n.user_id = ' . (int) $options['user_id'] . +				(($options['notification_id']) ? ((is_array($options['notification_id'])) ? ' AND ' . $this->db->sql_in_set('n.notification_id', $options['notification_id']) : ' AND n.notification_id = ' . (int) $options['notification_id']) : '') . ' +					AND nt.notification_type_id = n.notification_type_id +					AND nt.notification_type_enabled = 1 +				ORDER BY n.' . $this->db->sql_escape($options['order_by']) . ' ' . $this->db->sql_escape($options['order_dir']); +			$result = $this->db->sql_query_limit($sql, $options['limit'], $options['start']); + +			while ($row = $this->db->sql_fetchrow($result)) +			{ +				$rowset[$row['notification_id']] = $row; +			} +			$this->db->sql_freeresult($result); + +			// Get all unread notifications +			if ($unread_count && $options['all_unread'] && !empty($rowset)) +			{ +				$sql = 'SELECT n.*, nt.notification_type_name +				FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt +					WHERE n.user_id = ' . (int) $options['user_id'] . ' +						AND n.notification_read = 0 +						AND ' . $this->db->sql_in_set('n.notification_id', array_keys($rowset), true) . ' +						AND nt.notification_type_id = n.notification_type_id +						AND nt.notification_type_enabled = 1 +					ORDER BY n.' . $this->db->sql_escape($options['order_by']) . ' ' . $this->db->sql_escape($options['order_dir']); +				$result = $this->db->sql_query_limit($sql, $options['limit'], $options['start']); + +				while ($row = $this->db->sql_fetchrow($result)) +				{ +					$rowset[$row['notification_id']] = $row; +				} +				$this->db->sql_freeresult($result); +			} + +			foreach ($rowset as $row) +			{ +				$notification = $this->notification_manager->get_item_type_class($row['notification_type_name'], $row); + +				// Array of user_ids to query all at once +				$user_ids = array_merge($user_ids, $notification->users_to_query()); + +				// Some notification types also require querying additional tables themselves +				if (!isset($load_special[$row['notification_type_name']])) +				{ +					$load_special[$row['notification_type_name']] = array(); +				} +				$load_special[$row['notification_type_name']] = array_merge($load_special[$row['notification_type_name']], $notification->get_load_special()); + +				$notifications[$row['notification_id']] = $notification; +			} + +			$this->user_loader->load_users($user_ids); + +			// Allow each type to load its own special items +			foreach ($load_special as $item_type => $data) +			{ +				$item_class = $this->notification_manager->get_item_type_class($item_type); + +				$item_class->load_special($data, $notifications); +			} +		} + +		return array( +			'notifications'		=> $notifications, +			'unread_count'		=> $unread_count, +			'total_count'		=> $total_count, +		); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function notify() +	{ +		$insert_buffer = new \phpbb\db\sql_insert_buffer($this->db, $this->notifications_table); + +		/** @var \phpbb\notification\type\type_interface $notification */ +		foreach ($this->queue as $notification) +		{ +			$data = $notification->get_insert_array(); +			$insert_buffer->insert($data); +		} + +		$insert_buffer->flush(); + +		// We're done, empty the queue +		$this->empty_queue(); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function update_notification($notification, array $data, array $options) +	{ +		// Allow the notifications class to over-ride the update_notifications functionality +		if (method_exists($notification, 'update_notifications')) +		{ +			// Return False to over-ride the rest of the update +			if ($notification->update_notifications($data) === false) +			{ +				return; +			} +		} + +		$notification_type_id = $this->notification_manager->get_notification_type_id($notification->get_type()); +		$update_array = $notification->create_update_array($data); + +		$sql = 'UPDATE ' . $this->notifications_table . ' +			SET ' . $this->db->sql_build_array('UPDATE', $update_array) . ' +			WHERE notification_type_id = ' . (int) $notification_type_id . +			(isset($options['item_id']) ? ' AND item_id = ' . (int) $options['item_id'] : '') . +			(isset($options['item_parent_id']) ? ' AND item_parent_id = ' . (int) $options['item_parent_id'] : '') . +			(isset($options['user_id']) ? ' AND user_id = ' . (int) $options['user_id'] : '') . +			(isset($options['read']) ? ' AND notification_read = ' . (int) $options['read'] : ''); +		$this->db->sql_query($sql); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function mark_notifications($notification_type_id, $item_id, $user_id, $time = false, $mark_read = true) +	{ +		$time = ($time !== false) ? $time : time(); + +		$sql = 'UPDATE ' . $this->notifications_table . ' +			SET notification_read = ' . ($mark_read ? 1 : 0) . ' +			WHERE notification_time <= ' . (int) $time . +			(($notification_type_id !== false) ? ' AND ' . +				(is_array($notification_type_id) ? $this->db->sql_in_set('notification_type_id', $notification_type_id) : 'notification_type_id = ' . $notification_type_id) : '') . +			(($user_id !== false) ? ' AND ' . (is_array($user_id) ? $this->db->sql_in_set('user_id', $user_id) : 'user_id = ' . (int) $user_id) : '') . +			(($item_id !== false) ? ' AND ' . (is_array($item_id) ? $this->db->sql_in_set('item_id', $item_id) : 'item_id = ' . (int) $item_id) : ''); +		$this->db->sql_query($sql); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function mark_notifications_by_parent($notification_type_id, $item_parent_id, $user_id, $time = false, $mark_read = true) +	{ +		$time = ($time !== false) ? $time : time(); + +		$sql = 'UPDATE ' . $this->notifications_table . ' +			SET notification_read = ' . ($mark_read ? 1 : 0) . ' +			WHERE notification_time <= ' . (int) $time . +			(($notification_type_id !== false) ? ' AND ' . +				(is_array($notification_type_id) ? $this->db->sql_in_set('notification_type_id', $notification_type_id) : 'notification_type_id = ' . $notification_type_id) : '') . +			(($item_parent_id !== false) ? ' AND ' . (is_array($item_parent_id) ? $this->db->sql_in_set('item_parent_id', $item_parent_id, false, true) : 'item_parent_id = ' . (int) $item_parent_id) : '') . +			(($user_id !== false) ? ' AND ' . (is_array($user_id) ? $this->db->sql_in_set('user_id', $user_id) : 'user_id = ' . (int) $user_id) : ''); +		$this->db->sql_query($sql); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function mark_notifications_by_id($notification_id, $time = false, $mark_read = true) +	{ +		$time = ($time !== false) ? $time : time(); + +		$sql = 'UPDATE ' . $this->notifications_table . ' +			SET notification_read = ' . ($mark_read ? 1 : 0) . ' +			WHERE notification_time <= ' . (int) $time . ' +				AND ' . ((is_array($notification_id)) ? $this->db->sql_in_set('notification_id', $notification_id) : 'notification_id = ' . (int) $notification_id); +		$this->db->sql_query($sql); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function delete_notifications($notification_type_id, $item_id, $parent_id = false, $user_id = false) +	{ +		$sql = 'DELETE FROM ' . $this->notifications_table . ' +			WHERE notification_type_id = ' . (int) $notification_type_id . ' +				AND ' . (is_array($item_id) ? $this->db->sql_in_set('item_id', $item_id) : 'item_id = ' . (int) $item_id) . +			(($parent_id !== false) ? ' AND ' . ((is_array($parent_id) ? $this->db->sql_in_set('item_parent_id', $parent_id) : 'item_parent_id = ' . (int) $parent_id)) : '') . +			(($user_id !== false) ? ' AND ' . ((is_array($user_id) ? $this->db->sql_in_set('user_id', $user_id) : 'user_id = ' . (int) $user_id)) : ''); +		$this->db->sql_query($sql); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function prune_notifications($timestamp, $only_read = true) +	{ +		$sql = 'DELETE FROM ' . $this->notifications_table . ' +			WHERE notification_time < ' . (int) $timestamp . +			(($only_read) ? ' AND notification_read = 1' : ''); +		$this->db->sql_query($sql); + +		$this->config->set('read_notification_last_gc', time(), false); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function purge_notifications($notification_type_id) +	{ +		$sql = 'DELETE FROM ' . $this->notifications_table . ' +			WHERE notification_type_id = ' . (int) $notification_type_id; +		$this->db->sql_query($sql); + +		$sql = 'DELETE FROM ' . $this->notification_types_table . ' +			WHERE notification_type_id = ' . (int) $notification_type_id; +		$this->db->sql_query($sql); + +		$this->cache->destroy('notification_type_ids'); +	} +} diff --git a/phpBB/phpbb/notification/method/email.php b/phpBB/phpbb/notification/method/email.php index a4b93bc85c..21a6559012 100644 --- a/phpBB/phpbb/notification/method/email.php +++ b/phpBB/phpbb/notification/method/email.php @@ -20,6 +20,29 @@ namespace phpbb\notification\method;  class email extends \phpbb\notification\method\messenger_base  { +	/** @var \phpbb\user */ +	protected $user; + +	/** @var \phpbb\config\config */ +	protected $config; + +	/** +	 * Notification Method email Constructor +	 * +	 * @param \phpbb\user_loader $user_loader +	 * @param \phpbb\user $user +	 * @param \phpbb\config\config $config +	 * @param string $phpbb_root_path +	 * @param string $php_ext +	 */ +	public function __construct(\phpbb\user_loader $user_loader, \phpbb\user $user, \phpbb\config\config $config, $phpbb_root_path, $php_ext) +	{ +		parent::__construct($user_loader, $phpbb_root_path, $php_ext); + +		$this->user = $user; +		$this->config = $config; +	} +  	/**  	* Get notification method name  	* diff --git a/phpBB/phpbb/notification/method/jabber.php b/phpBB/phpbb/notification/method/jabber.php index 09f186e3ca..509c6b432c 100644 --- a/phpBB/phpbb/notification/method/jabber.php +++ b/phpBB/phpbb/notification/method/jabber.php @@ -20,6 +20,29 @@ namespace phpbb\notification\method;  class jabber extends \phpbb\notification\method\messenger_base  { +	/** @var \phpbb\user */ +	protected $user; + +	/** @var \phpbb\config\config */ +	protected $config; + +	/** +	 * Notification Method jabber Constructor +	 * +	 * @param \phpbb\user_loader $user_loader +	 * @param \phpbb\user $user +	 * @param \phpbb\config\config $config +	 * @param string $phpbb_root_path +	 * @param string $php_ext +	 */ +	public function __construct(\phpbb\user_loader $user_loader, \phpbb\user $user, \phpbb\config\config $config, $phpbb_root_path, $php_ext) +	{ +		parent::__construct($user_loader, $phpbb_root_path, $php_ext); + +		$this->user = $user; +		$this->config = $config; +	} +  	/**  	* Get notification method name  	* @@ -61,6 +84,6 @@ class jabber extends \phpbb\notification\method\messenger_base  			return;  		} -		return $this->notify_using_messenger(NOTIFY_IM, 'short/'); +		$this->notify_using_messenger(NOTIFY_IM, 'short/');  	}  } diff --git a/phpBB/phpbb/notification/method/messenger_base.php b/phpBB/phpbb/notification/method/messenger_base.php index 3c6d617c66..812cd6a911 100644 --- a/phpBB/phpbb/notification/method/messenger_base.php +++ b/phpBB/phpbb/notification/method/messenger_base.php @@ -19,6 +19,29 @@ namespace phpbb\notification\method;  */  abstract class messenger_base extends \phpbb\notification\method\base  { +	/** @var \phpbb\user_loader */ +	protected $user_loader; + +	/** @var string */ +	protected $phpbb_root_path; + +	/** @var string */ +	protected $php_ext; + +	/** +	 * Notification Method Board Constructor +	 * +	 * @param \phpbb\user_loader $user_loader +	 * @param string $phpbb_root_path +	 * @param string $php_ext +	 */ +	public function __construct(\phpbb\user_loader $user_loader, $phpbb_root_path, $php_ext) +	{ +		$this->user_loader = $user_loader; +		$this->phpbb_root_path = $phpbb_root_path; +		$this->php_ext = $php_ext; +	} +  	/**  	* Notify using phpBB messenger  	* @@ -57,9 +80,9 @@ abstract class messenger_base extends \phpbb\notification\method\base  			include($this->phpbb_root_path . 'includes/functions_messenger.' . $this->php_ext);  		}  		$messenger = new \messenger(); -		$board_url = generate_board_url();  		// Time to go through the queue and send emails +		/** @var \phpbb\notification\type\type_interface $notification */  		foreach ($this->queue as $notification)  		{  			if ($notification->get_email_template() === false) diff --git a/phpBB/phpbb/notification/method/method_interface.php b/phpBB/phpbb/notification/method/method_interface.php index 76b0de179c..c2e4940485 100644 --- a/phpBB/phpbb/notification/method/method_interface.php +++ b/phpBB/phpbb/notification/method/method_interface.php @@ -26,12 +26,48 @@ interface method_interface  	public function get_type();  	/** +	* Is the method enable by default? +	* +	* @return bool +	*/ +	public function is_enabled_by_default(); + +	/**  	* Is this method available for the user?  	* This is checked on the notifications options  	*/  	public function is_available();  	/** +	* Return the list of the users already notified +	* +	* @param int $notification_type_id Type of the notification +	* @param array $options +	* @return array User +	*/ +	public function get_notified_users($notification_type_id, array $options); + +	/** +	* Load the user's notifications +	* +	* @param array $options Optional options to control what notifications are loaded +	*				notification_id		Notification id to load (or array of notification ids) +	*				user_id				User id to load notifications for (Default: $user->data['user_id']) +	*				order_by			Order by (Default: notification_time) +	*				order_dir			Order direction (Default: DESC) +	* 				limit				Number of notifications to load (Default: 5) +	* 				start				Notifications offset (Default: 0) +	* 				all_unread			Load all unread notifications? If set to true, count_unread is set to true (Default: false) +	* 				count_unread		Count all unread notifications? (Default: false) +	* 				count_total			Count all notifications? (Default: false) +	* @return array Array of information based on the request with keys: +	*	'notifications'		array of notification type objects +	*	'unread_count'		number of unread notifications the user has if count_unread is true in the options +	*	'total_count'		number of notifications the user has if count_total is true in the options +	*/ +	public function load_notifications(array $options = array()); + +	/**  	* Add a notification to the queue  	*  	* @param \phpbb\notification\type\type_interface $notification @@ -42,4 +78,72 @@ interface method_interface  	* Parse the queue and notify the users  	*/  	public function notify(); + +	/** +	* Update a notification +	* +	* @param \phpbb\notification\type\type_interface $notification Notification to update +	* @param array $data Data specific for this type that will be updated +	* @param array $options +	*/ +	public function update_notification($notification, array $data, array $options); + +	/** +	* Mark notifications read or unread +	* +	* @param bool|string $notification_type_id Type identifier of item types. False to mark read for all item types +	* @param bool|int|array $item_id Item id or array of item ids. False to mark read for all item ids +	* @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids +	* @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) +	* @param bool $mark_read Define if the notification as to be set to True or False. (Default: True) +	*/ +	public function mark_notifications($notification_type_id, $item_id, $user_id, $time = false, $mark_read = true); + +	/** +	* Mark notifications read or unread from a parent identifier +	* +	* @param string $notification_type_id Type identifier of item types +	* @param bool|int|array $item_parent_id Item parent id or array of item parent ids. False to mark read for all item parent ids +	* @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids +	* @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) +	* @param bool $mark_read Define if the notification as to be set to True or False. (Default: True) +	*/ +	public function mark_notifications_by_parent($notification_type_id, $item_parent_id, $user_id, $time = false, $mark_read = true); + +	/** +	* Mark notifications read or unread +	* +	* @param int $notification_id Notification id of notification ids. +	* @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) +	* @param bool $mark_read Define if the notification as to be set to True or False. (Default: True) +	*/ +	public function mark_notifications_by_id($notification_id, $time = false, $mark_read = true); + +	/** +	* Delete a notification +	* +	* @param string $notification_type_id Type identifier of item types +	* @param int|array $item_id Identifier within the type (or array of ids) +	* @param mixed $parent_id Parent identifier within the type (or array of ids), used in combination with item_id if specified (Default: false; not checked) +	* @param mixed $user_id User id (Default: false; not checked) +	*/ +	public function delete_notifications($notification_type_id, $item_id, $parent_id = false, $user_id = false); + +	/** +	* Delete all notifications older than a certain time +	* +	* @param int $timestamp Unix timestamp to delete all notifications that were created before +	* @param bool $only_read True (default) to only prune read notifications +	*/ +	public function prune_notifications($timestamp, $only_read = true); + +	/** +	* Purge all notifications of a certain type +	* +	* This should be called when an extension which has notification types +	* is purged so that all those notifications are removed +	* +	* @param string $notification_type_id Type identifier of the subscription +	*/ +	public function purge_notifications($notification_type_id);  } diff --git a/phpBB/phpbb/notification/type/admin_activate_user.php b/phpBB/phpbb/notification/type/admin_activate_user.php index 7c5c18aa47..9f2ae857ef 100644 --- a/phpBB/phpbb/notification/type/admin_activate_user.php +++ b/phpBB/phpbb/notification/type/admin_activate_user.php @@ -36,11 +36,27 @@ 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',  	); +	/** @var \phpbb\user_loader */ +	protected $user_loader; + +	/** @var \phpbb\config\config */ +	protected $config; + +	public function set_config(\phpbb\config\config $config) +	{ +		$this->config = $config; +	} + +	public function set_user_loader(\phpbb\user_loader $user_loader) +	{ +		$this->user_loader = $user_loader; +	} +  	/**  	* {@inheritdoc}  	*/ @@ -52,7 +68,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 +76,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;  	} @@ -114,7 +130,7 @@ class admin_activate_user extends \phpbb\notification\type\base  	{  		$username = $this->user_loader->get_username($this->item_id, 'no_profile'); -		return $this->user->lang($this->language_key, $username); +		return $this->language->lang($this->language_key, $username);  	}  	/** @@ -164,6 +180,6 @@ class admin_activate_user extends \phpbb\notification\type\base  		$this->set_data('user_actkey', $user['user_actkey']);  		$this->notification_time = $user['user_regdate']; -		return parent::create_insert_array($user, $pre_create_data); +		parent::create_insert_array($user, $pre_create_data);  	}  } diff --git a/phpBB/phpbb/notification/type/approve_post.php b/phpBB/phpbb/notification/type/approve_post.php index 5760c12166..e4b111e4da 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', @@ -79,7 +79,7 @@ class approve_post extends \phpbb\notification\type\post  		), $options);  		$users = array(); -		$users[$post['poster_id']] = array(''); +		$users[$post['poster_id']] = $this->notification_manager->get_default_methods();  		return $this->get_authorised_recipients(array_keys($users), $post['forum_id'], array_merge($options, array(  			'item_type'		=> static::$notification_option['id'], @@ -107,21 +107,24 @@ class approve_post extends \phpbb\notification\type\post  	}  	/** -	* Function for preparing the data for insertion in an SQL query -	* (The service handles insertion) -	* -	* @param array $post Data from submit_post -	* @param array $pre_create_data Data from pre_create_insert_array() -	* -	* @return array Array of data ready to be inserted into the database +	* {@inheritdoc}  	*/  	public function create_insert_array($post, $pre_create_data = array())  	{  		$this->set_data('post_subject', $post['post_subject']); -		$data = parent::create_insert_array($post, $pre_create_data); +		parent::create_insert_array($post, $pre_create_data); + +		$this->notification_time = time(); +	} -		$this->notification_time = $data['notification_time'] = time(); +	/** +	* {@inheritdoc} +	*/ +	public function get_insert_array() +	{ +		$data = parent::get_insert_array(); +		$data['notification_time'] = $this->notification_time;  		return $data;  	} diff --git a/phpBB/phpbb/notification/type/approve_topic.php b/phpBB/phpbb/notification/type/approve_topic.php index 26e51bf9cd..f8a3fdec6f 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', @@ -79,7 +79,7 @@ class approve_topic extends \phpbb\notification\type\topic  		), $options);  		$users = array(); -		$users[$post['poster_id']] = array(''); +		$users[$post['poster_id']] = $this->notification_manager->get_default_methods();  		return $this->get_authorised_recipients(array_keys($users), $post['forum_id'], array_merge($options, array(  			'item_type'		=> static::$notification_option['id'], @@ -107,19 +107,23 @@ class approve_topic extends \phpbb\notification\type\topic  	}  	/** -	* Function for preparing the data for insertion in an SQL query -	* (The service handles insertion) -	* -	* @param array $post Data from submit_post -	* @param array $pre_create_data Data from pre_create_insert_array() -	* -	* @return array Array of data ready to be inserted into the database +	* {@inheritdoc}  	*/  	public function create_insert_array($post, $pre_create_data = array())  	{ -		$data = parent::create_insert_array($post, $pre_create_data); -		$this->notification_time = $data['notification_time'] = time(); +		parent::create_insert_array($post, $pre_create_data); + +		$this->notification_time = time(); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function get_insert_array() +	{ +		$data = parent::get_insert_array(); +		$data['notification_time'] = $this->notification_time;  		return $data;  	} diff --git a/phpBB/phpbb/notification/type/base.php b/phpBB/phpbb/notification/type/base.php index 4ead06071e..77ed7f2b09 100644 --- a/phpBB/phpbb/notification/type/base.php +++ b/phpBB/phpbb/notification/type/base.php @@ -21,17 +21,11 @@ abstract class base implements \phpbb\notification\type\type_interface  	/** @var \phpbb\notification\manager */  	protected $notification_manager; -	/** @var \phpbb\user_loader */ -	protected $user_loader; -  	/** @var \phpbb\db\driver\driver_interface */  	protected $db; -	/** @var \phpbb\cache\driver\driver_interface */ -	protected $cache; - -	/** @var \phpbb\template\template */ -	protected $template; +	/** @var \phpbb\language\language */ +	protected $language;  	/** @var \phpbb\user */  	protected $user; @@ -39,9 +33,6 @@ abstract class base implements \phpbb\notification\type\type_interface  	/** @var \phpbb\auth\auth */  	protected $auth; -	/** @var \phpbb\config\config */ -	protected $config; -  	/** @var string */  	protected $phpbb_root_path; @@ -49,12 +40,6 @@ abstract class base implements \phpbb\notification\type\type_interface  	protected $php_ext;  	/** @var string */ -	protected $notification_types_table; - -	/** @var string */ -	protected $notifications_table; - -	/** @var string */  	protected $user_notifications_table;  	/** @@ -63,7 +48,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 @@ -74,7 +59,7 @@ abstract class base implements \phpbb\notification\type\type_interface  	protected $notification_type_id;  	/** -	* Indentification data +	* Identification data  	* notification_type_id	- ID of the item type (auto generated, from notification types table)  	* item_id				- ID of the item (e.g. post_id, msg_id)  	* item_parent_id		- Parent item id (ex: for topic => forum_id, for post => topic_id, etc) @@ -89,35 +74,26 @@ abstract class base implements \phpbb\notification\type\type_interface  	private $data = array();  	/** -	* Notification Type Base Constructor -	* -	* @param \phpbb\user_loader $user_loader -	* @param \phpbb\db\driver\driver_interface $db -	* @param \phpbb\cache\driver\driver_interface $cache -	* @param \phpbb\user $user -	* @param \phpbb\auth\auth $auth -	* @param \phpbb\config\config $config -	* @param string $phpbb_root_path -	* @param string $php_ext -	* @param string $notification_types_table -	* @param string $notifications_table -	* @param string $user_notifications_table -	* @return \phpbb\notification\type\base -	*/ -	public function __construct(\phpbb\user_loader $user_loader, \phpbb\db\driver\driver_interface $db, \phpbb\cache\driver\driver_interface $cache, $user, \phpbb\auth\auth $auth, \phpbb\config\config $config, $phpbb_root_path, $php_ext, $notification_types_table, $notifications_table, $user_notifications_table) +	 * Notification Type Base Constructor +	 * +	 * @param \phpbb\db\driver\driver_interface $db +	 * @param \phpbb\language\language          $language +	 * @param \phpbb\user                       $user +	 * @param \phpbb\auth\auth                  $auth +	 * @param string                            $phpbb_root_path +	 * @param string                            $php_ext +	 * @param string                            $user_notifications_table +	 */ +	public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\language\language $language, \phpbb\user $user, \phpbb\auth\auth $auth, $phpbb_root_path, $php_ext, $user_notifications_table)  	{ -		$this->user_loader = $user_loader;  		$this->db = $db; -		$this->cache = $cache; +		$this->language = $language;  		$this->user = $user;  		$this->auth = $auth; -		$this->config = $config;  		$this->phpbb_root_path = $phpbb_root_path;  		$this->php_ext = $php_ext; -		$this->notification_types_table = $notification_types_table; -		$this->notifications_table = $notifications_table;  		$this->user_notifications_table = $user_notifications_table;  	} @@ -207,12 +183,7 @@ abstract class base implements \phpbb\notification\type\type_interface  	}  	/** -	* Function for preparing the data for insertion in an SQL query -	* (The service handles insertion) -	* -	* @param array $type_data Data unique to this notification type -	* @param array $pre_create_data Data from pre_create_insert_array() -	* @return array Array of data ready to be inserted into the database +	* {@inheritdoc}  	*/  	public function create_insert_array($type_data, $pre_create_data = array())  	{ @@ -225,9 +196,15 @@ abstract class base implements \phpbb\notification\type\type_interface  			'notification_time'		=> time(),  			'notification_read'		=> false, -			'notification_data'					=> array(), +			'notification_data'		=> array(),  		), $this->data); +	} +	/** +	* {@inheritdoc} +	*/ +	public function get_insert_array() +	{  		$data = $this->data;  		$data['notification_data'] = serialize($data['notification_data']); @@ -244,7 +221,8 @@ abstract class base implements \phpbb\notification\type\type_interface  	*/  	public function create_update_array($type_data)  	{ -		$data = $this->create_insert_array($type_data); +		$this->create_insert_array($type_data); +		$data = $this->get_insert_array();  		// Unset data unique to each row  		unset( @@ -330,6 +308,7 @@ abstract class base implements \phpbb\notification\type\type_interface  	* URL to unsubscribe to this notification (fall back)  	*  	* @param string|bool $method Method name to unsubscribe from (email|jabber|etc), False to unsubscribe from all notifications for this item +	* @return false  	*/  	public function get_unsubscribe_url($method = false)  	{ @@ -397,8 +376,11 @@ abstract class base implements \phpbb\notification\type\type_interface  	}  	/** -	* Load the special items (fall back) -	*/ +	 * Load the special items (fall back) +	 * +	 * @param array $data +	 * @param array $notifications +	 */  	public function load_special($data, $notifications)  	{  		return; @@ -415,10 +397,12 @@ abstract class base implements \phpbb\notification\type\type_interface  	}  	/** -	* Pre create insert array function (fall back) -	* -	* @return array -	*/ +	 * Pre create insert array function (fall back) +	 * +	 * @param array $type_data +	 * @param array $notify_users +	 * @return array +	 */  	public function pre_create_insert_array($type_data, $notify_users)  	{  		return array(); @@ -429,13 +413,13 @@ abstract class base implements \phpbb\notification\type\type_interface  	*/  	/** -	* Find the users who want to receive notifications (helper) -	* -	* @param array $user_ids User IDs to check if they want to receive notifications -	* 		(Bool False to check all users besides anonymous and bots (USER_IGNORE)) -	* -	* @return array -	*/ +	 * Find the users who want to receive notifications (helper) +	 * +	 * @param array|bool $user_ids User IDs to check if they want to receive notifications +	 *                             (Bool False to check all users besides anonymous and bots (USER_IGNORE)) +	 * @param array      $options +	 * @return array +	 */  	protected function check_user_notification_options($user_ids = false, $options = array())  	{  		$options = array_merge(array( @@ -465,7 +449,7 @@ abstract class base implements \phpbb\notification\type\type_interface  			return array();  		} -		$rowset = $resulting_user_ids = array(); +		$rowset = $output = array();  		$sql = 'SELECT user_id, method, notify  			FROM ' . $this->user_notifications_table . ' @@ -476,9 +460,7 @@ abstract class base implements \phpbb\notification\type\type_interface  		while ($row = $this->db->sql_fetchrow($result))  		{ -			$resulting_user_ids[] = $row['user_id']; - -			if (!$row['notify'] || (isset($options['ignore_users'][$row['user_id']]) && in_array($row['method'], $options['ignore_users'][$row['user_id']]))) +			if (isset($options['ignore_users'][$row['user_id']]) && in_array($row['method'], $options['ignore_users'][$row['user_id']]))  			{  				continue;  			} @@ -487,22 +469,47 @@ abstract class base implements \phpbb\notification\type\type_interface  			{  				$rowset[$row['user_id']] = array();  			} +			$rowset[$row['user_id']][$row['method']] = $row['notify']; -			$rowset[$row['user_id']][] = $row['method']; +			if (!isset($output[$row['user_id']])) +			{ +				$output[$row['user_id']] = array(); +			} +			if ($row['notify']) +			{ +				$output[$row['user_id']][] = $row['method']; +			}  		}  		$this->db->sql_freeresult($result); +		$default_methods = $this->notification_manager->get_default_methods(); +  		foreach ($user_ids as $user_id)  		{ -			if (!in_array($user_id, $resulting_user_ids) && !isset($options['ignore_users'][$user_id])) +			if (isset($options['ignore_users'][$user_id])) +			{ +				continue; +			} +			if (!array_key_exists($user_id, $rowset)) +			{ +				// No rows at all for this user, use the default methods +				$output[$user_id] = $default_methods; +			} +			else  			{ -				// No rows at all for this user, default to '' -				$rowset[$user_id] = array(''); +				foreach ($default_methods as $default_method) +				{ +					if (!array_key_exists($default_method, $rowset[$user_id])) +					{ +						// No user preference for this type recorded, but it should be enabled by default. +						$output[$user_id][] = $default_method; +					} +				}  			}  		} -		return $rowset; +		return $output;  	}  	/** @@ -516,22 +523,23 @@ abstract class base implements \phpbb\notification\type\type_interface  	{  		$this->notification_read = (bool) !$unread; -		$where = array( -			'notification_type_id = ' . (int) $this->notification_type_id, -			'item_id = ' . (int) $this->item_id, -			'user_id = ' . (int) $this->user_id, -		); -		$where = implode(' AND ', $where); -  		if ($return)  		{ +			$where = array( +				'notification_type_id = ' . (int) $this->notification_type_id, +				'item_id = ' . (int) $this->item_id, +				'user_id = ' . (int) $this->user_id, +			); + +			$where = implode(' AND ', $where);  			return $where;  		} +		else +		{ +			$this->notification_manager->mark_notifications($this->get_type(), (int) $this->item_id, (int) $this->user_id, false, $this->notification_read); +		} -		$sql = 'UPDATE ' . $this->notifications_table . ' -			SET notification_read = ' . (int) $this->notification_read . ' -			WHERE ' . $where; -		$this->db->sql_query($sql); +		return null;  	}  	/** diff --git a/phpBB/phpbb/notification/type/bookmark.php b/phpBB/phpbb/notification/type/bookmark.php index 1626add22c..ebbb961c57 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',  	); @@ -91,31 +91,27 @@ class bookmark extends \phpbb\notification\type\post  		}  		// Try to find the users who already have been notified about replies and have not read the topic since and just update their notifications -		$update_notifications = array(); -		$sql = 'SELECT n.* -			FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt -			WHERE n.notification_type_id = ' . (int) $this->notification_type_id . ' -				AND n.item_parent_id = ' . (int) static::get_item_parent_id($post) . ' -				AND n.notification_read = 0 -				AND nt.notification_type_id = n.notification_type_id -				AND nt.notification_type_enabled = 1'; -		$result = $this->db->sql_query($sql); -		while ($row = $this->db->sql_fetchrow($result)) +		$notified_users = $this->notification_manager->get_notified_users($this->get_type(), array( +			'item_parent_id'	=> static::get_item_parent_id($post), +			'read'				=> 0, +		)); + +		foreach ($notified_users as $user => $notification_data)  		{ -			// Do not create a new notification -			unset($notify_users[$row['user_id']]); +			unset($notify_users[$user]); -			$notification = $this->notification_manager->get_item_type_class($this->get_type(), $row); +			/** @var bookmark $notification */ +			$notification = $this->notification_manager->get_item_type_class($this->get_type(), $notification_data);  			$update_responders = $notification->add_responders($post);  			if (!empty($update_responders))  			{ -				$sql = 'UPDATE ' . $this->notifications_table . ' -					SET ' . $this->db->sql_build_array('UPDATE', $update_responders) . ' -					WHERE notification_id = ' . $row['notification_id']; -				$this->db->sql_query($sql); +				$this->notification_manager->update_notification($notification, $update_responders, array( +					'item_parent_id'	=> self::get_item_parent_id($post), +					'read'				=> 0, +					'user_id'			=> $user, +				));  			}  		} -		$this->db->sql_freeresult($result);  		return $notify_users;  	} diff --git a/phpBB/phpbb/notification/type/disapprove_post.php b/phpBB/phpbb/notification/type/disapprove_post.php index 6c7bcbcaee..2d908eb254 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', @@ -73,7 +73,7 @@ class disapprove_post extends \phpbb\notification\type\approve_post  	*/  	public function get_title()  	{ -		return $this->user->lang($this->language_key); +		return $this->language->lang($this->language_key);  	}  	/** @@ -83,7 +83,7 @@ class disapprove_post extends \phpbb\notification\type\approve_post  	*/  	public function get_reference()  	{ -		return $this->user->lang( +		return $this->language->lang(  			'NOTIFICATION_REFERENCE',  			censor_text($this->get_data('topic_title'))  		); @@ -96,7 +96,7 @@ class disapprove_post extends \phpbb\notification\type\approve_post  	*/  	public function get_reason()  	{ -		return $this->user->lang( +		return $this->language->lang(  			'NOTIFICATION_REASON',  			$this->get_data('disapprove_reason')  		); @@ -125,21 +125,24 @@ class disapprove_post extends \phpbb\notification\type\approve_post  	}  	/** -	* Function for preparing the data for insertion in an SQL query -	* (The service handles insertion) -	* -	* @param array $post Data from submit_post -	* @param array $pre_create_data Data from pre_create_insert_array() -	* -	* @return array Array of data ready to be inserted into the database +	* {@inheritdoc}  	*/  	public function create_insert_array($post, $pre_create_data = array())  	{  		$this->set_data('disapprove_reason', $post['disapprove_reason']); -		$data = parent::create_insert_array($post); +		parent::create_insert_array($post, $pre_create_data); + +		$this->notification_time = time(); +	} -		$this->notification_time = $data['notification_time'] = time(); +	/** +	* {@inheritdoc} +	*/ +	public function get_insert_array() +	{ +		$data = parent::get_insert_array(); +		$data['notification_time'] = $this->notification_time;  		return $data;  	} diff --git a/phpBB/phpbb/notification/type/disapprove_topic.php b/phpBB/phpbb/notification/type/disapprove_topic.php index efa5eb7ecd..c2522fb562 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', @@ -73,7 +73,7 @@ class disapprove_topic extends \phpbb\notification\type\approve_topic  	*/  	public function get_title()  	{ -		return $this->user->lang($this->language_key); +		return $this->language->lang($this->language_key);  	}  	/** @@ -83,7 +83,7 @@ class disapprove_topic extends \phpbb\notification\type\approve_topic  	*/  	public function get_reference()  	{ -		return $this->user->lang( +		return $this->language->lang(  			'NOTIFICATION_REFERENCE',  			censor_text($this->get_data('topic_title'))  		); @@ -96,7 +96,7 @@ class disapprove_topic extends \phpbb\notification\type\approve_topic  	*/  	public function get_reason()  	{ -		return $this->user->lang( +		return $this->language->lang(  			'NOTIFICATION_REASON',  			$this->get_data('disapprove_reason')  		); @@ -125,21 +125,24 @@ class disapprove_topic extends \phpbb\notification\type\approve_topic  	}  	/** -	* Function for preparing the data for insertion in an SQL query -	* (The service handles insertion) -	* -	* @param array $post Data from submit_post -	* @param array $pre_create_data Data from pre_create_insert_array() -	* -	* @return array Array of data ready to be inserted into the database +	* {@inheritdoc}  	*/  	public function create_insert_array($post, $pre_create_data = array())  	{  		$this->set_data('disapprove_reason', $post['disapprove_reason']); -		$data = parent::create_insert_array($post, $pre_create_data); +		parent::create_insert_array($post, $pre_create_data); + +		$this->notification_time = time(); +	} -		$this->notification_time = $data['notification_time'] = time(); +	/** +	* {@inheritdoc} +	*/ +	public function get_insert_array() +	{ +		$data = parent::get_insert_array(); +		$data['notification_time'] = $this->notification_time;  		return $data;  	} diff --git a/phpBB/phpbb/notification/type/group_request.php b/phpBB/phpbb/notification/type/group_request.php index 96bfc86322..28a9e73bf9 100644 --- a/phpBB/phpbb/notification/type/group_request.php +++ b/phpBB/phpbb/notification/type/group_request.php @@ -26,10 +26,18 @@ class group_request extends \phpbb\notification\type\base  	/**  	* {@inheritdoc}  	*/ -	public static $notification_option = array( +	static public $notification_option = array(  		'lang'	=> 'NOTIFICATION_TYPE_GROUP_REQUEST',  	); +	/** @var \phpbb\user_loader */ +	protected $user_loader; + +	public function set_user_loader(\phpbb\user_loader $user_loader) +	{ +		$this->user_loader = $user_loader; +	} +  	/**  	* {@inheritdoc}  	*/ @@ -50,7 +58,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 +66,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']; @@ -106,7 +114,7 @@ class group_request extends \phpbb\notification\type\base  	{  		$username = $this->user_loader->get_username($this->item_id, 'no_profile'); -		return $this->user->lang('NOTIFICATION_GROUP_REQUEST', $username, $this->get_data('group_name')); +		return $this->language->lang('NOTIFICATION_GROUP_REQUEST', $username, $this->get_data('group_name'));  	}  	/** @@ -156,6 +164,6 @@ class group_request extends \phpbb\notification\type\base  	{  		$this->set_data('group_name', $group['group_name']); -		return parent::create_insert_array($group, $pre_create_data); +		parent::create_insert_array($group, $pre_create_data);  	}  } diff --git a/phpBB/phpbb/notification/type/group_request_approved.php b/phpBB/phpbb/notification/type/group_request_approved.php index d284046ffa..f55d28bafd 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;  	} @@ -58,7 +58,7 @@ class group_request_approved extends \phpbb\notification\type\base  		foreach ($group['user_ids'] as $user_id)  		{ -			$users[$user_id] = array(''); +			$users[$user_id] = $this->notification_manager->get_default_methods();  		}  		return $users; @@ -69,7 +69,7 @@ class group_request_approved extends \phpbb\notification\type\base  	*/  	public function get_title()  	{ -		return $this->user->lang('NOTIFICATION_GROUP_REQUEST_APPROVED', $this->get_data('group_name')); +		return $this->language->lang('NOTIFICATION_GROUP_REQUEST_APPROVED', $this->get_data('group_name'));  	}  	/** @@ -87,7 +87,7 @@ class group_request_approved extends \phpbb\notification\type\base  	{  		$this->set_data('group_name', $group['group_name']); -		return parent::create_insert_array($group, $pre_create_data); +		parent::create_insert_array($group, $pre_create_data);  	}  	/** diff --git a/phpBB/phpbb/notification/type/pm.php b/phpBB/phpbb/notification/type/pm.php index d2f34f95d0..8fb9172911 100644 --- a/phpBB/phpbb/notification/type/pm.php +++ b/phpBB/phpbb/notification/type/pm.php @@ -36,10 +36,26 @@ 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',  	); +	/** @var \phpbb\user_loader */ +	protected $user_loader; + +	/** @var \phpbb\config\config */ +	protected $config; + +	public function set_config(\phpbb\config\config $config) +	{ +		$this->config = $config; +	} + +	public function set_user_loader(\phpbb\user_loader $user_loader) +	{ +		$this->user_loader = $user_loader; +	} +  	/**  	* Is available  	*/ @@ -53,7 +69,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 +79,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; @@ -112,7 +128,7 @@ class pm extends \phpbb\notification\type\base  	{  		$username = $this->user_loader->get_username($this->get_data('from_user_id'), 'no_profile'); -		return $this->user->lang('NOTIFICATION_PM', $username); +		return $this->language->lang('NOTIFICATION_PM', $username);  	}  	/** @@ -122,7 +138,7 @@ class pm extends \phpbb\notification\type\base  	*/  	public function get_reference()  	{ -		return $this->user->lang( +		return $this->language->lang(  			'NOTIFICATION_REFERENCE',  			$this->get_data('message_subject')  		); @@ -176,13 +192,7 @@ class pm extends \phpbb\notification\type\base  	}  	/** -	* Function for preparing the data for insertion in an SQL query -	* (The service handles insertion) -	* -	* @param array $pm Data from submit_post -	* @param array $pre_create_data Data from pre_create_insert_array() -	* -	* @return array Array of data ready to be inserted into the database +	* {@inheritdoc}  	*/  	public function create_insert_array($pm, $pre_create_data = array())  	{ @@ -190,6 +200,6 @@ class pm extends \phpbb\notification\type\base  		$this->set_data('message_subject', $pm['message_subject']); -		return parent::create_insert_array($pm, $pre_create_data); +		parent::create_insert_array($pm, $pre_create_data);  	}  } diff --git a/phpBB/phpbb/notification/type/post.php b/phpBB/phpbb/notification/type/post.php index 2969da550d..b9afc6d70a 100644 --- a/phpBB/phpbb/notification/type/post.php +++ b/phpBB/phpbb/notification/type/post.php @@ -50,11 +50,27 @@ 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',  	); +	/** @var \phpbb\user_loader */ +	protected $user_loader; + +	/** @var \phpbb\config\config */ +	protected $config; + +	public function set_config(\phpbb\config\config $config) +	{ +		$this->config = $config; +	} + +	public function set_user_loader(\phpbb\user_loader $user_loader) +	{ +		$this->user_loader = $user_loader; +	} +  	/**  	* Is available  	*/ @@ -67,8 +83,9 @@ class post extends \phpbb\notification\type\base  	* Get the id of the item  	*  	* @param array $post The data from the post +	* @return int The post id  	*/ -	public static function get_item_id($post) +	static public function get_item_id($post)  	{  		return (int) $post['post_id'];  	} @@ -77,8 +94,9 @@ class post extends \phpbb\notification\type\base  	* Get the id of the parent  	*  	* @param array $post The data from the post +	* @return int The topic id  	*/ -	public static function get_item_parent_id($post) +	static public function get_item_parent_id($post)  	{  		return (int) $post['topic_id'];  	} @@ -131,31 +149,27 @@ class post extends \phpbb\notification\type\base  		}  		// Try to find the users who already have been notified about replies and have not read the topic since and just update their notifications -		$update_notifications = array(); -		$sql = 'SELECT n.* -			FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt -			WHERE n.notification_type_id = ' . (int) $this->notification_type_id . ' -				AND n.item_parent_id = ' . (int) static::get_item_parent_id($post) . ' -				AND n.notification_read = 0 -				AND nt.notification_type_id = n.notification_type_id -				AND nt.notification_type_enabled = 1'; -		$result = $this->db->sql_query($sql); -		while ($row = $this->db->sql_fetchrow($result)) +		$notified_users = $this->notification_manager->get_notified_users($this->get_type(), array( +			'item_parent_id'	=> static::get_item_parent_id($post), +			'read'				=> 0, +		)); + +		foreach ($notified_users as $user => $notification_data)  		{ -			// Do not create a new notification -			unset($notify_users[$row['user_id']]); +			unset($notify_users[$user]); -			$notification = $this->notification_manager->get_item_type_class($this->get_type(), $row); +			/** @var post $notification */ +			$notification = $this->notification_manager->get_item_type_class($this->get_type(), $notification_data);  			$update_responders = $notification->add_responders($post);  			if (!empty($update_responders))  			{ -				$sql = 'UPDATE ' . $this->notifications_table . ' -					SET ' . $this->db->sql_build_array('UPDATE', $update_responders) . ' -					WHERE notification_id = ' . $row['notification_id']; -				$this->db->sql_query($sql); +				$this->notification_manager->update_notification($notification, $update_responders, array( +					'item_parent_id'	=> self::get_item_parent_id($post), +					'read'				=> 0, +					'user_id'			=> $user, +				));  			}  		} -		$this->db->sql_freeresult($result);  		return $notify_users;  	} @@ -206,14 +220,14 @@ class post extends \phpbb\notification\type\base  		if ($trimmed_responders_cnt > 20)  		{ -			$usernames[] = $this->user->lang('NOTIFICATION_MANY_OTHERS'); +			$usernames[] = $this->language->lang('NOTIFICATION_MANY_OTHERS');  		}  		else if ($trimmed_responders_cnt)  		{ -			$usernames[] = $this->user->lang('NOTIFICATION_X_OTHERS', $trimmed_responders_cnt); +			$usernames[] = $this->language->lang('NOTIFICATION_X_OTHERS', $trimmed_responders_cnt);  		} -		return $this->user->lang( +		return $this->language->lang(  			$this->language_key,  			phpbb_generate_string_list($usernames, $this->user),  			$responders_cnt @@ -227,7 +241,7 @@ class post extends \phpbb\notification\type\base  	*/  	public function get_reference()  	{ -		return $this->user->lang( +		return $this->language->lang(  			'NOTIFICATION_REFERENCE',  			censor_text($this->get_data('topic_title'))  		); @@ -363,13 +377,7 @@ class post extends \phpbb\notification\type\base  	}  	/** -	* Function for preparing the data for insertion in an SQL query -	* (The service handles insertion) -	* -	* @param array $post Data from submit_post -	* @param array $pre_create_data Data from pre_create_insert_array() -	* -	* @return array Array of data ready to be inserted into the database +	* {@inheritdoc}  	*/  	public function create_insert_array($post, $pre_create_data = array())  	{ @@ -394,13 +402,14 @@ class post extends \phpbb\notification\type\base  			$this->notification_read = true;  		} -		return parent::create_insert_array($post, $pre_create_data); +		parent::create_insert_array($post, $pre_create_data);  	}  	/**  	* Add responders to the notification  	*  	* @param mixed $post +	* @return array Array of responder data  	*/  	public function add_responders($post)  	{ diff --git a/phpBB/phpbb/notification/type/post_in_queue.php b/phpBB/phpbb/notification/type/post_in_queue.php index 5832c99cd2..2d556fc9c8 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', @@ -131,19 +131,22 @@ class post_in_queue extends \phpbb\notification\type\post  	}  	/** -	* Function for preparing the data for insertion in an SQL query -	* (The service handles insertion) -	* -	* @param array $post Data from submit_post -	* @param array $pre_create_data Data from pre_create_insert_array() -	* -	* @return array Array of data ready to be inserted into the database +	* {@inheritdoc}  	*/  	public function create_insert_array($post, $pre_create_data = array())  	{ -		$data = parent::create_insert_array($post, $pre_create_data); +		parent::create_insert_array($post, $pre_create_data); + +		$this->notification_time = time(); +	} -		$this->notification_time = $data['notification_time'] = time(); +	/** +	* {@inheritdoc} +	*/ +	public function get_insert_array() +	{ +		$data = parent::get_insert_array(); +		$data['notification_time'] = $this->notification_time;  		return $data;  	} diff --git a/phpBB/phpbb/notification/type/quote.php b/phpBB/phpbb/notification/type/quote.php index 2732cb84e4..323c18b204 100644 --- a/phpBB/phpbb/notification/type/quote.php +++ b/phpBB/phpbb/notification/type/quote.php @@ -21,6 +21,11 @@ namespace phpbb\notification\type;  class quote extends \phpbb\notification\type\post  {  	/** +	* @var \phpbb\textformatter\utils_interface +	*/ +	protected $utils; + +	/**  	* Get notification type name  	*  	* @return string @@ -31,13 +36,6 @@ class quote extends \phpbb\notification\type\post  	}  	/** -	* regular expression to match to find usernames -	* -	* @var string -	*/ -	protected static $regular_expression_match = '#\[quote="(.+?)"#'; - -	/**  	* Language key used to output the text  	*  	* @var string @@ -50,7 +48,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',  	); @@ -77,17 +75,16 @@ class quote extends \phpbb\notification\type\post  			'ignore_users'		=> array(),  		), $options); -		$usernames = false; -		preg_match_all(static::$regular_expression_match, $post['post_text'], $usernames); +		$usernames = $this->utils->get_outermost_quote_authors($post['post_text']); -		if (empty($usernames[1])) +		if (empty($usernames))  		{  			return array();  		} -		$usernames[1] = array_unique($usernames[1]); +		$usernames = array_unique($usernames); -		$usernames = array_map('utf8_clean_string', $usernames[1]); +		$usernames = array_map('utf8_clean_string', $usernames);  		$users = array(); @@ -109,32 +106,23 @@ class quote extends \phpbb\notification\type\post  	* Update a notification  	*  	* @param array $post Data specific for this type that will be updated +	* @return true  	*/  	public function update_notifications($post)  	{ -		$old_notifications = array(); -		$sql = 'SELECT n.user_id -			FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt -			WHERE n.notification_type_id = ' . (int) $this->notification_type_id . ' -				AND n.item_id = ' . static::get_item_id($post) . ' -				AND nt.notification_type_id = n.notification_type_id -				AND nt.notification_type_enabled = 1'; -		$result = $this->db->sql_query($sql); -		while ($row = $this->db->sql_fetchrow($result)) -		{ -			$old_notifications[] = $row['user_id']; -		} -		$this->db->sql_freeresult($result); +		$old_notifications = $this->notification_manager->get_notified_users($this->get_type(), array( +			'item_id'	=> static::get_item_id($post), +		));  		// Find the new users to notify  		$notifications = $this->find_users_for_notification($post);  		// Find the notifications we must delete -		$remove_notifications = array_diff($old_notifications, array_keys($notifications)); +		$remove_notifications = array_diff(array_keys($old_notifications), array_keys($notifications));  		// Find the notifications we must add  		$add_notifications = array(); -		foreach (array_diff(array_keys($notifications), $old_notifications) as $user_id) +		foreach (array_diff(array_keys($notifications), array_keys($old_notifications)) as $user_id)  		{  			$add_notifications[$user_id] = $notifications[$user_id];  		} @@ -145,11 +133,7 @@ class quote extends \phpbb\notification\type\post  		// Remove the necessary notifications  		if (!empty($remove_notifications))  		{ -			$sql = 'DELETE FROM ' . $this->notifications_table . ' -				WHERE notification_type_id = ' . (int) $this->notification_type_id . ' -					AND item_id = ' . static::get_item_id($post) . ' -					AND ' . $this->db->sql_in_set('user_id', $remove_notifications); -			$this->db->sql_query($sql); +			$this->notification_manager->delete_notifications($this->get_type(), static::get_item_id($post), false, $remove_notifications);  		}  		// return true to continue with the update code in the notifications service (this will update the rest of the notifications) @@ -187,4 +171,14 @@ class quote extends \phpbb\notification\type\post  			'AUTHOR_NAME'		=> htmlspecialchars_decode($user_data['username']),  		));  	} + +	/** +	* Set the utils service used to retrieve quote authors +	* +	* @param \phpbb\textformatter\utils_interface $utils +	*/ +	public function set_utils(\phpbb\textformatter\utils_interface $utils) +	{ +		$this->utils = $utils; +	}  } diff --git a/phpBB/phpbb/notification/type/report_pm.php b/phpBB/phpbb/notification/type/report_pm.php index 785e5f243d..6091919769 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', @@ -70,8 +70,9 @@ class report_pm extends \phpbb\notification\type\pm  	* Get the id of the parent  	*  	* @param array $pm The data from the pm +	* @return int The report id  	*/ -	public static function get_item_parent_id($pm) +	static public function get_item_parent_id($pm)  	{  		return (int) $pm['report_id'];  	} @@ -141,11 +142,13 @@ class report_pm extends \phpbb\notification\type\pm  	*/  	public function get_email_template_variables()  	{ +		$user_data = $this->user_loader->get_username($this->get_data('reporter_id'), 'no_profile'); +  		return array( -			'AUTHOR_NAME'				=> htmlspecialchars_decode($user_data['username']), -			'SUBJECT'					=> htmlspecialchars_decode(censor_text($this->get_data('message_subject'))), +			'AUTHOR_NAME'	=> htmlspecialchars_decode($user_data['username']), +			'SUBJECT'		=> htmlspecialchars_decode(censor_text($this->get_data('message_subject'))), -			'U_VIEW_REPORT'				=> generate_board_url() . "mcp.{$this->php_ext}?r={$this->item_parent_id}&i=pm_reports&mode=pm_report_details", +			'U_VIEW_REPORT'	=> generate_board_url() . "mcp.{$this->php_ext}?r={$this->item_parent_id}&i=pm_reports&mode=pm_report_details",  		);  	} @@ -166,11 +169,11 @@ class report_pm extends \phpbb\notification\type\pm  	*/  	public function get_title()  	{ -		$this->user->add_lang('mcp'); +		$this->language->add_lang('mcp');  		$username = $this->user_loader->get_username($this->get_data('reporter_id'), 'no_profile'); -		return $this->user->lang( +		return $this->language->lang(  			$this->language_key,  			$username  		); @@ -183,7 +186,7 @@ class report_pm extends \phpbb\notification\type\pm  	*/  	public function get_reference()  	{ -		return $this->user->lang( +		return $this->language->lang(  			'NOTIFICATION_REFERENCE',  			censor_text($this->get_data('message_subject'))  		); @@ -198,21 +201,21 @@ class report_pm extends \phpbb\notification\type\pm  	{  		if ($this->get_data('report_text'))  		{ -			return $this->user->lang( +			return $this->language->lang(  				'NOTIFICATION_REASON',  				$this->get_data('report_text')  			);  		} -		if (isset($this->user->lang[$this->get_data('reason_title')])) +		if ($this->language->is_set($this->get_data('reason_title')))  		{ -			return $this->user->lang( +			return $this->language->lang(  				'NOTIFICATION_REASON', -				$this->user->lang[$this->get_data('reason_title')] +				$this->language->lang($this->get_data('reason_title'))  			);  		} -		return $this->user->lang( +		return $this->language->lang(  			'NOTIFICATION_REASON',  			$this->get_data('reason_description')  		); @@ -237,13 +240,7 @@ class report_pm extends \phpbb\notification\type\pm  	}  	/** -	* Function for preparing the data for insertion in an SQL query -	* (The service handles insertion) -	* -	* @param array $post Data from submit_post -	* @param array $pre_create_data Data from pre_create_insert_array() -	* -	* @return array Array of data ready to be inserted into the database +	* {@inheritdoc}  	*/  	public function create_insert_array($post, $pre_create_data = array())  	{ @@ -252,6 +249,6 @@ class report_pm extends \phpbb\notification\type\pm  		$this->set_data('reason_description', $post['reason_description']);  		$this->set_data('report_text', $post['report_text']); -		return parent::create_insert_array($post, $pre_create_data); +		parent::create_insert_array($post, $pre_create_data);  	}  } diff --git a/phpBB/phpbb/notification/type/report_pm_closed.php b/phpBB/phpbb/notification/type/report_pm_closed.php index 1c99db60c3..5e98eb5feb 100644 --- a/phpBB/phpbb/notification/type/report_pm_closed.php +++ b/phpBB/phpbb/notification/type/report_pm_closed.php @@ -64,7 +64,7 @@ class report_pm_closed extends \phpbb\notification\type\pm  			return array();  		} -		return array($pm['reporter'] => array('')); +		return array($pm['reporter'] => $this->notification_manager->get_default_methods());  	}  	/** @@ -106,7 +106,7 @@ class report_pm_closed extends \phpbb\notification\type\pm  	{  		$username = $this->user_loader->get_username($this->get_data('closer_id'), 'no_profile'); -		return $this->user->lang( +		return $this->language->lang(  			$this->language_key,  			$username  		); @@ -119,7 +119,7 @@ class report_pm_closed extends \phpbb\notification\type\pm  	*/  	public function get_reference()  	{ -		return $this->user->lang( +		return $this->language->lang(  			'NOTIFICATION_REFERENCE',  			censor_text($this->get_data('message_subject'))  		); @@ -144,21 +144,24 @@ class report_pm_closed extends \phpbb\notification\type\pm  	}  	/** -	* Function for preparing the data for insertion in an SQL query -	* (The service handles insertion) -	* -	* @param array $pm PM Data -	* @param array $pre_create_data Data from pre_create_insert_array() -	* -	* @return array Array of data ready to be inserted into the database +	* {@inheritdoc}  	*/  	public function create_insert_array($pm, $pre_create_data = array())  	{  		$this->set_data('closer_id', $pm['closer_id']); -		$data = parent::create_insert_array($pm, $pre_create_data); +		parent::create_insert_array($pm, $pre_create_data); + +		$this->notification_time = time(); +	} -		$this->notification_time = $data['notification_time'] = time(); +	/** +	* {@inheritdoc} +	*/ +	public function get_insert_array() +	{ +		$data = parent::get_insert_array(); +		$data['notification_time'] = $this->notification_time;  		return $data;  	} diff --git a/phpBB/phpbb/notification/type/report_post.php b/phpBB/phpbb/notification/type/report_post.php index aed31e8642..84a5241417 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', @@ -139,11 +139,11 @@ class report_post extends \phpbb\notification\type\post_in_queue  	*/  	public function get_title()  	{ -		$this->user->add_lang('mcp'); +		$this->language->add_lang('mcp');  		$username = $this->user_loader->get_username($this->get_data('reporter_id'), 'no_profile'); -		return $this->user->lang( +		return $this->language->lang(  			$this->language_key,  			$username  		); @@ -156,7 +156,7 @@ class report_post extends \phpbb\notification\type\post_in_queue  	*/  	public function get_reference()  	{ -		return $this->user->lang( +		return $this->language->lang(  			'NOTIFICATION_REFERENCE',  			censor_text($this->get_data('post_subject'))  		); @@ -171,21 +171,21 @@ class report_post extends \phpbb\notification\type\post_in_queue  	{  		if ($this->get_data('report_text'))  		{ -			return $this->user->lang( +			return $this->language->lang(  				'NOTIFICATION_REASON',  				$this->get_data('report_text')  			);  		} -		if (isset($this->user->lang[$this->get_data('reason_title')])) +		if ($this->language->is_set($this->get_data('reason_title')))  		{ -			return $this->user->lang( +			return $this->language->lang(  				'NOTIFICATION_REASON', -				$this->user->lang[$this->get_data('reason_title')] +				$this->language->lang($this->get_data('reason_title'))  			);  		} -		return $this->user->lang( +		return $this->language->lang(  			'NOTIFICATION_REASON',  			$this->get_data('reason_description')  		); @@ -210,13 +210,7 @@ class report_post extends \phpbb\notification\type\post_in_queue  	}  	/** -	* Function for preparing the data for insertion in an SQL query -	* (The service handles insertion) -	* -	* @param array $post Data from submit_post -	* @param array $pre_create_data Data from pre_create_insert_array() -	* -	* @return array Array of data ready to be inserted into the database +	* {@inheritdoc}  	*/  	public function create_insert_array($post, $pre_create_data = array())  	{ @@ -225,6 +219,6 @@ class report_post extends \phpbb\notification\type\post_in_queue  		$this->set_data('reason_description', $post['reason_description']);  		$this->set_data('report_text', $post['report_text']); -		return parent::create_insert_array($post, $pre_create_data); +		parent::create_insert_array($post, $pre_create_data);  	}  } diff --git a/phpBB/phpbb/notification/type/report_post_closed.php b/phpBB/phpbb/notification/type/report_post_closed.php index 3f4378628b..165034d57e 100644 --- a/phpBB/phpbb/notification/type/report_post_closed.php +++ b/phpBB/phpbb/notification/type/report_post_closed.php @@ -71,7 +71,7 @@ class report_post_closed extends \phpbb\notification\type\post  			return array();  		} -		return array($post['reporter'] => array('')); +		return array($post['reporter'] => $this->notification_manager->get_default_methods());  	}  	/** @@ -113,7 +113,7 @@ class report_post_closed extends \phpbb\notification\type\post  	{  		$username = $this->user_loader->get_username($this->get_data('closer_id'), 'no_profile'); -		return $this->user->lang( +		return $this->language->lang(  			$this->language_key,  			$username  		); @@ -126,7 +126,7 @@ class report_post_closed extends \phpbb\notification\type\post  	*/  	public function get_reference()  	{ -		return $this->user->lang( +		return $this->language->lang(  			'NOTIFICATION_REFERENCE',  			censor_text($this->get_data('post_subject'))  		); @@ -151,21 +151,24 @@ class report_post_closed extends \phpbb\notification\type\post  	}  	/** -	* Function for preparing the data for insertion in an SQL query -	* (The service handles insertion) -	* -	* @param array $post Data from submit_post -	* @param array $pre_create_data Data from pre_create_insert_array() -	* -	* @return array Array of data ready to be inserted into the database +	* {@inheritdoc}  	*/  	public function create_insert_array($post, $pre_create_data = array())  	{  		$this->set_data('closer_id', $post['closer_id']); -		$data = parent::create_insert_array($post, $pre_create_data); +		parent::create_insert_array($post, $pre_create_data); + +		$this->notification_time = time(); +	} -		$this->notification_time = $data['notification_time'] = time(); +	/** +	* {@inheritdoc} +	*/ +	public function get_insert_array() +	{ +		$data = parent::get_insert_array(); +		$data['notification_time'] = $this->notification_time;  		return $data;  	} diff --git a/phpBB/phpbb/notification/type/topic.php b/phpBB/phpbb/notification/type/topic.php index fb08a9eee1..671c34fe96 100644 --- a/phpBB/phpbb/notification/type/topic.php +++ b/phpBB/phpbb/notification/type/topic.php @@ -50,11 +50,27 @@ 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',  	); +	/** @var \phpbb\user_loader */ +	protected $user_loader; + +	/** @var \phpbb\config\config */ +	protected $config; + +	public function set_config(\phpbb\config\config $config) +	{ +		$this->config = $config; +	} + +	public function set_user_loader(\phpbb\user_loader $user_loader) +	{ +		$this->user_loader = $user_loader; +	} +  	/**  	* Is available  	*/ @@ -67,8 +83,9 @@ class topic extends \phpbb\notification\type\base  	* Get the id of the item  	*  	* @param array $post The data from the post +	* @return int The topic id  	*/ -	public static function get_item_id($post) +	static public function get_item_id($post)  	{  		return (int) $post['topic_id'];  	} @@ -77,8 +94,9 @@ class topic extends \phpbb\notification\type\base  	* Get the id of the parent  	*  	* @param array $post The data from the post +	* @return int The forum id  	*/ -	public static function get_item_parent_id($post) +	static public function get_item_parent_id($post)  	{  		return (int) $post['forum_id'];  	} @@ -138,7 +156,7 @@ class topic extends \phpbb\notification\type\base  			$username = $this->user_loader->get_username($this->get_data('poster_id'), 'no_profile');  		} -		return $this->user->lang( +		return $this->language->lang(  			$this->language_key,  			$username  		); @@ -151,7 +169,7 @@ class topic extends \phpbb\notification\type\base  	*/  	public function get_reference()  	{ -		return $this->user->lang( +		return $this->language->lang(  			'NOTIFICATION_REFERENCE',  			censor_text($this->get_data('topic_title'))  		); @@ -164,7 +182,7 @@ class topic extends \phpbb\notification\type\base  	*/  	public function get_forum()  	{ -		return $this->user->lang( +		return $this->language->lang(  			'NOTIFICATION_FORUM',  			$this->get_data('forum_name')  		); @@ -263,13 +281,7 @@ class topic extends \phpbb\notification\type\base  	}  	/** -	* Function for preparing the data for insertion in an SQL query -	* (The service handles insertion) -	* -	* @param array $post Data from submit_post -	* @param array $pre_create_data Data from pre_create_insert_array() -	* -	* @return array Array of data ready to be inserted into the database +	* {@inheritdoc}  	*/  	public function create_insert_array($post, $pre_create_data = array())  	{ @@ -290,6 +302,6 @@ class topic extends \phpbb\notification\type\base  			$this->notification_read = true;  		} -		return parent::create_insert_array($post, $pre_create_data); +		parent::create_insert_array($post, $pre_create_data);  	}  } diff --git a/phpBB/phpbb/notification/type/topic_in_queue.php b/phpBB/phpbb/notification/type/topic_in_queue.php index 6e57b9ac0c..2d732b9cd7 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', @@ -123,19 +123,22 @@ class topic_in_queue extends \phpbb\notification\type\topic  	}  	/** -	* Function for preparing the data for insertion in an SQL query -	* (The service handles insertion) -	* -	* @param array $topic Data from submit_post -	* @param array $pre_create_data Data from pre_create_insert_array() -	* -	* @return array Array of data ready to be inserted into the database +	* {@inheritdoc}  	*/  	public function create_insert_array($topic, $pre_create_data = array())  	{ -		$data = parent::create_insert_array($topic, $pre_create_data); +		parent::create_insert_array($topic, $pre_create_data); + +		$this->notification_time = time(); +	} -		$this->notification_time = $data['notification_time'] = time(); +	/** +	* {@inheritdoc} +	*/ +	public function get_insert_array() +	{ +		$data = parent::get_insert_array(); +		$data['notification_time'] = $this->notification_time;  		return $data;  	} diff --git a/phpBB/phpbb/notification/type/type_interface.php b/phpBB/phpbb/notification/type/type_interface.php index 5c5a110836..f9f832bdda 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) @@ -177,14 +177,18 @@ interface type_interface  	/**  	* Function for preparing the data for insertion in an SQL query -	* (The service handles insertion)  	*  	* @param array $type_data The type specific data  	* @param array $pre_create_data Data from pre_create_insert_array() +	*/ +	public function create_insert_array($type_data, $pre_create_data); + +	/** +	* Function for getting the data for insertion in an SQL query  	*  	* @return array Array of data ready to be inserted into the database  	*/ -	public function create_insert_array($type_data, $pre_create_data); +	public function get_insert_array();  	/**  	* Function for preparing the data for update in an SQL query @@ -202,7 +206,7 @@ interface type_interface  	* @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False)  	* @return string  	*/ -	public function mark_read($return); +	public function mark_read($return = false);  	/**  	* Mark this item unread @@ -210,5 +214,5 @@ interface type_interface  	* @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False)  	* @return string  	*/ -	public function mark_unread($return); +	public function mark_unread($return = false);  } diff --git a/phpBB/phpbb/passwords/manager.php b/phpBB/phpbb/passwords/manager.php index aa9147ecf4..b2caba81f2 100644 --- a/phpBB/phpbb/passwords/manager.php +++ b/phpBB/phpbb/passwords/manager.php @@ -50,21 +50,47 @@ class manager  	protected $config;  	/** +	 * @var bool Whether or not initialized() has been called +	 */ +	private $initialized = false; + +	/** +	 * @var array Hashing driver service collection +	 */ +	private $hashing_algorithms; + +	/** +	 * @var array List of default driver types +	 */ +	private $defaults; + +	/**  	* Construct a passwords object  	* -	* @param \phpbb\config\config $config phpBB configuration -	* @param array $hashing_algorithms Hashing driver -	*			service collection -	* @param \phpbb\passwords\helper $helper Passwords helper object -	* @param array $defaults List of default driver types +	* @param \phpbb\config\config		$config				phpBB configuration +	* @param array						$hashing_algorithms	Hashing driver service collection +	* @param \phpbb\passwords\helper	$helper				Passwords helper object +	* @param array						$defaults			List of default driver types  	*/  	public function __construct(\phpbb\config\config $config, $hashing_algorithms, helper $helper, $defaults)  	{  		$this->config = $config;  		$this->helper = $helper; +		$this->hashing_algorithms = $hashing_algorithms; +		$this->defaults = $defaults; +	} -		$this->fill_type_map($hashing_algorithms); -		$this->register_default_type($defaults); +	/** +	 * Initialize the internal state +	 */ +	protected function initialize() +	{ +		if (!$this->initialized) +		{ +			$this->initialized = true; +			$this->fill_type_map($this->hashing_algorithms); +			$this->register_default_type($this->defaults); +		}  	}  	/** @@ -144,6 +170,8 @@ class manager  			return false;  		} +		$this->initialize(); +  		// Be on the lookout for multiple hashing algorithms  		// 2 is correct: H\2a > 2, H\P > 2  		if (strlen($match[1]) > 2) @@ -192,6 +220,8 @@ class manager  			return false;  		} +		$this->initialize(); +  		// Try to retrieve algorithm by service name if type doesn't  		// start with dollar sign  		if (!is_array($type) && strpos($type, '$') !== 0 && isset($this->algorithms[$type])) @@ -242,6 +272,8 @@ class manager  			return false;  		} +		$this->initialize(); +  		// First find out what kind of hash we're dealing with  		$stored_hash_type = $this->detect_algorithm($hash);  		if ($stored_hash_type == false) @@ -297,6 +329,8 @@ class manager  	*/  	public function combined_hash_password($password_hash, $type)  	{ +		$this->initialize(); +  		$data = array(  			'prefix' => '$',  			'settings' => '$', diff --git a/phpBB/phpbb/path_helper.php b/phpBB/phpbb/path_helper.php index 5400c1c5a6..154361ef64 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; @@ -100,11 +100,18 @@ class path_helper  	*/  	public function update_web_root_path($path)  	{ +		$web_root_path = $this->get_web_root_path(); + +		// Removes the web root path if it is already present +		if (strpos($path, $web_root_path) === 0) +		{ +			$path = $this->phpbb_root_path . substr($path, strlen($web_root_path)); +		} +  		if (strpos($path, $this->phpbb_root_path) === 0)  		{  			$path = substr($path, strlen($this->phpbb_root_path)); -			$web_root_path = $this->get_web_root_path();  			if (substr($web_root_path, -8) === 'app.php/' && substr($path, 0, 7) === 'app.php')  			{  				$path = substr($path, 8); diff --git a/phpBB/phpbb/permissions.php b/phpBB/phpbb/permissions.php index e75476f59b..c9181e6202 100644 --- a/phpBB/phpbb/permissions.php +++ b/phpBB/phpbb/permissions.php @@ -273,6 +273,7 @@ class permissions  		'f_post'		=> array('lang' => 'ACL_F_POST', 'cat' => 'post'),  		'f_sticky'		=> array('lang' => 'ACL_F_STICKY', 'cat' => 'post'),  		'f_announce'	=> array('lang' => 'ACL_F_ANNOUNCE', 'cat' => 'post'), +		'f_announce_global'	=> array('lang' => 'ACL_F_ANNOUNCE_GLOBAL', 'cat' => 'post'),  		'f_reply'		=> array('lang' => 'ACL_F_REPLY', 'cat' => 'post'),  		'f_edit'		=> array('lang' => 'ACL_F_EDIT', 'cat' => 'post'),  		'f_delete'		=> array('lang' => 'ACL_F_DELETE', 'cat' => 'post'), diff --git a/phpBB/phpbb/plupload/plupload.php b/phpBB/phpbb/plupload/plupload.php index 7f6267ed32..a47fc87adf 100644 --- a/phpBB/phpbb/plupload/plupload.php +++ b/phpBB/phpbb/plupload/plupload.php @@ -39,7 +39,7 @@ class plupload  	protected $user;  	/** -	* @var \phpbb\php\ini +	* @var \bantu\IniGetWrapper\IniGetWrapper  	*/  	protected $php_ini; @@ -67,10 +67,10 @@ class plupload  	* @param \phpbb\config\config $config  	* @param \phpbb\request\request_interface $request  	* @param \phpbb\user $user -	* @param \phpbb\php\ini $php_ini +	* @param \bantu\IniGetWrapper\IniGetWrapper $php_ini  	* @param \phpbb\mimetype\guesser $mimetype_guesser  	*/ -	public function __construct($phpbb_root_path, \phpbb\config\config $config, \phpbb\request\request_interface $request, \phpbb\user $user, \phpbb\php\ini $php_ini, \phpbb\mimetype\guesser $mimetype_guesser) +	public function __construct($phpbb_root_path, \phpbb\config\config $config, \phpbb\request\request_interface $request, \phpbb\user $user, \bantu\IniGetWrapper\IniGetWrapper $php_ini, \phpbb\mimetype\guesser $mimetype_guesser)  	{  		$this->phpbb_root_path = $phpbb_root_path;  		$this->config = $config; @@ -284,9 +284,9 @@ class plupload  	public function get_chunk_size()  	{  		$max = min( -			$this->php_ini->get_bytes('upload_max_filesize'), -			$this->php_ini->get_bytes('post_max_size'), -			max(1, $this->php_ini->get_bytes('memory_limit')), +			$this->php_ini->getBytes('upload_max_filesize'), +			$this->php_ini->getBytes('post_max_size'), +			max(1, $this->php_ini->getBytes('memory_limit')),  			$this->config['max_filesize']  		); @@ -303,7 +303,7 @@ class plupload  			$this->temporary_directory,  			$this->config['plupload_salt'],  			md5($file_name), -			\filespec::get_extension($file_name) +			\phpbb\files\filespec::get_extension($file_name)  		);  	} 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..5574a16dc0 --- /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'], +		); + +		$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/request/type_cast_helper.php b/phpBB/phpbb/request/type_cast_helper.php index bc654e6182..96e66950ca 100644 --- a/phpBB/phpbb/request/type_cast_helper.php +++ b/phpBB/phpbb/request/type_cast_helper.php @@ -172,7 +172,6 @@ class type_cast_helper implements \phpbb\request\type_cast_helper_interface  			}  			list($default_key, $default_value) = each($default); -			$value_type = gettype($default_value);  			$key_type = gettype($default_key);  			$_var = $var; diff --git a/phpBB/phpbb/routing/file_locator.php b/phpBB/phpbb/routing/file_locator.php new file mode 100644 index 0000000000..64efcc6c76 --- /dev/null +++ b/phpBB/phpbb/routing/file_locator.php @@ -0,0 +1,33 @@ +<?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 phpbb\filesystem\filesystem_interface; +use Symfony\Component\Config\FileLocator; + +class file_locator extends FileLocator +{ +	public function __construct(filesystem_interface $filesystem, $paths = []) +	{ +		$paths = (array) $paths; +		$absolute_paths = []; + +		foreach ($paths as $path) +		{ +			$absolute_paths[] = $filesystem->realpath($path); +		} + +		parent::__construct($absolute_paths); +	} +} diff --git a/phpBB/phpbb/routing/helper.php b/phpBB/phpbb/routing/helper.php new file mode 100644 index 0000000000..c15608dce5 --- /dev/null +++ b/phpBB/phpbb/routing/helper.php @@ -0,0 +1,162 @@ +<?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\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\RequestContext; + +/** +* Controller helper class, contains methods that do things for controllers +*/ +class helper +{ +	/** +	 * config object +	 * @var \phpbb\config\config +	 */ +	protected $config; + +	/** +	 * phpBB router +	 * @var \phpbb\routing\router +	 */ +	protected $router; + +	/** +	 * @var \phpbb\symfony_request +	 */ +	protected $symfony_request; + +	/** +	 * @var \phpbb\request\request_interface +	 */ +	protected $request; + +	/** +	 * @var \phpbb\filesystem The filesystem object +	 */ +	protected $filesystem; + +	/** +	 * phpBB root path +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * PHP file extension +	 * @var string +	 */ +	protected $php_ext; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\config\config $config Config 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 $filesystem The filesystem object +	 * @param string $phpbb_root_path phpBB root path +	 * @param string $php_ext PHP file extension +	 */ +	public function __construct(\phpbb\config\config $config, \phpbb\routing\router $router, \phpbb\symfony_request $symfony_request, \phpbb\request\request_interface $request, \phpbb\filesystem\filesystem $filesystem, $phpbb_root_path, $php_ext) +	{ +		$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; +	} + +	/** +	 * Generate a URL to a route +	 * +	 * @param string	$route		Name of the route to travel +	 * @param array	$params		String or array of additional url parameters +	 * @param bool	$is_amp		Is url using & (true) or & (false) +	 * @param string|bool		$session_id	Possibility to use a custom session id instead of the global one +	 * @param bool|string		$reference_type The type of reference to be generated (one of the constants) +	 * @return string The URL already passed through append_sid() +	 */ +	public function route($route, array $params = array(), $is_amp = true, $session_id = false, $reference_type = UrlGeneratorInterface::ABSOLUTE_PATH) +	{ +		$anchor = ''; +		if (isset($params['#'])) +		{ +			$anchor = '#' . $params['#']; +			unset($params['#']); +		} + +		$context = new RequestContext(); +		$context->fromRequest($this->symfony_request); + +		if ($this->config['force_server_vars']) +		{ +			$context->setHost($this->config['server_name']); +			$context->setScheme(substr($this->config['server_protocol'], 0, -3)); +			$context->setHttpPort($this->config['server_port']); +			$context->setHttpsPort($this->config['server_port']); +			$context->setBaseUrl(rtrim($this->config['script_path'], '/')); +		} + +		$script_name = $this->symfony_request->getScriptName(); +		$page_name = substr($script_name, -1, 1) == '/' ? '' : utf8_basename($script_name); + +		$base_url = $context->getBaseUrl(); + +		// Append page name if base URL does not contain it +		if (!empty($page_name) && strpos($base_url, '/' . $page_name) === false) +		{ +			$base_url .= '/' . $page_name; +		} + +		// If enable_mod_rewrite is false we need to replace the current front-end by app.php, otherwise we need to remove it. +		$base_url = str_replace('/' . $page_name, empty($this->config['enable_mod_rewrite']) ? '/app.' . $this->php_ext : '', $base_url); + +		// We need to update the base url to move to the directory of the app.php file if the current script is not app.php +		if ($page_name !== 'app.php' && !$this->config['force_server_vars']) +		{ +			if (empty($this->config['enable_mod_rewrite'])) +			{ +				$base_url = str_replace('/app.' . $this->php_ext, '/' . $this->phpbb_root_path . 'app.' . $this->php_ext, $base_url); +			} +			else +			{ +				$base_url .= preg_replace(get_preg_expression('path_remove_dot_trailing_slash'), '$2', $this->phpbb_root_path); +			} +		} + +		$base_url = $this->request->escape($this->filesystem->clean_path($base_url), true); + +		$context->setBaseUrl($base_url); + +		$this->router->setContext($context); +		$route_url = $this->router->generate($route, $params, $reference_type); + +		if ($is_amp) +		{ +			$route_url = str_replace(array('&', '&'), array('&', '&'), $route_url); +		} + +		if ($reference_type === UrlGeneratorInterface::RELATIVE_PATH && empty($this->config['enable_mod_rewrite'])) +		{ +			$route_url = 'app.' . $this->php_ext . '/' . $route_url; +		} + +		return append_sid($route_url . $anchor, false, $is_amp, $session_id, true); +	} +} diff --git a/phpBB/phpbb/routing/loader_resolver.php b/phpBB/phpbb/routing/loader_resolver.php new file mode 100644 index 0000000000..13fbc6405c --- /dev/null +++ b/phpBB/phpbb/routing/loader_resolver.php @@ -0,0 +1,50 @@ +<?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\Loader\LoaderResolverInterface; + +/** + * @see Symfony\Component\Config\Loader\LoaderResolver + */ +class loader_resolver implements LoaderResolverInterface +{ +	/** +	 * @var \Symfony\Component\Config\Loader\LoaderInterface[] An array of LoaderInterface objects +	 */ +	protected $loaders = []; + +	public function __construct($loaders = []) +	{ +		$this->loaders = $loaders; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function resolve($resource, $type = null) +	{ +		/** @var \Symfony\Component\Config\Loader\LoaderInterface $loader */ +		foreach ($this->loaders as $loader) +		{ +			if ($loader->supports($resource, $type)) +			{ +				$loader->setResolver($this); +				return $loader; +			} +		} + +		return false; +	} +} diff --git a/phpBB/phpbb/routing/resources_locator/chained_resources_locator.php b/phpBB/phpbb/routing/resources_locator/chained_resources_locator.php new file mode 100644 index 0000000000..db9abf2095 --- /dev/null +++ b/phpBB/phpbb/routing/resources_locator/chained_resources_locator.php @@ -0,0 +1,47 @@ +<?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\resources_locator; + +class chained_resources_locator implements resources_locator_interface +{ +	/** +	 * @var resources_locator_interface[] +	 */ +	protected $locators; + +	/** +	 * Construct method +	 * +	 * @param resources_locator_interface[]	$locators	Locators +	 */ +	public function __construct($locators) +	{ +		$this->locators		= $locators; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function locate_resources() +	{ +		$resources = []; + +		foreach ($this->locators as $locator) +		{ +			$resources = array_merge($resources, $locator->locate_resources()); +		} + +		return $resources; +	} +} diff --git a/phpBB/phpbb/routing/resources_locator/default_resources_locator.php b/phpBB/phpbb/routing/resources_locator/default_resources_locator.php new file mode 100644 index 0000000000..90c3877007 --- /dev/null +++ b/phpBB/phpbb/routing/resources_locator/default_resources_locator.php @@ -0,0 +1,105 @@ +<?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\resources_locator; + +use phpbb\extension\manager; + +/** + * Locates the yaml routing resources located in the default locations + */ +class default_resources_locator implements resources_locator_interface +{ +	/** +	 * phpBB root path +	 * +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * Name of the current environment +	 * +	 * @var string +	 */ +	protected $environment; + +	/** +	 * Extension manager +	 * +	 * @var manager +	 */ +	protected $extension_manager; + +	/** +	 * Construct method +	 * +	 * @param string	$phpbb_root_path	phpBB root path +	 * @param string	$environment		Name of the current environment +	 * @param manager	$extension_manager	Extension manager +	 */ +	public function __construct($phpbb_root_path, $environment, manager $extension_manager = null) +	{ +		$this->phpbb_root_path		= $phpbb_root_path; +		$this->environment			= $environment; +		$this->extension_manager	= $extension_manager; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function locate_resources() +	{ +		$resources = [['config/' . $this->environment . '/routing/environment.yml', 'yaml']]; + +		$resources = $this->append_ext_resources($resources); + +		return $resources; +	} + +	/** +	 * Append extension resources to an array of resouces +	 * +	 * @see resources_locator_interface::locate_resources() +	 * +	 * @param mixed[] $resources List of resources +	 * +	 * @return mixed[] List of resources +	 */ +	protected function append_ext_resources(array $resources) +	{ +		if ($this->extension_manager !== null) +		{ +			foreach ($this->extension_manager->all_enabled(false) as $path) +			{ +				if (file_exists($this->phpbb_root_path . $path . 'config/' . $this->environment . '/routing/environment.yml')) +				{ +					$resources[] = [$path . 'config/' . $this->environment . '/routing/environment.yml', 'yaml']; +				} +				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')) +					{ +						$resources[] = [$path . 'config/default/routing/environment.yml', 'yaml']; +					} +					else if (!is_dir($this->phpbb_root_path . $path . 'config/default/routing') && file_exists($this->phpbb_root_path . $path . 'config/routing.yml')) +					{ +						$resources[] = [$path . 'config/routing.yml', 'yaml']; +					} +				} +			} +		} + +		return $resources; +	} +} diff --git a/phpBB/phpbb/routing/resources_locator/installer_resources_locator.php b/phpBB/phpbb/routing/resources_locator/installer_resources_locator.php new file mode 100644 index 0000000000..42cd0f11af --- /dev/null +++ b/phpBB/phpbb/routing/resources_locator/installer_resources_locator.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\routing\resources_locator; + +use phpbb\filesystem\filesystem_interface; + +/** + * Locates the yaml routing resources taking update directories into consideration + */ +class installer_resources_locator implements resources_locator_interface +{ +	/** +	 * phpBB's filesystem handler +	 * +	 * @var filesystem_interface +	 */ +	protected $filesystem; + +	/** +	 * phpBB root path +	 * +	 * @var string +	 */ +	protected $phpbb_root_path; + +	/** +	 * Name of the current environment +	 * +	 * @var string +	 */ +	protected $environment; + +	/** +	 * Construct method +	 * +	 * @param filesystem_interface	$filesystem			phpBB's filesystem handler +	 * @param string				$phpbb_root_path	phpBB root path +	 * @param string				$environment		Name of the current environment +	 */ +	public function __construct(filesystem_interface $filesystem, $phpbb_root_path, $environment) +	{ +		$this->filesystem			= $filesystem; +		$this->phpbb_root_path		= $phpbb_root_path; +		$this->environment			= $environment; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function locate_resources() +	{ +		if ($this->filesystem->exists($this->phpbb_root_path . 'install/update/new/config')) +		{ +			$resources = array( +				array('install/update/new/config/' . $this->environment . '/routing/environment.yml', 'yaml') +			); +		} +		else +		{ +			$resources = array( +				array('config/' . $this->environment . '/routing/environment.yml', 'yaml') +			); +		} + +		return $resources; +	} +} diff --git a/phpBB/phpbb/routing/resources_locator/resources_locator_interface.php b/phpBB/phpbb/routing/resources_locator/resources_locator_interface.php new file mode 100644 index 0000000000..46335cb288 --- /dev/null +++ b/phpBB/phpbb/routing/resources_locator/resources_locator_interface.php @@ -0,0 +1,27 @@ +<?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\resources_locator; + +interface resources_locator_interface +{ +	/** +	 * Locates a list of resources used to load the routes +	 * +	 * Each entry of the list can be either the resource or an array composed of 2 elements: +	 * the resource and its type. +	 * +	 * @return mixed[] List of resources +	 */ +	public function locate_resources(); +} diff --git a/phpBB/phpbb/routing/router.php b/phpBB/phpbb/routing/router.php new file mode 100644 index 0000000000..f19886fb0b --- /dev/null +++ b/phpBB/phpbb/routing/router.php @@ -0,0 +1,395 @@ +<?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 phpbb\routing\resources_locator\resources_locator_interface; +use Symfony\Component\Config\ConfigCache; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Routing\Generator\Dumper\PhpGeneratorDumper; +use Symfony\Component\Routing\Generator\UrlGenerator; +use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper; +use Symfony\Component\Routing\Matcher\UrlMatcher; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RouterInterface; + +/** + * Integration of all pieces of the routing system for easier use. + */ +class router implements RouterInterface +{ +	/** +	 * @var ContainerInterface +	 */ +	protected $container; + +	/** +	 * @var resources_locator_interface +	 */ +	protected $resources_locator; + +	/** +	 * @var LoaderInterface +	 */ +	protected $loader; + +	/** +	 * PHP file extensions +	 * +	 * @var string +	 */ +	protected $php_ext; + +	/** +	 * @var \Symfony\Component\Routing\Matcher\UrlMatcherInterface|null +	 */ +	protected $matcher; + +	/** +	 * @var \Symfony\Component\Routing\Generator\UrlGeneratorInterface|null +	 */ +	protected $generator; + +	/** +	 * @var RequestContext +	 */ +	protected $context; + +	/** +	 * @var RouteCollection +	 */ +	protected $route_collection; + +	/** +	 * @var string +	 */ +	protected $cache_dir; + +	/** +	 * Construct method +	 * +	 * @param ContainerInterface			$container			DI container +	 * @param resources_locator_interface	$resources_locator	Resources locator +	 * @param LoaderInterface				$loader				Resources loader +	 * @param string						$php_ext			PHP file extension +	 * @param string						$cache_dir			phpBB cache directory +	 */ +	public function __construct(ContainerInterface $container, resources_locator_interface $resources_locator, LoaderInterface $loader, $php_ext, $cache_dir) +	{ +		$this->container			= $container; +		$this->resources_locator	= $resources_locator; +		$this->loader				= $loader; +		$this->php_ext				= $php_ext; +		$this->context				= new RequestContext(); +		$this->cache_dir			= $cache_dir; +	} + +	/** +	 * Get the list of routes +	 * +	 * @return RouteCollection Get the route collection +	 */ +	public function get_routes() +	{ +		if ($this->route_collection === null /*|| $this->route_collection->count() === 0*/) +		{ +			$this->route_collection = new RouteCollection; +			foreach ($this->resources_locator->locate_resources() as $resource) +			{ +				if (is_array($resource)) +				{ +					$this->route_collection->addCollection($this->loader->load($resource[0], $resource[1])); +				} +				else +				{ +					$this->route_collection->addCollection($this->loader->load($resource)); +				} +			} + +			$this->resolveParameters($this->route_collection); +		} + +		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() +	{ +		try +		{ +			$cache = new ConfigCache("{$this->cache_dir}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); +		} +		catch (IOException $e) +		{ +			$this->create_new_url_matcher(); +		} +	} + +	/** +	 * 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() +	{ +		try +		{ +			$cache = new ConfigCache("{$this->cache_dir}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); +		} +		catch (IOException $e) +		{ +			$this->create_new_url_generator(); +		} +	} + +	/** +	 * Creates a new URL Generator +	 */ +	protected function create_new_url_generator() +	{ +		$this->generator = new UrlGenerator($this->get_routes(), $this->context); +	} + +	/** +	 * Replaces placeholders with service container parameter values in: +	 * - the route defaults, +	 * - the route requirements, +	 * - the route path, +	 * - the route host, +	 * - the route schemes, +	 * - the route methods. +	 * +	 * @param RouteCollection $collection +	 */ +	protected function resolveParameters(RouteCollection $collection) +	{ +		/** @var \Symfony\Component\Routing\Route $route */ +		foreach ($collection as $route) +		{ +			foreach ($route->getDefaults() as $name => $value) +			{ +				$route->setDefault($name, $this->resolve($value)); +			} + +			$requirements = $route->getRequirements(); +			unset($requirements['_scheme']); +			unset($requirements['_method']); + +			foreach ($requirements as $name => $value) +			{ +				$route->setRequirement($name, $this->resolve($value)); +			} + +			$route->setPath($this->resolve($route->getPath())); +			$route->setHost($this->resolve($route->getHost())); + +			$schemes = array(); +			foreach ($route->getSchemes() as $scheme) +			{ +				$schemes = array_merge($schemes, explode('|', $this->resolve($scheme))); +			} + +			$route->setSchemes($schemes); +			$methods = array(); +			foreach ($route->getMethods() as $method) +			{ +				$methods = array_merge($methods, explode('|', $this->resolve($method))); +			} + +			$route->setMethods($methods); +			$route->setCondition($this->resolve($route->getCondition())); +		} +	} + +	/** +	 * Recursively replaces placeholders with the service container parameters. +	 * +	 * @param mixed $value The source which might contain "%placeholders%" +	 * +	 * @return mixed The source with the placeholders replaced by the container +	 *               parameters. Arrays are resolved recursively. +	 * +	 * @throws ParameterNotFoundException When a placeholder does not exist as a container parameter +	 * @throws RuntimeException           When a container value is not a string or a numeric value +	 */ +	private function resolve($value) +	{ +		if (is_array($value)) +		{ +			foreach ($value as $key => $val) +			{ +				$value[$key] = $this->resolve($val); +			} + +			return $value; +		} + +		if (!is_string($value)) +		{ +			return $value; +		} + +		$container = $this->container; +		$escapedValue = preg_replace_callback('/%%|%([^%\s]++)%/', function ($match) use ($container, $value) +		{ +			// skip %% +			if (!isset($match[1])) +			{ +				return '%%'; +			} + +			$resolved = $container->getParameter($match[1]); +			if (is_string($resolved) || is_numeric($resolved)) +			{ +				return (string) $resolved; +			} + +			throw new RuntimeException(sprintf( +					'The container parameter "%s", used in the route configuration value "%s", '. +					'must be a string or numeric, but it is of type %s.', +					$match[1], +					$value, +					gettype($resolved) +				) +			); +		}, $value); + +		return str_replace('%%', '%', $escapedValue); +	} +} diff --git a/phpBB/phpbb/search/fulltext_mysql.php b/phpBB/phpbb/search/fulltext_mysql.php index 3ddbd85b36..d1962bc8cc 100644 --- a/phpBB/phpbb/search/fulltext_mysql.php +++ b/phpBB/phpbb/search/fulltext_mysql.php @@ -196,8 +196,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;  	} @@ -919,7 +919,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 e2c02ffdab..0fec092d52 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 @@ -99,7 +106,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  	* @param	\phpbb\event\dispatcher_interface	$phpbb_dispatcher	Event dispatcher object @@ -118,10 +125,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); @@ -349,9 +352,6 @@ class fulltext_native extends \phpbb\search\base  		$this->must_not_contain_ids = array();  		$this->must_exclude_one_ids = array(); -		$mode = ''; -		$ignore_no_id = true; -  		foreach ($query as $word)  		{  			if (empty($word)) @@ -594,7 +594,6 @@ class fulltext_native extends \phpbb\search\base  		$id_ary = array();  		$sql_where = array(); -		$group_by = false;  		$m_num = 0;  		$w_num = 0; @@ -1325,7 +1324,6 @@ class fulltext_native extends \phpbb\search\base  		$match[] = '#\[\/?[a-z0-9\*\+\-]+(?:=.*?)?(?::[a-z])?(\:?[0-9a-z]{5,})\]#';  		$min = $this->word_length['min']; -		$max = $this->word_length['max'];  		$isset_min = $min - 1; @@ -1361,9 +1359,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; @@ -1608,7 +1606,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;  		} @@ -1643,7 +1641,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 . ' @@ -1659,7 +1657,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);  	}  	/** @@ -1730,13 +1728,11 @@ 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')  	{  		static $conv = array(), $conv_loaded = array(); -		$words = $allow = array(); +		$allow = array();  		// Convert the text to UTF-8  		$encoding = strtolower($encoding); @@ -1758,12 +1754,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: @@ -1856,9 +1849,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 04441e6226..8dbc7212a1 100644 --- a/phpBB/phpbb/search/fulltext_postgres.php +++ b/phpBB/phpbb/search/fulltext_postgres.php @@ -501,7 +501,6 @@ class fulltext_postgres extends \phpbb\search\base  		$sql_select			= ($type == 'posts') ? 'p.post_id' : 'DISTINCT t.topic_id';  		$sql_from			= ($join_topic) ? TOPICS_TABLE . ' t, ' : '';  		$field				= ($type == 'posts') ? 'post_id' : 'topic_id'; -		$sql_author			= (sizeof($author_ary) == 1) ? ' = ' . $author_ary[0] : 'IN (' . implode(', ', $author_ary) . ')';  		if (sizeof($author_ary) && $author_name)  		{ @@ -526,7 +525,6 @@ class fulltext_postgres extends \phpbb\search\base  		$sql_where_options .= ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : '';  		$sql_where_options .= $sql_match_where; -		$tmp_sql_match = array();  		$sql_match = str_replace(',', " || ' ' ||", $sql_match);  		$tmp_sql_match = "to_tsvector ('" . $this->db->sql_escape($this->config['fulltext_postgres_ts_name']) . "', " . $sql_match . ") @@ to_tsquery ('" . $this->db->sql_escape($this->config['fulltext_postgres_ts_name']) . "', '" . $this->db->sql_escape($this->tsearch_query) . "')"; @@ -838,7 +836,7 @@ class fulltext_postgres extends \phpbb\search\base  					GROUP BY t.topic_id, $sort_by_sql[$sort_key]";  			} -			$result = $this->db->sql_query($sql_count); +			$this->db->sql_query($sql_count);  			$result_count = (int) $this->db->sql_fetchfield('result_count');  			if (!$result_count) @@ -916,7 +914,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 0dbc6e33df..89c615e087 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; @@ -143,12 +143,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'; @@ -219,7 +220,7 @@ class fulltext_sphinx  		}  		// Move delta to main index each hour -		set_config('search_gc', 3600); +		$this->config->set('search_gc', 3600);  		return false;  	} @@ -403,7 +404,7 @@ class fulltext_sphinx  					$variable = $section->get_variable_by_name($key);  					if (!$variable)  					{ -						$variable = $section->create_variable($key, $value); +						$section->create_variable($key, $value);  					}  					else  					{ @@ -412,7 +413,7 @@ class fulltext_sphinx  				}  				else  				{ -					$variable = $section->create_variable($key, $value); +					$section->create_variable($key, $value);  				}  			}  		} @@ -436,7 +437,6 @@ class fulltext_sphinx  			$match		= array('#\sand\s#i', '#\sor\s#i', '#\snot\s#i', '#\+#', '#-#', '#\|#', '#@#');  			$replace	= array(' & ', ' | ', '  - ', ' +', ' -', ' |', ''); -			$replacements = 0;  			$keywords = preg_replace($match, $replace, $keywords);  			$this->sphinx->SetMatchMode(SPH_MATCH_EXTENDED);  		} @@ -479,6 +479,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))  		{ @@ -487,8 +489,6 @@ class fulltext_sphinx  		$id_ary = array(); -		$join_topic = ($type != 'posts'); -  		// Sorting  		if ($type == 'topics') @@ -661,7 +661,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())); @@ -815,7 +815,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 83e87b7704..12031bda03 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(); @@ -460,11 +461,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));  						}  					}  				} @@ -486,7 +494,7 @@ class session  	*/  	function session_create($user_id = false, $set_admin = false, $persist_login = false, $viewonline = true)  	{ -		global $SID, $_SID, $db, $config, $cache, $phpbb_root_path, $phpEx, $phpbb_container, $phpbb_dispatcher; +		global $SID, $_SID, $db, $config, $cache, $phpbb_container, $phpbb_dispatcher;  		$this->data = array(); @@ -549,6 +557,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(); @@ -874,7 +883,7 @@ class session  	*/  	function session_kill($new_session = true)  	{ -		global $SID, $_SID, $db, $config, $phpbb_root_path, $phpEx, $phpbb_container, $phpbb_dispatcher; +		global $SID, $_SID, $db, $phpbb_container, $phpbb_dispatcher;  		$sql = 'DELETE FROM ' . SESSIONS_TABLE . "  			WHERE session_id = '" . $db->sql_escape($this->session_id) . "' @@ -899,6 +908,7 @@ class session  		unset($session_id);  		// 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); @@ -965,7 +975,7 @@ class session  	*/  	function session_gc()  	{ -		global $db, $config, $phpbb_root_path, $phpEx, $phpbb_container, $phpbb_dispatcher; +		global $db, $config, $phpbb_container, $phpbb_dispatcher;  		$batch_size = 10; @@ -1015,7 +1025,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'])  			{ @@ -1025,6 +1035,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']); @@ -1058,6 +1069,12 @@ class session  	{  		global $config; +		// If headers are already set, we just return +		if (headers_sent()) +		{ +			return; +		} +  		$name_data = rawurlencode($config['cookie_name'] . '_' . $name) . '=' . rawurlencode($cookiedata);  		$expire = gmdate('D, d-M-Y H:i:s \\G\\M\\T', $cookietime);  		$domain = (!$config['cookie_domain'] || $config['cookie_domain'] == '127.0.0.1' || strpos($config['cookie_domain'], '.') === false) ? '' : '; domain=' . $config['cookie_domain']; @@ -1209,7 +1226,7 @@ class session  		if ($banned && !$return)  		{ -			global $template, $phpbb_root_path, $phpEx; +			global $phpbb_root_path, $phpEx;  			// If the session is empty we need to create a valid one...  			if (empty($this->session_id)) @@ -1386,7 +1403,7 @@ class session  	*/  	function set_login_key($user_id = false, $key = false, $user_ip = false)  	{ -		global $config, $db; +		global $db;  		$user_id = ($user_id === false) ? $this->data['user_id'] : $user_id;  		$user_ip = ($user_ip === false) ? $this->ip : $user_ip; @@ -1396,7 +1413,7 @@ class session  		$sql_ary = array(  			'key_id'		=> (string) md5($key_id), -			'last_ip'		=> (string) $this->ip, +			'last_ip'		=> (string) $user_ip,  			'last_login'	=> (int) time()  		); @@ -1433,7 +1450,7 @@ class session  	*/  	function reset_login_keys($user_id = false)  	{ -		global $config, $db; +		global $db;  		$user_id = ($user_id === false) ? (int) $this->data['user_id'] : (int) $user_id; diff --git a/phpBB/phpbb/template/asset.php b/phpBB/phpbb/template/asset.php index ff9366af4a..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,18 +157,18 @@ 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 to the working directory. -		$real_root_path = realpath($this->path_helper->get_phpbb_root_path()) . DIRECTORY_SEPARATOR; +		// 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 (substr($path . DIRECTORY_SEPARATOR, 0, strlen($real_root_path)) === $real_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 = realpath('.') . DIRECTORY_SEPARATOR; +			$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))); diff --git a/phpBB/phpbb/template/assets_bag.php b/phpBB/phpbb/template/assets_bag.php new file mode 100644 index 0000000000..9013061b96 --- /dev/null +++ b/phpBB/phpbb/template/assets_bag.php @@ -0,0 +1,95 @@ +<?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\template; + +class assets_bag +{ +	/** @var asset[] */ +	protected $stylesheets = []; + +	/** @var asset[] */ +	protected $scripts = []; + +	/** +	 * Add a css asset to the bag +	 * +	 * @param asset $asset +	 */ +	public function add_stylesheet(asset $asset) +	{ +		$this->stylesheets[] = $asset; +	} + +	/** +	 * Add a js script asset to the bag +	 * +	 * @param asset $asset +	 */ +	public function add_script(asset $asset) +	{ +		$this->scripts[] = $asset; +	} + +	/** +	 * Returns all css assets +	 * +	 * @return asset[] +	 */ +	public function get_stylesheets() +	{ +		return $this->stylesheets; +	} + +	/** +	 * Returns all js assets +	 * +	 * @return asset[] +	 */ +	public function get_scripts() +	{ +		return $this->scripts; +	} + +	/** +	 * Returns the HTML code to includes all css assets +	 * +	 * @return string +	 */ +	public function get_stylesheets_content() +	{ +		$output = ''; +		foreach ($this->stylesheets as $stylesheet) +		{ +			$output .= "<link href=\"{$stylesheet->get_url()}\" rel=\"stylesheet\" type=\"text/css\" media=\"screen\" />\n"; +		} + +		return $output; +	} + +	/** +	 * Returns the HTML code to includes all js assets +	 * +	 * @return string +	 */ +	public function get_scripts_content() +	{ +		$output = ''; +		foreach ($this->scripts as $script) +		{ +			$output .= "<script type=\"text/javascript\" src=\"{$script->get_url()}\"></script>\n"; +		} + +		return $output; +	} +} diff --git a/phpBB/phpbb/template/exception/user_object_not_available.php b/phpBB/phpbb/template/exception/user_object_not_available.php new file mode 100644 index 0000000000..62fd2743c1 --- /dev/null +++ b/phpBB/phpbb/template/exception/user_object_not_available.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\template\exception; + +/** + * This exception is thrown when the user object was not set but it is required by the called method + */ +class user_object_not_available extends \phpbb\exception\runtime_exception +{ + +} diff --git a/phpBB/phpbb/template/twig/environment.php b/phpBB/phpbb/template/twig/environment.php index 476ffd935e..179412a2e3 100644 --- a/phpBB/phpbb/template/twig/environment.php +++ b/phpBB/phpbb/template/twig/environment.php @@ -13,14 +13,22 @@  namespace phpbb\template\twig; +use phpbb\template\assets_bag; +  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; @@ -33,26 +41,41 @@ class environment extends \Twig_Environment  	/** @var array **/  	protected $namespace_look_up_order = array('__main__'); +	/** @var assets_bag */ +	protected $assets_bag; +  	/**  	* 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 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, $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->phpbb_root_path = $this->phpbb_path_helper->get_phpbb_root_path();  		$this->web_root_path = $this->phpbb_path_helper->get_web_root_path(); -		return parent::__construct($loader, $options); +		$this->assets_bag = new assets_bag(); + +		$options = array_merge(array( +			'cache'			=> (defined('IN_INSTALL')) ? false : $cache_path, +			'debug'			=> false, +			'auto_reload'	=> (bool) $this->phpbb_config['load_tplcompile'], +			'autoescape'	=> false, +		), $options); + +		parent::__construct($loader, $options);  	}  	/** @@ -78,16 +101,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 @@ -108,6 +141,16 @@ class environment extends \Twig_Environment  	}  	/** +	 * Gets the assets bag +	 * +	 * @return assets_bag +	 */ +	public function get_assets_bag() +	{ +		return $this->assets_bag; +	} + +	/**  	* Get the namespace look up order  	*  	* @return array @@ -131,6 +174,55 @@ class environment extends \Twig_Environment  	}  	/** +	 * {@inheritdoc} +	 */ +	public function render($name, array $context = []) +	{ +		return $this->display_with_assets($name, $context); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function display($name, array $context = []) +	{ +		echo $this->display_with_assets($name, $context); +	} + +	/** +	 * {@inheritdoc} +	 */ +	private function display_with_assets($name, array $context = []) +	{ +		$placeholder_salt = unique_id(); + +		if (array_key_exists('definition', $context)) +		{ +			$context['definition']->set('SCRIPTS', '__SCRIPTS_' . $placeholder_salt . '__'); +			$context['definition']->set('STYLESHEETS', '__STYLESHEETS_' . $placeholder_salt . '__'); +		} + +		$output = parent::render($name, $context); + +		return $this->inject_assets($output, $placeholder_salt); +	} + +	/** +	 * Injects the assets (from INCLUDECSS/JS) in the output. +	 * +	 * @param string $output +	 * +	 * @return string +	 */ +	private function inject_assets($output, $placeholder_salt) +	{ +		$output = str_replace('__STYLESHEETS_' . $placeholder_salt . '__', $this->assets_bag->get_stylesheets_content(), $output); +		$output = str_replace('__SCRIPTS_' . $placeholder_salt . '__', $this->assets_bag->get_scripts_content(), $output); + +		return $output; +	} + +	/**  	* Loads a template by name.  	*  	* @param string  $name  The template name 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/extension/routing.php b/phpBB/phpbb/template/twig/extension/routing.php new file mode 100644 index 0000000000..829ce738eb --- /dev/null +++ b/phpBB/phpbb/template/twig/extension/routing.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\template\twig\extension; + +use Symfony\Bridge\Twig\Extension\RoutingExtension; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +class routing extends RoutingExtension +{ +	/** @var \phpbb\controller\helper */ +	protected $helper; + +	/** +	* Constructor +	* +	* @param \phpbb\routing\helper $helper +	*/ +	public function __construct(\phpbb\routing\helper $helper) +	{ +		$this->helper = $helper; +	} + +	public function getPath($name, $parameters = array(), $relative = false) +	{ +		return $this->helper->route($name, $parameters, true, false, $relative ? UrlGeneratorInterface::RELATIVE_PATH : UrlGeneratorInterface::ABSOLUTE_PATH); +	} + +	public function getUrl($name, $parameters = array(), $schemeRelative = false) +	{ +		return $this->helper->route($name, $parameters, true, false, $schemeRelative ? UrlGeneratorInterface::NETWORK_PATH : UrlGeneratorInterface::ABSOLUTE_URL); +	} +} 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 139a413b70..8b12188a77 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)  		{ @@ -119,7 +137,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/event.php b/phpBB/phpbb/template/twig/node/event.php index b765bde98d..11fdb75247 100644 --- a/phpBB/phpbb/template/twig/node/event.php +++ b/phpBB/phpbb/template/twig/node/event.php @@ -46,7 +46,7 @@ class event extends \Twig_Node  		{  			$ext_namespace = str_replace('/', '_', $ext_namespace); -			if (defined('DEBUG')) +			if ($this->environment->isDebug())  			{  				// If debug mode is enabled, lets check for new/removed EVENT  				//  templates on page load rather than at compile. This is @@ -58,7 +58,7 @@ class event extends \Twig_Node  				;  			} -			if (defined('DEBUG') || $this->environment->getLoader()->exists('@' . $ext_namespace . '/' . $location . '.html')) +			if ($this->environment->isDebug() || $this->environment->getLoader()->exists('@' . $ext_namespace . '/' . $location . '.html'))  			{  				$compiler  					->write("\$previous_look_up_order = \$this->env->getNamespaceLookUpOrder();\n") @@ -70,7 +70,7 @@ class event extends \Twig_Node  				;  			} -			if (defined('DEBUG')) +			if ($this->environment->isDebug())  			{  				$compiler  					->outdent() diff --git a/phpBB/phpbb/template/twig/node/includeasset.php b/phpBB/phpbb/template/twig/node/includeasset.php index 15195a226b..6d50eafc9d 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();") @@ -49,33 +49,18 @@ abstract class includeasset extends \Twig_Node  					->write("\$local_file = \$this->getEnvironment()->findTemplate(\$asset_path);\n")  					->write("\$asset->set_path(\$local_file, true);\n")  				->outdent() -				->write("\$asset->add_assets_version('{$config['assets_version']}');\n") -				->write("\$asset_file = \$asset->get_url();\n")  				->write("}\n") +				->write("\$asset->add_assets_version('{$config['assets_version']}');\n")  			->outdent()  			->write("}\n") -			->write("\$context['definition']->append('{$this->get_definition_name()}', '") -		; - -		$this->append_asset($compiler); - -		$compiler -			->raw("\n');\n") +			->write("\$this->getEnvironment()->get_assets_bag()->add_{$this->get_setters_name()}(\$asset);")  		;  	}  	/** -	* Get the definition name +	* Get the name of the assets bag setter  	* -	* @return string (e.g. 'SCRIPTS') -	*/ -	abstract public function get_definition_name(); - -	/** -	* Append the output code for the asset -	* -	* @param \Twig_Compiler A Twig_Compiler instance -	* @return null +	* @return string (e.g. 'script')  	*/ -	abstract protected function append_asset(\Twig_Compiler $compiler); +	abstract public function get_setters_name();  } diff --git a/phpBB/phpbb/template/twig/node/includecss.php b/phpBB/phpbb/template/twig/node/includecss.php index 2dac154036..2e97d4972d 100644 --- a/phpBB/phpbb/template/twig/node/includecss.php +++ b/phpBB/phpbb/template/twig/node/includecss.php @@ -18,20 +18,8 @@ class includecss extends \phpbb\template\twig\node\includeasset  	/**  	* {@inheritdoc}  	*/ -	public function get_definition_name() +	public function get_setters_name()  	{ -		return 'STYLESHEETS'; -	} - -	/** -	* {@inheritdoc} -	*/ -	public function append_asset(\Twig_Compiler $compiler) -	{ -		$compiler -			->raw("<link href=\"' . ") -			->raw("\$asset_file . '\"") -			->raw(' rel="stylesheet" type="text/css" media="screen" />') -		; +		return 'stylesheet';  	}  } diff --git a/phpBB/phpbb/template/twig/node/includejs.php b/phpBB/phpbb/template/twig/node/includejs.php index 0f67f9ff60..505b49757b 100644 --- a/phpBB/phpbb/template/twig/node/includejs.php +++ b/phpBB/phpbb/template/twig/node/includejs.php @@ -18,22 +18,8 @@ class includejs extends \phpbb\template\twig\node\includeasset  	/**  	* {@inheritdoc}  	*/ -	public function get_definition_name() +	public function get_setters_name()  	{ -		return 'SCRIPTS'; -	} - -	/** -	* {@inheritdoc} -	*/ -	protected function append_asset(\Twig_Compiler $compiler) -	{ -		$config = $this->environment->get_phpbb_config(); - -		$compiler -			->raw("<script type=\"text/javascript\" src=\"' . ") -			->raw("\$asset_file") -			->raw(". '\"></script>\n") -		; +		return 'script';  	}  } diff --git a/phpBB/phpbb/template/twig/twig.php b/phpBB/phpbb/template/twig/twig.php index d1bbb2b55a..f322778eda 100644 --- a/phpBB/phpbb/template/twig/twig.php +++ b/phpBB/phpbb/template/twig/twig.php @@ -13,6 +13,8 @@  namespace phpbb\template\twig; +use phpbb\template\exception\user_object_not_available; +  /**  * Twig Template class.  */ @@ -76,11 +78,14 @@ class twig extends \phpbb\template\base  	*  	* @param \phpbb\path_helper $path_helper  	* @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 \phpbb\user|null $user +	* @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, \phpbb\template\context $context, \phpbb\template\twig\environment $twig_environment, $cache_path, \phpbb\user $user = null, $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 +94,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/'))  		{ @@ -150,9 +128,16 @@ class twig extends \phpbb\template\base  	* Get the style tree of the style preferred by the current user  	*  	* @return array Style tree, most specific first +	* +	* @throws \phpbb\template\exception\user_object_not_available	When user service was not set  	*/  	public function get_user_style()  	{ +		if ($this->user === null) +		{ +			throw new user_object_not_available(); +		} +  		$style_list = array(  			$this->user->style['style_path'],  		); @@ -368,14 +353,24 @@ class twig extends \phpbb\template\base  			$context_vars['.'][0], // To get normal vars  			array(  				'definition'	=> new \phpbb\template\twig\definition(), -				'user'			=> $this->user,  				'loops'			=> $context_vars, // To get loops  			)  		); +		if ($this->user instanceof \phpbb\user) +		{ +			$vars['user'] = $this->user; +		} +  		// 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..ad611fb5b4 --- /dev/null +++ b/phpBB/phpbb/textformatter/parser_interface.php @@ -0,0 +1,112 @@ +<?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[] Array of arrays. Each array contains a lang string at index 0 plus any number +	*                 of optional parameters +	*/ +	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..916fdff909 --- /dev/null +++ b/phpBB/phpbb/textformatter/s9e/factory.php @@ -0,0 +1,617 @@ +<?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\textformatter\s9e\link_helper +	*/ +	protected $link_helper; + +	/** +	* @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 \phpbb\config\config +	*/ +	protected $config; + +	/** +	* @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 lang={IDENTIFIER;optional}]{TEXT}[/CODE]', +		'color' => '[COLOR={COLOR}]{TEXT}[/COLOR]', +		'email' => '[EMAIL={EMAIL;useContent} subject={TEXT;optional;postFilter=rawurlencode} body={TEXT;optional;postFilter=rawurlencode}]{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} #createChild=LI]{TEXT}[/LIST]', +		'li'    => '[* $tagName=LI]{TEXT}[/*]', +		'quote' => +			"[QUOTE +				author={TEXT1;optional} +				post_id={UINT;optional} +				post_url={URL;optional;postFilter=#false} +				profile_url={URL;optional;postFilter=#false} +				time={UINT;optional} +				url={URL;optional} +				user_id={UINT;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} $forceLookahead=true]{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}" class="postimage" 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> +			<xsl:attribute name="href"> +				<xsl:text>mailto:</xsl:text> +				<xsl:value-of select="@email"/> +				<xsl:if test="@subject or @body"> +					<xsl:text>?</xsl:text> +					<xsl:if test="@subject">subject=<xsl:value-of select="@subject"/></xsl:if> +					<xsl:if test="@body"><xsl:if test="@subject">&</xsl:if>body=<xsl:value-of select="@body"/></xsl:if> +				</xsl:if> +			</xsl:attribute> +			<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 \phpbb\config\config $config +	* @param \phpbb\textformatter\s9e\link_helper $link_helper +	* @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, \phpbb\config\config $config, \phpbb\textformatter\s9e\link_helper $link_helper, $cache_dir, $cache_key_parser, $cache_key_renderer) +	{ +		$this->link_helper = $link_helper; +		$this->cache = $cache; +		$this->cache_dir = $cache_dir; +		$this->cache_key_parser = $cache_key_parser; +		$this->cache_key_renderer = $cache_key_renderer; +		$this->config = $config; +		$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))); + +		// Reset the list of allowed schemes +		foreach ($configurator->urlConfig->getAllowedSchemes() as $scheme) +		{ +			$configurator->urlConfig->disallowScheme($scheme); +		} +		foreach (explode(',', $this->config['allowed_schemes_links']) as $scheme) +		{ +			$configurator->urlConfig->allowScheme(trim($scheme)); +		} + +		// 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->set( +				$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 +		$this->configure_autolink($configurator); + +		// 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; + +		// Load the Emoji plugin and modify its tag's template to obey viewsmilies +		$configurator->Emoji->setImageSize(18); +		$tag = $configurator->Emoji->getTag(); +		$tag->template = '<xsl:choose><xsl:when test="$S_VIEWSMILIES">' . str_replace('class="emoji"', 'class="smilies"', $tag->template) . '</xsl:when><xsl:otherwise><xsl:value-of select="."/></xsl:otherwise></xsl:choose>'; + +		/** +		* 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); +	} + +	/** +	* Configure the Autolink / Autoemail plugins used to linkify text +	* +	* @param  \s9e\TextFormatter\Configurator $configurator +	* @return void +	*/ +	protected function configure_autolink(Configurator $configurator) +	{ +		$configurator->plugins->load('Autoemail'); +		$configurator->plugins->load('Autolink', array('matchWww' => true)); + +		// Add a tag filter that creates a tag that stores and replace the +		// content of a link created by the Autolink plugin +		$configurator->Autolink->getTag()->filterChain +			->add(array($this->link_helper, 'generate_link_text_tag')) +			->resetParameters() +			->addParameterByName('tag') +			->addParameterByName('parser'); + +		// Create a tag that will be used to display the truncated text by +		// replacing the original content with the content of the @text attribute +		$tag = $configurator->tags->add('LINK_TEXT'); +		$tag->attributes->add('text'); +		$tag->template = '<xsl:value-of select="@text"/>'; + +		$tag->filterChain +			->add(array($this->link_helper, 'truncate_local_url')) +			->resetParameters() +			->addParameterByName('tag') +			->addParameterByValue(generate_board_url() . '/'); +		$tag->filterChain +			->add(array($this->link_helper, 'truncate_text')) +			->resetParameters() +			->addParameterByName('tag'); +		$tag->filterChain +			->add(array($this->link_helper, 'cleanup_tag')) +			->resetParameters() +			->addParameterByName('tag') +			->addParameterByName('parser'); +	} + +	/** +	* 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']; + +		// Replace the regular quote template with the extended quote template if available +		if (isset($fragments['quote_extended'])) +		{ +			$templates['quote'] = $fragments['quote_extended']; +		} + +		// 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/link_helper.php b/phpBB/phpbb/textformatter/s9e/link_helper.php new file mode 100644 index 0000000000..0f44603dec --- /dev/null +++ b/phpBB/phpbb/textformatter/s9e/link_helper.php @@ -0,0 +1,118 @@ +<?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; + +class link_helper +{ +	/** +	* Clean up and invalidate a LINK_TEXT tag if applicable +	* +	* Will invalidate the tag if its replacement text is the same as the original +	* text and would have no visible effect +	* +	* @param  \s9e\TextFormatter\Parser\Tag $tag    LINK_TEXT tag +	* @param  \s9e\TextFormatter\Parser     $parser Parser +	* @return bool                                  Whether the tag is valid +	*/ +	public function cleanup_tag(\s9e\TextFormatter\Parser\Tag $tag, \s9e\TextFormatter\Parser $parser) +	{ +		// Invalidate if the content of the tag matches the text attribute +		$text = substr($parser->getText(), $tag->getPos(), $tag->getLen()); + +		return ($text !== $tag->getAttribute('text')); +	} + +	/** +	* Create a LINK_TEXT tag inside of a link +	* +	* Meant to only apply to linkified URLs and [url] BBCodes without a parameter +	* +	* @param  \s9e\TextFormatter\Parser\Tag $tag    URL tag (start tag) +	* @param  \s9e\TextFormatter\Parser     $parser Parser +	* @return bool                                  Always true to indicate that the tag is valid +	*/ +	public function generate_link_text_tag(\s9e\TextFormatter\Parser\Tag $tag, \s9e\TextFormatter\Parser $parser) +	{ +		// Only create a LINK_TEXT tag if the start tag is paired with an end +		// tag, which is the case with tags from the Autolink plugins and with +		// the [url] BBCode when its content is used for the URL +		if (!$tag->getEndTag() || !$this->should_shorten($tag, $parser->getText())) +		{ +			return true; +		} + +		// Capture the text between the start tag and its end tag +		$start  = $tag->getPos() + $tag->getLen(); +		$end    = $tag->getEndTag()->getPos(); +		$length = $end - $start; +		$text   = substr($parser->getText(), $start, $length); + +		// Create a tag that consumes the link's text +		$parser->addSelfClosingTag('LINK_TEXT', $start, $length)->setAttribute('text', $text); + +		return true; +	} + +	/** +	* Test whether we should shorten this tag's text +	* +	* Will test whether the tag either does not use any markup or uses a single +	* [url] BBCode +	* +	* @param  \s9e\TextFormatter\Parser\Tag $tag  URL tag +	* @param  string                        $text Original text +	* @return bool +	*/ +	protected function should_shorten(\s9e\TextFormatter\Parser\Tag $tag, $text) +	{ +		return ($tag->getLen() === 0 || strtolower(substr($text, $tag->getPos(), $tag->getLen())) === '[url]'); +	} + +	/** +	* Remove the board's root URL from a the start of a string +	* +	* @param  \s9e\TextFormatter\Parser\Tag $tag       LINK_TEXT tag +	* @param  string                        $board_url Forum's root URL (with trailing slash) +	* @return bool                                     Always true to indicate that the tag is valid +	*/ +	public function truncate_local_url(\s9e\TextFormatter\Parser\Tag $tag, $board_url) +	{ +		$text = $tag->getAttribute('text'); +		if (stripos($text, $board_url) === 0 && strlen($text) > strlen($board_url)) +		{ +			$tag->setAttribute('text', substr($text, strlen($board_url))); +		} + +		return true; +	} + +	/** +	* Truncate the replacement text set in a LINK_TEXT tag +	* +	* @param  \s9e\TextFormatter\Parser\Tag $tag LINK_TEXT tag +	* @return bool                               Always true to indicate that the tag is valid +	*/ +	public function truncate_text(\s9e\TextFormatter\Parser\Tag $tag) +	{ +		$text = $tag->getAttribute('text'); +		if (utf8_strlen($text) > 55) +		{ +			$text = utf8_substr($text, 0, 39) . ' ... ' . utf8_substr($text, -10); +		} + +		$tag->setAttribute('text', $text); + +		return true; +	} +} diff --git a/phpBB/phpbb/textformatter/s9e/parser.php b/phpBB/phpbb/textformatter/s9e/parser.php new file mode 100644 index 0000000000..e2653d60f0 --- /dev/null +++ b/phpBB/phpbb/textformatter/s9e/parser.php @@ -0,0 +1,397 @@ +<?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; + +	/** +	* Constructor +	* +	* @param \phpbb\cache\driver_interface $cache +	* @param string $key Cache key +	* @param factory $factory +	* @param \phpbb\event\dispatcher_interface $dispatcher +	*/ +	public function __construct(\phpbb\cache\driver\driver_interface $cache, $key, 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; + +		$parser = $this; + +		/** +		* Configure the parser service +		* +		* Can be used to: +		*  - toggle features or BBCodes +		*  - register variables or custom parsers in the s9e\TextFormatter parser +		*  - configure the s9e\TextFormatter parser's runtime settings +		* +		* @event core.text_formatter_s9e_parser_setup +		* @var \phpbb\textformatter\s9e\parser parser This parser service +		* @since 3.2.0-a1 +		*/ +		$vars = array('parser'); +		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 convert 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(, $msg, $context) = $entry; + +			if ($msg === 'Tag limit exceeded') +			{ +				if ($context['tagName'] === 'E') +				{ +					$errors[] = array('TOO_MANY_SMILIES', $context['tagLimit']); +				} +				else if ($context['tagName'] === 'URL') +				{ +					$errors[] = array('TOO_MANY_URLS', $context['tagLimit']); +				} +			} +			else if ($msg === 'MAX_FONT_SIZE_EXCEEDED') +			{ +				$errors[] = array($msg, $context['max_size']); +			} +			else if (preg_match('/^MAX_(?:FLASH|IMG)_(HEIGHT|WIDTH)_EXCEEDED$/D', $msg, $m)) +			{ +				$errors[] = array($msg, $context['max_' . strtolower($m[1])]); +			} +			else if ($msg === 'Tag is disabled') +			{ +				$name = strtolower($context['tag']->getName()); +				$errors[] = array('UNAUTHORISED_BBCODE', '[' . $name . ']'); +			} +			else if ($msg === 'UNABLE_GET_IMAGE_SIZE') +			{ +				$errors[] = array($msg); +			} +		} + +		// Deduplicate error messages. array_unique() only works on strings so we have to serialize +		if (!empty($errors)) +		{ +			$errors = array_map('unserialize', array_unique(array_map('serialize', $errors))); +		} + +		return $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/quote_helper.php b/phpBB/phpbb/textformatter/s9e/quote_helper.php new file mode 100644 index 0000000000..24109ac8cc --- /dev/null +++ b/phpBB/phpbb/textformatter/s9e/quote_helper.php @@ -0,0 +1,81 @@ +<?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; + +class quote_helper +{ +	/** +	* @var string Base URL for a post link, uses {POST_ID} as placeholder +	*/ +	protected $post_url; + +	/** +	* @var string Base URL for a profile link, uses {USER_ID} as placeholder +	*/ +	protected $profile_url; + +	/** +	* @var \phpbb\user +	*/ +	protected $user; + +	/** +	* Constructor +	* +	* @param \phpbb\user $user +	* @param string $root_path +	* @param string $php_ext +	*/ +	public function __construct(\phpbb\user $user, $root_path, $php_ext) +	{ +		$this->post_url = append_sid($root_path . 'viewtopic.' . $php_ext, 'p={POST_ID}#p{POST_ID}'); +		$this->profile_url = append_sid($root_path . 'memberlist.' . $php_ext, 'mode=viewprofile&u={USER_ID}'); +		$this->user = $user; +	} + +	/** +	* Inject dynamic metadata into QUOTE tags in given XML +	* +	* @param  string $xml Original XML +	* @return string      Modified XML +	*/ +	public function inject_metadata($xml) +	{ +		$post_url = $this->post_url; +		$profile_url = $this->profile_url; +		$user = $this->user; + +		return \s9e\TextFormatter\Utils::replaceAttributes( +			$xml, +			'QUOTE', +			function ($attributes) use ($post_url, $profile_url, $user) +			{ +				if (isset($attributes['post_id'])) +				{ +					$attributes['post_url'] = str_replace('{POST_ID}', $attributes['post_id'], $post_url); +				} +				if (isset($attributes['time'])) +				{ +					$attributes['date'] = $user->format_date($attributes['time']); +				} +				if (isset($attributes['user_id'])) +				{ +					$attributes['profile_url'] = str_replace('{USER_ID}', $attributes['user_id'], $profile_url); +				} + +				return $attributes; +			} +		); +	} +} diff --git a/phpBB/phpbb/textformatter/s9e/renderer.php b/phpBB/phpbb/textformatter/s9e/renderer.php new file mode 100644 index 0000000000..9be20b7f53 --- /dev/null +++ b/phpBB/phpbb/textformatter/s9e/renderer.php @@ -0,0 +1,315 @@ +<?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 quote_helper +	*/ +	protected $quote_helper; + +	/** +	* @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))); +	} + +	/** +	* Configure the quote_helper object used to display extended information in quotes +	* +	* @param  quote_helper $quote_helper +	*/ +	public function configure_quote_helper(quote_helper $quote_helper) +	{ +		$this->quote_helper = $quote_helper; +	} + +	/** +	* 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) +	{ +		if (isset($this->quote_helper)) +		{ +			$xml = $this->quote_helper->inject_metadata($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); + +		/** +		* 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; +	} + +	/** +	* {@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..b317fe4a8d --- /dev/null +++ b/phpBB/phpbb/textformatter/s9e/utils.php @@ -0,0 +1,139 @@ +<?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); +	} + +	/** +	* Format given string to be used as an attribute value +	* +	* Will return the string as-is if it can be used in a BBCode without quotes. Otherwise, +	* it will use either single- or double- quotes depending on whichever requires less escaping. +	* Quotes and backslashes are escaped with backslashes where necessary +	* +	* @param  string $str Original string +	* @return string      Same string if possible, escaped string within quotes otherwise +	*/ +	protected function format_attribute_value($str) +	{ +		if (!preg_match('/[ "\'\\\\\\]]/', $str)) +		{ +			// Return as-is if it contains none of: space, ' " \ or ] +			return $str; +		} +		$singleQuoted = "'" . addcslashes($str, "\\'") . "'"; +		$doubleQuoted = '"' . addcslashes($str, '\\"') . '"'; + +		return (strlen($singleQuoted) < strlen($doubleQuoted)) ? $singleQuoted : $doubleQuoted; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function generate_quote($text, array $attributes = array()) +	{ +		$text = trim($text); +		$quote = '[quote'; +		if (isset($attributes['author'])) +		{ +			// Add the author as the BBCode's default attribute +			$quote .= '=' . $this->format_attribute_value($attributes['author']); +			unset($attributes['author']); +		} + +		if (isset($attributes['user_id']) && $attributes['user_id'] == ANONYMOUS) +		{ +			unset($attributes['user_id']); +		} + +		ksort($attributes); +		foreach ($attributes as $name => $value) +		{ +			$quote .= ' ' . $name . '=' . $this->format_attribute_value($value); +		} +		$quote .= ']'; +		$newline = (strlen($quote . $text . '[/quote]') > 80 || strpos($text, "\n") !== false) ? "\n" : ''; +		$quote .= $newline . $text . $newline . '[/quote]'; + +		return $quote; +	} + +	/** +	* Get a list of quote authors, limited to the outermost quotes +	* +	* @param  string   $xml Parsed text +	* @return string[]      List of authors +	*/ +	public function get_outermost_quote_authors($xml) +	{ +		$authors = array(); +		if (strpos($xml, '<QUOTE ') === false) +		{ +			return $authors; +		} + +		$dom = new \DOMDocument; +		$dom->loadXML($xml); +		$xpath = new \DOMXPath($dom); +		foreach ($xpath->query('//QUOTE[not(ancestor::QUOTE)]/@author') as $author) +		{ +			$authors[] = $author->textContent; +		} + +		return $authors; +	} + +	/** +	* 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..4810453cd1 --- /dev/null +++ b/phpBB/phpbb/textformatter/utils_interface.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\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); + +	/** +	* Create a quote block for given text +	* +	* Possible attributes: +	*   - author:  author's name (usually a username) +	*   - post_id: post_id of the post being quoted +	*   - user_id: user_id of the user being quoted +	*   - time:    timestamp of the original message +	* +	* @param  string $text       Quote's text +	* @param  array  $attributes Quote's attributes +	* @return string             Quote block to be used in a new post/text +	*/ +	public function generate_quote($text, array $attributes = array()); + +	/** +	* Get a list of quote authors, limited to the outermost quotes +	* +	* @param  string   $text Parsed text +	* @return string[]       List of authors +	*/ +	public function get_outermost_quote_authors($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/textreparser/base.php b/phpBB/phpbb/textreparser/base.php new file mode 100644 index 0000000000..afa5ccacad --- /dev/null +++ b/phpBB/phpbb/textreparser/base.php @@ -0,0 +1,244 @@ +<?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\textreparser; + +abstract class base implements reparser_interface +{ +	/** +	* @var bool Whether to save changes to the database +	*/ +	protected $save_changes = true; + +	/** +	* {@inheritdoc} +	*/ +	abstract public function get_max_id(); + +	/** +	* Return all records in given range +	* +	* @param  integer $min_id Lower bound +	* @param  integer $max_id Upper bound +	* @return array           Array of records +	*/ +	abstract protected function get_records_by_range($min_id, $max_id); + +	/** +	* {@inheritdoc} +	*/ +	abstract protected function save_record(array $record); + +	/** +	* Add fields to given record, if applicable +	* +	* The enable_* fields are not always saved to the database. Sometimes we need to guess their +	* original value based on the text content or possibly other fields +	* +	* @param  array $record Original record +	* @return array         Complete record +	*/ +	protected function add_missing_fields(array $record) +	{ +		if (!isset($record['enable_bbcode'], $record['enable_smilies'], $record['enable_magic_url'])) +		{ +			if (isset($record['options'])) +			{ +				$record += array( +					'enable_bbcode'    => (bool) ($record['options'] & OPTION_FLAG_BBCODE), +					'enable_smilies'   => (bool) ($record['options'] & OPTION_FLAG_SMILIES), +					'enable_magic_url' => (bool) ($record['options'] & OPTION_FLAG_LINKS), +				); +			} +			else +			{ +				$record += array( +					'enable_bbcode'    => $this->guess_bbcodes($record), +					'enable_smilies'   => $this->guess_smilies($record), +					'enable_magic_url' => $this->guess_magic_url($record), +				); +			} +		} + +		// Those BBCodes are disabled based on context and user permissions and that value is never +		// stored in the database. Here we test whether they were used in the original text. +		$bbcodes = array('flash', 'img', 'quote', 'url'); +		foreach ($bbcodes as $bbcode) +		{ +			$field_name = 'enable_' . $bbcode . '_bbcode'; +			$record[$field_name] = $this->guess_bbcode($record, $bbcode); +		} + +		// Magic URLs are tied to the URL BBCode, that's why if magic URLs are enabled we make sure +		// that the URL BBCode is also enabled +		if ($record['enable_magic_url']) +		{ +			$record['enable_url_bbcode'] = true; +		} + +		return $record; +	} + +	/** +	* Disable saving changes to the database +	*/ +	public function disable_save() +	{ +		$this->save_changes = false; +	} + +	/** +	* Enable saving changes to the database +	*/ +	public function enable_save() +	{ +		$this->save_changes = true; +	} + +	/** +	* Guess whether given BBCode is in use in given record +	* +	* @param  array  $record +	* @param  string $bbcode +	* @return bool +	*/ +	protected function guess_bbcode(array $record, $bbcode) +	{ +		if (!empty($record['bbcode_uid'])) +		{ +			// Look for the closing tag, e.g. [/url] +			$match = '[/' . $bbcode . ':' . $record['bbcode_uid']; +			if (strpos($record['text'], $match) !== false) +			{ +				return true; +			} +		} + +		if (substr($record['text'], 0, 2) === '<r') +		{ +			// Look for the closing tag inside of a e element, in an element of the same name, e.g. +			// <e>[/url]</e></URL> +			$match = '<e>[/' . $bbcode . ']</e></' . strtoupper($bbcode) . '>'; +			if (strpos($record['text'], $match) !== false) +			{ +				return true; +			} +		} + +		return false; +	} + +	/** +	* Guess whether any BBCode is in use in given record +	* +	* @param  array $record +	* @return bool +	*/ +	protected function guess_bbcodes(array $record) +	{ +		if (!empty($record['bbcode_uid'])) +		{ +			// Test whether the bbcode_uid is in use +			$match = ':' . $record['bbcode_uid']; +			if (strpos($record['text'], $match) !== false) +			{ +				return true; +			} +		} + +		if (substr($record['text'], 0, 2) === '<r') +		{ +			// Look for a closing tag inside of an e element +			return (bool) preg_match('(<e>\\[/\\w+\\]</e>)', $match); +		} + +		return false; +	} + +	/** +	* Guess whether magic URLs are in use in given record +	* +	* @param  array $record +	* @return bool +	*/ +	protected function guess_magic_url(array $record) +	{ +		// Look for <!-- m --> or for a URL tag that's not immediately followed by <s> +		return (strpos($record['text'], '<!-- m -->') !== false || preg_match('(<URL [^>]++>(?!<s>))', $record['text'])); +	} + +	/** +	* Guess whether smilies are in use in given record +	* +	* @param  array $record +	* @return bool +	*/ +	protected function guess_smilies(array $record) +	{ +		return (strpos($record['text'], '<!-- s') !== false || strpos($record['text'], '<E>') !== false); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function reparse_range($min_id, $max_id) +	{ +		foreach ($this->get_records_by_range($min_id, $max_id) as $record) +		{ +			$this->reparse_record($record); +		} +	} + +	/** +	* Reparse given record +	* +	* @param array $record Associative array containing the record's data +	*/ +	protected function reparse_record(array $record) +	{ +		$record = $this->add_missing_fields($record); +		$flags = ($record['enable_bbcode']) ? OPTION_FLAG_BBCODE : 0; +		$flags |= ($record['enable_smilies']) ? OPTION_FLAG_SMILIES : 0; +		$flags |= ($record['enable_magic_url']) ? OPTION_FLAG_LINKS : 0; +		$unparsed = array_merge( +			$record, +			generate_text_for_edit($record['text'], $record['bbcode_uid'], $flags) +		); + +		// generate_text_for_edit() and decode_message() actually return the text as HTML. It has to +		// be decoded to plain text before it can be reparsed +		$text = html_entity_decode($unparsed['text'], ENT_QUOTES, 'UTF-8'); +		$bitfield = $flags = null; +		generate_text_for_storage( +			$text, +			$unparsed['bbcode_uid'], +			$bitfield, +			$flags, +			$unparsed['enable_bbcode'], +			$unparsed['enable_magic_url'], +			$unparsed['enable_smilies'], +			$unparsed['enable_img_bbcode'], +			$unparsed['enable_flash_bbcode'], +			$unparsed['enable_quote_bbcode'], +			$unparsed['enable_url_bbcode'], +			'reparse' +		); + +		// Save the new text if it has changed and it's not a dry run +		if ($text !== $record['text'] && $this->save_changes) +		{ +			$record['text'] = $text; +			$this->save_record($record); +		} +	} +} diff --git a/phpBB/phpbb/textreparser/manager.php b/phpBB/phpbb/textreparser/manager.php new file mode 100644 index 0000000000..fddd867923 --- /dev/null +++ b/phpBB/phpbb/textreparser/manager.php @@ -0,0 +1,128 @@ +<?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\textreparser; + +class manager +{ +	/** +	 * @var \phpbb\config\config +	 */ +	protected $config; + +	/** +	 * @var \phpbb\config\db_text +	 */ +	protected $config_text; + +	/** +	 * @var \phpbb\di\service_collection +	 */ +	protected $reparsers; + +	/** +	 * @var array +	 */ +	protected $resume_data; + +	/** +	 * Constructor +	 * +	 * @param \phpbb\config\config			$config +	 * @param \phpbb\config\db_text			$config_text +	 * @param \phpbb\di\service_collection	$reparsers +	 */ +	public function __construct(\phpbb\config\config $config, \phpbb\config\db_text $config_text, \phpbb\di\service_collection $reparsers) +	{ +		$this->config = $config; +		$this->config_text = $config_text; +		$this->reparsers = $reparsers; +	} + +	/** +	 * Loads resume data from the database +	 * +	 * @param string	$name	Name of the reparser to which the resume data belongs +	 * +	 * @return array +	 */ +	public function get_resume_data($name) +	{ +		if ($this->resume_data === null) +		{ +			$resume_data = $this->config_text->get('reparser_resume'); +			$this->resume_data = !empty($resume_data) ? unserialize($resume_data) : array(); +		} + +		return isset($this->resume_data[$name]) ? $this->resume_data[$name] : array(); +	} + +	/** +	 * Updates the resume data in the database +	 * +	 * @param string	$name		Name of the reparser to which the resume data belongs +	 * @param int		$min		Lowest record ID +	 * @param int		$current	Current record ID +	 * @param int		$size		Number of records to process at a time +	 * @param bool		$update_db	True if the resume data should be written to the database, false if not. (default: true) +	 */ +	public function update_resume_data($name, $min, $current, $size, $update_db = true) +	{ +		// Prevent overwriting the old, stored array +		if ($this->resume_data === null) +		{ +			$this->get_resume_data(''); +		} + +		$this->resume_data[$name] = array( +			'range-min'		=> $min, +			'range-max'		=> $current, +			'range-size'	=> $size, +		); + +		if ($update_db) +		{ +			$this->config_text->set('reparser_resume', serialize($this->resume_data)); +		} +	} + +	/** +	 * Sets the interval for a text_reparser cron task +	 * +	 * @param string	$name		Name of the reparser to schedule +	 * @param int		$interval	Interval in seconds, 0 to disable the cron task +	 */ +	public function schedule($name, $interval) +	{ +		if (isset($this->reparsers[$name]) && isset($this->config[$name . '_cron_interval'])) +		{ +			$this->config->set($name . '_cron_interval', $interval); +		} +	} + +	/** +	 * Sets the interval for all text_reparser cron tasks +	 * +	 * @param int	$interval	Interval in seconds, 0 to disable the cron task +	 */ +	public function schedule_all($interval) +	{ +		// This way we don't construct every registered reparser +		$reparser_array = array_keys($this->reparsers->getArrayCopy()); + +		foreach ($reparser_array as $reparser) +		{ +			$this->schedule($reparser, $interval); +		} +	} +} diff --git a/phpBB/phpbb/textreparser/plugins/contact_admin_info.php b/phpBB/phpbb/textreparser/plugins/contact_admin_info.php new file mode 100644 index 0000000000..8910f2256b --- /dev/null +++ b/phpBB/phpbb/textreparser/plugins/contact_admin_info.php @@ -0,0 +1,69 @@ +<?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\textreparser\plugins; + +class contact_admin_info extends \phpbb\textreparser\base +{ +	/** +	* @var \phpbb\config\db_text +	*/ +	protected $config_text; + +	/** +	* Constructor +	* +	* @param \phpbb\config\db_text $config_text +	*/ +	public function __construct(\phpbb\config\db_text $config_text) +	{ +		$this->config_text = $config_text; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function get_max_id() +	{ +		return 1; +	} + +	/** +	* {@inheritdoc} +	*/ +	protected function get_records_by_range($min_id, $max_id) +	{ +		$values = $this->config_text->get_array(array( +			'contact_admin_info', +			'contact_admin_info_uid', +			'contact_admin_info_flags', +		)); + +		return array(array( +			'id'               => 1, +			'text'             => $values['contact_admin_info'], +			'bbcode_uid'       => $values['contact_admin_info_uid'], +			'enable_bbcode'    => $values['contact_admin_info_flags'] & OPTION_FLAG_BBCODE, +			'enable_magic_url' => $values['contact_admin_info_flags'] & OPTION_FLAG_LINKS, +			'enable_smilies'   => $values['contact_admin_info_flags'] & OPTION_FLAG_SMILIES, +		)); +	} + +	/** +	* {@inheritdoc} +	*/ +	protected function save_record(array $record) +	{ +		$this->config_text->set('contact_admin_info', $record['text']); +	} +} diff --git a/phpBB/phpbb/textreparser/plugins/forum_description.php b/phpBB/phpbb/textreparser/plugins/forum_description.php new file mode 100644 index 0000000000..b0f5a42452 --- /dev/null +++ b/phpBB/phpbb/textreparser/plugins/forum_description.php @@ -0,0 +1,30 @@ +<?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\textreparser\plugins; + +class forum_description extends \phpbb\textreparser\row_based_plugin +{ +	/** +	* {@inheritdoc} +	*/ +	public function get_columns() +	{ +		return array( +			'id'         => 'forum_id', +			'text'       => 'forum_desc', +			'bbcode_uid' => 'forum_desc_uid', +			'options'    => 'forum_desc_options', +		); +	} +} diff --git a/phpBB/phpbb/textreparser/plugins/forum_rules.php b/phpBB/phpbb/textreparser/plugins/forum_rules.php new file mode 100644 index 0000000000..d131d00707 --- /dev/null +++ b/phpBB/phpbb/textreparser/plugins/forum_rules.php @@ -0,0 +1,30 @@ +<?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\textreparser\plugins; + +class forum_rules extends \phpbb\textreparser\row_based_plugin +{ +	/** +	* {@inheritdoc} +	*/ +	public function get_columns() +	{ +		return array( +			'id'         => 'forum_id', +			'text'       => 'forum_rules', +			'bbcode_uid' => 'forum_rules_uid', +			'options'    => 'forum_rules_options', +		); +	} +} diff --git a/phpBB/phpbb/textreparser/plugins/group_description.php b/phpBB/phpbb/textreparser/plugins/group_description.php new file mode 100644 index 0000000000..2c45c00474 --- /dev/null +++ b/phpBB/phpbb/textreparser/plugins/group_description.php @@ -0,0 +1,30 @@ +<?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\textreparser\plugins; + +class group_description extends \phpbb\textreparser\row_based_plugin +{ +	/** +	* {@inheritdoc} +	*/ +	public function get_columns() +	{ +		return array( +			'id'         => 'group_id', +			'text'       => 'group_desc', +			'bbcode_uid' => 'group_desc_uid', +			'options'    => 'group_desc_options', +		); +	} +} diff --git a/phpBB/phpbb/textreparser/plugins/pm_text.php b/phpBB/phpbb/textreparser/plugins/pm_text.php new file mode 100644 index 0000000000..867da624ee --- /dev/null +++ b/phpBB/phpbb/textreparser/plugins/pm_text.php @@ -0,0 +1,32 @@ +<?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\textreparser\plugins; + +class pm_text extends \phpbb\textreparser\row_based_plugin +{ +	/** +	* {@inheritdoc} +	*/ +	public function get_columns() +	{ +		return array( +			'id'               => 'msg_id', +			'enable_bbcode'    => 'enable_bbcode', +			'enable_smilies'   => 'enable_smilies', +			'enable_magic_url' => 'enable_magic_url', +			'text'             => 'message_text', +			'bbcode_uid'       => 'bbcode_uid', +		); +	} +} diff --git a/phpBB/phpbb/textreparser/plugins/poll_option.php b/phpBB/phpbb/textreparser/plugins/poll_option.php new file mode 100644 index 0000000000..44cacfae62 --- /dev/null +++ b/phpBB/phpbb/textreparser/plugins/poll_option.php @@ -0,0 +1,74 @@ +<?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\textreparser\plugins; + +class poll_option extends \phpbb\textreparser\base +{ +	/** +	* @var \phpbb\db\driver\driver_interface +	*/ +	protected $db; + +	/** +	* Constructor +	* +	* @param \phpbb\db\driver\driver_interface $db Database connection +	*/ +	public function __construct(\phpbb\db\driver\driver_interface $db) +	{ +		$this->db = $db; +	} + +	/** +	* {@inheritdoc} +	*/ +	public function get_max_id() +	{ +		$sql = 'SELECT MAX(topic_id) AS max_id FROM ' . POLL_OPTIONS_TABLE; +		$result = $this->db->sql_query($sql); +		$max_id = (int) $this->db->sql_fetchfield('max_id'); +		$this->db->sql_freeresult($result); + +		return $max_id; +	} + +	/** +	* {@inheritdoc} +	*/ +	protected function get_records_by_range($min_id, $max_id) +	{ +		$sql = 'SELECT o.topic_id, o.poll_option_id, o.poll_option_text AS text, p.enable_bbcode, p.enable_smilies, p.enable_magic_url, p.bbcode_uid +			FROM ' . POLL_OPTIONS_TABLE . ' o, ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . ' p +			WHERE o.topic_id BETWEEN ' . $min_id . ' AND ' . $max_id .' +				AND t.topic_id = o.topic_id +				AND p.post_id = t.topic_first_post_id'; +		$result = $this->db->sql_query($sql); +		$records = $this->db->sql_fetchrowset($result); +		$this->db->sql_freeresult($result); + +		return $records; +	} + +	/** +	* {@inheritdoc} +	*/ +	protected function save_record(array $record) +	{ +		$sql = 'UPDATE ' . POLL_OPTIONS_TABLE . " +			SET poll_option_text = '" . $this->db->sql_escape($record['text']) . "' +			WHERE topic_id = " . $record['topic_id'] . ' +				AND poll_option_id = ' . $record['poll_option_id']; +		$this->db->sql_query($sql); +	} +} diff --git a/phpBB/phpbb/textreparser/plugins/poll_title.php b/phpBB/phpbb/textreparser/plugins/poll_title.php new file mode 100644 index 0000000000..76d30655c9 --- /dev/null +++ b/phpBB/phpbb/textreparser/plugins/poll_title.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\textreparser\plugins; + +class poll_title extends \phpbb\textreparser\row_based_plugin +{ +	/** +	* {@inheritdoc} +	*/ +	public function get_columns() +	{ +		return array( +			'id'   => 'topic_id', +			'text' => 'poll_title', +		); +	} + +	/** +	* {@inheritdoc} +	*/ +	protected function get_records_by_range_query($min_id, $max_id) +	{ +		$sql = 'SELECT t.topic_id AS id, t.poll_title AS text, p.enable_bbcode, p.enable_smilies, p.enable_magic_url, p.bbcode_uid +			FROM ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . ' p +			WHERE t.topic_id BETWEEN ' . $min_id . ' AND ' . $max_id .' +				AND t.poll_max_options > 0 +				AND p.post_id = t.topic_first_post_id'; + +		return $sql; +	} +} diff --git a/phpBB/phpbb/textreparser/plugins/post_text.php b/phpBB/phpbb/textreparser/plugins/post_text.php new file mode 100644 index 0000000000..1c98e86067 --- /dev/null +++ b/phpBB/phpbb/textreparser/plugins/post_text.php @@ -0,0 +1,32 @@ +<?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\textreparser\plugins; + +class post_text extends \phpbb\textreparser\row_based_plugin +{ +	/** +	* {@inheritdoc} +	*/ +	public function get_columns() +	{ +		return array( +			'id'               => 'post_id', +			'enable_bbcode'    => 'enable_bbcode', +			'enable_smilies'   => 'enable_smilies', +			'enable_magic_url' => 'enable_magic_url', +			'text'             => 'post_text', +			'bbcode_uid'       => 'bbcode_uid', +		); +	} +} diff --git a/phpBB/phpbb/textreparser/plugins/user_signature.php b/phpBB/phpbb/textreparser/plugins/user_signature.php new file mode 100644 index 0000000000..647d3a7b14 --- /dev/null +++ b/phpBB/phpbb/textreparser/plugins/user_signature.php @@ -0,0 +1,65 @@ +<?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\textreparser\plugins; + +class user_signature extends \phpbb\textreparser\row_based_plugin +{ +	/** +	* @var array Bit numbers used for user options +	* @see \phpbb\user +	*/ +	protected $keyoptions; + +	/** +	* {@inheritdoc} +	*/ +	protected function add_missing_fields(array $row) +	{ +		if (!isset($this->keyoptions)) +		{ +			$this->save_keyoptions(); +		} + +		$options = $row['user_options']; +		$row += array( +			'enable_bbcode'    => phpbb_optionget($this->keyoptions['sig_bbcode'], $options), +			'enable_smilies'   => phpbb_optionget($this->keyoptions['sig_smilies'], $options), +			'enable_magic_url' => phpbb_optionget($this->keyoptions['sig_links'], $options), +		); + +		return parent::add_missing_fields($row); +	} + +	/** +	* {@inheritdoc} +	*/ +	public function get_columns() +	{ +		return array( +			'id'           => 'user_id', +			'text'         => 'user_sig', +			'bbcode_uid'   => 'user_sig_bbcode_uid', +			'user_options' => 'user_options', +		); +	} + +	/** +	* Save the keyoptions var from \phpbb\user +	*/ +	protected function save_keyoptions() +	{ +		$class_vars = get_class_vars('phpbb\\user'); +		$this->keyoptions = $class_vars['keyoptions']; +	} +} diff --git a/phpBB/phpbb/textreparser/reparser_interface.php b/phpBB/phpbb/textreparser/reparser_interface.php new file mode 100644 index 0000000000..9ea1732870 --- /dev/null +++ b/phpBB/phpbb/textreparser/reparser_interface.php @@ -0,0 +1,32 @@ +<?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\textreparser; + +interface reparser_interface +{ +	/** +	* Return the highest ID for all existing records +	* +	* @return integer +	*/ +	public function get_max_id(); + +	/** +	* Reparse all records in given range +	* +	* @param integer $min_id Lower bound +	* @param integer $max_id Upper bound +	*/ +	public function reparse_range($min_id, $max_id); +} diff --git a/phpBB/phpbb/textreparser/row_based_plugin.php b/phpBB/phpbb/textreparser/row_based_plugin.php new file mode 100644 index 0000000000..2d32104493 --- /dev/null +++ b/phpBB/phpbb/textreparser/row_based_plugin.php @@ -0,0 +1,117 @@ +<?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\textreparser; + +abstract class row_based_plugin extends base +{ +	/** +	* @var \phpbb\db\driver\driver_interface +	*/ +	protected $db; + +	/** +	 * @var string +	 */ +	protected $table; + +	/** +	* Constructor +	* +	* @param \phpbb\db\driver\driver_interface $db Database connection +	* @param string $table +	*/ +	public function __construct(\phpbb\db\driver\driver_interface $db, $table) +	{ +		$this->db = $db; +		$this->table = $table; +	} + +	/** +	* Return the name of the column that correspond to each field +	* +	* @return array +	*/ +	abstract public function get_columns(); + +	/** +	* {@inheritdoc} +	*/ +	public function get_max_id() +	{ +		$columns = $this->get_columns(); + +		$sql = 'SELECT MAX(' . $columns['id'] . ') AS max_id FROM ' . $this->table; +		$result = $this->db->sql_query($sql); +		$max_id = (int) $this->db->sql_fetchfield('max_id'); +		$this->db->sql_freeresult($result); + +		return $max_id; +	} + +	/** +	* {@inheritdoc} +	*/ +	protected function get_records_by_range($min_id, $max_id) +	{ +		$sql = $this->get_records_by_range_query($min_id, $max_id); +		$result = $this->db->sql_query($sql); +		$records = $this->db->sql_fetchrowset($result); +		$this->db->sql_freeresult($result); + +		return $records; +	} + +	/** +	* Generate the query that retrieves all records for given range +	* +	* @param  integer $min_id Lower bound +	* @param  integer $max_id Upper bound +	* @return string          SQL query +	*/ +	protected function get_records_by_range_query($min_id, $max_id) +	{ +		$columns = $this->get_columns(); +		$fields  = array(); +		foreach ($columns as $field_name => $column_name) +		{ +			if ($column_name === $field_name) +			{ +				$fields[] = $column_name; +			} +			else +			{ +				$fields[] = $column_name . ' AS ' . $field_name; +			} +		} + +		$sql = 'SELECT ' . implode(', ', $fields) . ' +			FROM ' . $this->table . ' +			WHERE ' . $columns['id'] . ' BETWEEN ' . $min_id . ' AND ' . $max_id; + +		return $sql; +	} + +	/** +	* {@inheritdoc} +	*/ +	protected function save_record(array $record) +	{ +		$columns = $this->get_columns(); + +		$sql = 'UPDATE ' . $this->table . ' +			SET ' . $columns['text'] . " = '" . $this->db->sql_escape($record['text']) . "' +			WHERE " . $columns['id'] . ' = ' . $record['id']; +		$this->db->sql_query($sql); +	} +} diff --git a/phpBB/phpbb/tree/nestedset.php b/phpBB/phpbb/tree/nestedset.php index 8490c7c299..7149513fd9 100644 --- a/phpBB/phpbb/tree/nestedset.php +++ b/phpBB/phpbb/tree/nestedset.php @@ -391,7 +391,6 @@ abstract class nestedset implements \phpbb\tree\tree_interface  			throw new \OutOfBoundsException($this->message_prefix . 'INVALID_PARENT');  		} -		$diff = sizeof($move_items) * 2;  		$sql_exclude_moved_items = $this->db->sql_in_set($this->column_item_id, $move_items, true);  		$this->db->sql_transaction('begin'); @@ -490,7 +489,6 @@ abstract class nestedset implements \phpbb\tree\tree_interface  			throw new \OutOfBoundsException($this->message_prefix . 'INVALID_PARENT');  		} -		$diff = sizeof($move_items) * 2;  		$sql_exclude_moved_items = $this->db->sql_in_set($this->column_item_id, $move_items, true);  		$this->db->sql_transaction('begin'); diff --git a/phpBB/phpbb/user.php b/phpBB/phpbb/user.php index faedd79703..305510851c 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 +	* @param string						$datetime_class	Class name of datetime class  	*/ -	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); @@ -401,6 +427,8 @@ class user extends \phpbb\session  			}  		} +		$this->is_setup_flag = true; +  		return;  	} @@ -414,103 +442,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);  	}  	/** @@ -520,24 +458,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: @@ -548,11 +484,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) @@ -563,6 +502,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') @@ -571,7 +511,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  				{ @@ -582,8 +522,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);  	}  	/** @@ -593,6 +562,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)  	{ @@ -605,109 +578,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) === 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 @@ -816,7 +686,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>'; @@ -877,8 +747,6 @@ class user extends \phpbb\session  	*/  	function leave_newly_registered()  	{ -		global $db; -  		if (empty($this->data['user_new']))  		{  			return false; diff --git a/phpBB/phpbb/user_loader.php b/phpBB/phpbb/user_loader.php index 967d96d73a..cdd28329db 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() @@ -189,7 +189,14 @@ class user_loader  			return '';  		} -		return phpbb_get_avatar(\phpbb\avatar\manager::clean_row($user, 'user'), 'USER_AVATAR', false, $lazy); +		$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 phpbb_get_avatar($row, 'USER_AVATAR', false, $lazy);  	}  	/** 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;  	}  | 
