diff options
Diffstat (limited to 'phpBB/phpbb/event')
| -rw-r--r-- | phpBB/phpbb/event/data.php | 66 | ||||
| -rw-r--r-- | phpBB/phpbb/event/dispatcher.php | 78 | ||||
| -rw-r--r-- | phpBB/phpbb/event/dispatcher_interface.php | 50 | ||||
| -rw-r--r-- | phpBB/phpbb/event/kernel_exception_subscriber.php | 115 | ||||
| -rw-r--r-- | phpBB/phpbb/event/kernel_request_subscriber.php | 82 | ||||
| -rw-r--r-- | phpBB/phpbb/event/kernel_terminate_subscriber.php | 41 | ||||
| -rw-r--r-- | phpBB/phpbb/event/md_exporter.php | 440 | ||||
| -rw-r--r-- | phpBB/phpbb/event/php_exporter.php | 610 | ||||
| -rw-r--r-- | phpBB/phpbb/event/recursive_event_filter_iterator.php | 71 | 
9 files changed, 1553 insertions, 0 deletions
| diff --git a/phpBB/phpbb/event/data.php b/phpBB/phpbb/event/data.php new file mode 100644 index 0000000000..c7365aee35 --- /dev/null +++ b/phpBB/phpbb/event/data.php @@ -0,0 +1,66 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license 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\Event; + +class data extends Event implements \ArrayAccess +{ +	private $data; + +	public function __construct(array $data = array()) +	{ +		$this->set_data($data); +	} + +	public function set_data(array $data = array()) +	{ +		$this->data = $data; +	} + +	public function get_data() +	{ +		return $this->data; +	} + +	/** +	 * Returns data filtered to only include specified keys. +	 * +	 * This effectively discards any keys added to data by hooks. +	 */ +	public function get_data_filtered($keys) +	{ +		return array_intersect_key($this->data, array_flip($keys)); +	} + +	public function offsetExists($offset) +	{ +		return isset($this->data[$offset]); +	} + +	public function offsetGet($offset) +	{ +		return isset($this->data[$offset]) ? $this->data[$offset] : null; +	} + +	public function offsetSet($offset, $value) +	{ +		$this->data[$offset] = $value; +	} + +	public function offsetUnset($offset) +	{ +		unset($this->data[$offset]); +	} +} diff --git a/phpBB/phpbb/event/dispatcher.php b/phpBB/phpbb/event/dispatcher.php new file mode 100644 index 0000000000..1c4abeb108 --- /dev/null +++ b/phpBB/phpbb/event/dispatcher.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\event; + +use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher; +use Symfony\Component\EventDispatcher\Event; + +/** +* Extension of the Symfony2 EventDispatcher +* +* It provides an additional `trigger_event` method, which +* gives some syntactic sugar for dispatching events. Instead +* of creating the event object, the method will do that for +* you. +* +* Example: +* +*     $vars = array('page_title'); +*     extract($phpbb_dispatcher->trigger_event('core.index', compact($vars))); +* +*/ +class dispatcher extends ContainerAwareEventDispatcher implements dispatcher_interface +{ +	/** +	 * @var bool +	 */ +	protected $disabled = false; + +	/** +	* {@inheritdoc} +	*/ +	public function trigger_event($eventName, $data = array()) +	{ +		$event = new \phpbb\event\data($data); +		$this->dispatch($eventName, $event); +		return $event->get_data_filtered(array_keys($data)); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function dispatch($eventName, Event $event = null) +	{ +		if ($this->disabled) +		{ +			return $event; +		} + +		return parent::dispatch($eventName, $event); +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function disable() +	{ +		$this->disabled = true; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function enable() +	{ +		$this->disabled = false; +	} +} diff --git a/phpBB/phpbb/event/dispatcher_interface.php b/phpBB/phpbb/event/dispatcher_interface.php new file mode 100644 index 0000000000..c66aa98260 --- /dev/null +++ b/phpBB/phpbb/event/dispatcher_interface.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\event; + +/** +* Extension of the Symfony2 EventDispatcher +* +* It provides an additional `trigger_event` method, which +* gives some syntactic sugar for dispatching events. Instead +* of creating the event object, the method will do that for +* you. +* +* Example: +* +* $vars = array('page_title'); +* extract($phpbb_dispatcher->trigger_event('core.index', compact($vars))); +* +*/ +interface dispatcher_interface extends \Symfony\Component\EventDispatcher\EventDispatcherInterface +{ +	/** +	* Construct and dispatch an event +	* +	* @param string $eventName	The event name +	* @param array $data		An array containing the variables sending with the event +	* @return mixed +	*/ +	public function trigger_event($eventName, $data = array()); + +	/** +	 * Disable the event dispatcher. +	 */ +	public function disable(); + +	/** +	 * Enable the event dispatcher. +	 */ +	public function enable(); +} diff --git a/phpBB/phpbb/event/kernel_exception_subscriber.php b/phpBB/phpbb/event/kernel_exception_subscriber.php new file mode 100644 index 0000000000..eb7831ad34 --- /dev/null +++ b/phpBB/phpbb/event/kernel_exception_subscriber.php @@ -0,0 +1,115 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license 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\HttpFoundation\JsonResponse; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpFoundation\Response; + +class kernel_exception_subscriber implements EventSubscriberInterface +{ +	/** +	* Template object +	* @var \phpbb\template\template +	*/ +	protected $template; + +	/** +	* User object +	* @var \phpbb\user +	*/ +	protected $user; + +	/** +	* Construct method +	* +	* @param \phpbb\template\template $template Template object +	* @param \phpbb\user $user User object +	*/ +	public function __construct(\phpbb\template\template $template, \phpbb\user $user) +	{ +		$this->template = $template; +		$this->user = $user; +	} + +	/** +	* This listener is run when the KernelEvents::EXCEPTION event is triggered +	* +	* @param GetResponseForExceptionEvent $event +	* @return null +	*/ +	public function on_kernel_exception(GetResponseForExceptionEvent $event) +	{ +		$exception = $event->getException(); + +		$message = $exception->getMessage(); + +		if ($exception instanceof \phpbb\exception\exception_interface) +		{ +			$message = call_user_func_array(array($this->user, 'lang'), array_merge(array($message), $exception->get_parameters())); +		} + +		if (!$event->getRequest()->isXmlHttpRequest()) +		{ +			page_header($this->user->lang('INFORMATION')); + +			$this->template->assign_vars(array( +				'MESSAGE_TITLE' => $this->user->lang('INFORMATION'), +				'MESSAGE_TEXT'  => $message, +			)); + +			$this->template->set_filenames(array( +				'body' => 'message_body.html', +			)); + +			page_footer(true, false, false); + +			$response = new Response($this->template->assign_display('body'), 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); +	} + +	public static 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 new file mode 100644 index 0000000000..ee9f29a59d --- /dev/null +++ b/phpBB/phpbb/event/kernel_request_subscriber.php @@ -0,0 +1,82 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license 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 new file mode 100644 index 0000000000..3a709f73fd --- /dev/null +++ b/phpBB/phpbb/event/kernel_terminate_subscriber.php @@ -0,0 +1,41 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license 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\PostResponseEvent; + +class kernel_terminate_subscriber implements EventSubscriberInterface +{ +	/** +	* This listener is run when the KernelEvents::TERMINATE event is triggered +	* This comes after a Response has been sent to the server; this is +	* primarily cleanup stuff. +	* +	* @param PostResponseEvent $event +	* @return null +	*/ +	public function on_kernel_terminate(PostResponseEvent $event) +	{ +		exit_handler(); +	} + +	public static 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 new file mode 100644 index 0000000000..f7021875f3 --- /dev/null +++ b/phpBB/phpbb/event/md_exporter.php @@ -0,0 +1,440 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license 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; + +/** +* Crawls through a markdown file and grabs all events +*/ +class md_exporter +{ +	/** @var string Path where we look for files*/ +	protected $path; + +	/** @var string phpBB Root Path */ +	protected $root_path; + +	/** @var string */ +	protected $filter; + +	/** @var string */ +	protected $current_event; + +	/** @var array */ +	protected $events; + +	/** +	* @param string $phpbb_root_path +	* @param mixed $extension	String 'vendor/ext' to filter, null for phpBB core +	*/ +	public function __construct($phpbb_root_path, $extension = null) +	{ +		$this->root_path = $phpbb_root_path; +		$this->path = $this->root_path; +		if ($extension) +		{ +			$this->path .= 'ext/' . $extension . '/'; +		} + +		$this->events = array(); +		$this->events_by_file = array(); +		$this->filter = $this->current_event = ''; +	} + +	/** +	* Get the list of all events +	* +	* @return array		Array with events: name => details +	*/ +	public function get_events() +	{ +		return $this->events; +	} + +	/** +	* @param string $md_file	Relative from phpBB root +	* @return int		Number of events found +	* @throws \LogicException +	*/ +	public function crawl_phpbb_directory_adm($md_file) +	{ +		$this->crawl_eventsmd($md_file, 'adm'); + +		$file_list = $this->get_recursive_file_list($this->path  . 'adm/style/'); +		foreach ($file_list as $file) +		{ +			$file_name = 'adm/style/' . $file; +			$this->validate_events_from_file($file_name, $this->crawl_file_for_events($file_name)); +		} + +		return sizeof($this->events); +	} + +	/** +	* @param string $md_file	Relative from phpBB root +	* @return int		Number of events found +	* @throws \LogicException +	*/ +	public function crawl_phpbb_directory_styles($md_file) +	{ +		$this->crawl_eventsmd($md_file, 'styles'); + +		$styles = array('prosilver', 'subsilver2'); +		foreach ($styles as $style) +		{ +			$file_list = $this->get_recursive_file_list( +				$this->path . 'styles/' . $style . '/template/' +			); + +			foreach ($file_list as $file) +			{ +				$file_name = 'styles/' . $style . '/template/' . $file; +				$this->validate_events_from_file($file_name, $this->crawl_file_for_events($file_name)); +			} +		} + +		return sizeof($this->events); +	} + +	/** +	* @param string $md_file	Relative from phpBB root +	* @param string $filter		Should be 'styles' or 'adm' +	* @return int		Number of events found +	* @throws \LogicException +	*/ +	public function crawl_eventsmd($md_file, $filter) +	{ +		if (!file_exists($this->path . $md_file)) +		{ +			throw new \LogicException("The event docs file '{$md_file}' could not be found"); +		} + +		$file_content = file_get_contents($this->path . $md_file); +		$this->filter = $filter; + +		$events = explode("\n\n", $file_content); +		foreach ($events as $event) +		{ +			// Last row of the file +			if (strpos($event, "\n===\n") === false) +			{ +				continue; +			} + +			list($event_name, $details) = explode("\n===\n", $event, 2); +			$this->validate_event_name($event_name); +			$this->current_event = $event_name; + +			if (isset($this->events[$this->current_event])) +			{ +				throw new \LogicException("The event '{$this->current_event}' is defined multiple times"); +			} + +			if (($this->filter == 'adm' && strpos($this->current_event, 'acp_') !== 0) +				|| ($this->filter == 'styles' && strpos($this->current_event, 'acp_') === 0)) +			{ +				continue; +			} + +			list($file_details, $details) = explode("\n* Since: ", $details, 2); +			list($since, $description) = explode("\n* Purpose: ", $details, 2); + +			$files = $this->validate_file_list($file_details); +			$since = $this->validate_since($since); + +			$this->events[$event_name] = array( +				'event'			=> $this->current_event, +				'files'			=> $files, +				'since'			=> $since, +				'description'	=> $description, +			); +		} + +		return sizeof($this->events); +	} + +	/** +	* Format the php events as a wiki table +	* @return string		Number of events found +	*/ +	public function export_events_for_wiki() +	{ +		if ($this->filter === 'adm') +		{ +			$wiki_page = '= ACP Template Events =' . "\n"; +			$wiki_page .= '{| class="zebra sortable" cellspacing="0" cellpadding="5"' . "\n"; +			$wiki_page .= '! Identifier !! Placement !! Added in Release !! Explanation' . "\n"; +		} +		else +		{ +			$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"; +		} + +		foreach ($this->events as $event_name => $event) +		{ +			$wiki_page .= "|- id=\"{$event_name}\"\n"; +			$wiki_page .= "| [[#{$event_name}|{$event_name}]] || "; + +			if ($this->filter === 'adm') +			{ +				$wiki_page .= implode(', ', $event['files']['adm']); +			} +			else +			{ +				$wiki_page .= implode(', ', $event['files']['prosilver']) . ' || ' . implode(', ', $event['files']['subsilver2']); +			} + +			$wiki_page .= " || {$event['since']} || " . str_replace("\n", ' ', $event['description']) . "\n"; +		} +		$wiki_page .= '|}' . "\n"; + +		return $wiki_page; +	} + +	/** +	* Validates a template event name +	* +	* @param $event_name +	* @return null +	* @throws \LogicException +	*/ +	public function validate_event_name($event_name) +	{ +		if (!preg_match('#^([a-z][a-z0-9]*(?:_[a-z][a-z0-9]*)+)$#', $event_name)) +		{ +			throw new \LogicException("Invalid event name '{$event_name}'"); +		} +	} + +	/** +	* Validate "Since" Information +	* +	* @param string $since +	* @return string +	* @throws \LogicException +	*/ +	public function validate_since($since) +	{ +		if (!preg_match('#^\d+\.\d+\.\d+(?:-(?:a|b|RC|pl)\d+)?$#', $since)) +		{ +			throw new \LogicException("Invalid since information found for event '{$this->current_event}'"); +		} + +		return $since; +	} + +	/** +	* Validate the files list +	* +	* @param string $file_details +	* @return array +	* @throws \LogicException +	*/ +	public function validate_file_list($file_details) +	{ +		$files_list = array( +			'prosilver'		=> array(), +			'subsilver2'	=> array(), +			'adm'			=> array(), +		); + +		// Multi file list +		if (strpos($file_details, "* Locations:\n    + ") === 0) +		{ +			$file_details = substr($file_details, strlen("* Locations:\n    + ")); +			$files = explode("\n    + ", $file_details); +			foreach ($files as $file) +			{ +				if (!file_exists($this->path . $file) || substr($file, -5) !== '.html') +				{ +					throw new \LogicException("Invalid file '{$file}' not found for event '{$this->current_event}'", 1); +				} + +				if (($this->filter !== 'adm') && strpos($file, 'styles/prosilver/template/') === 0) +				{ +					$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/')); +				} +				else +				{ +					throw new \LogicException("Invalid file '{$file}' not found for event '{$this->current_event}'", 2); +				} + +				$this->events_by_file[$file][] = $this->current_event; +			} +		} +		else if ($this->filter == 'adm') +		{ +			$file = substr($file_details, strlen('* Location: ')); +			if (!file_exists($this->path . $file) || substr($file, -5) !== '.html') +			{ +				throw new \LogicException("Invalid file '{$file}' not found for event '{$this->current_event}'", 1); +			} + +			$files_list['adm'][] =  substr($file, strlen('adm/style/')); + +			$this->events_by_file[$file][] = $this->current_event; +		} +		else +		{ +			throw new \LogicException("Invalid file list found for event '{$this->current_event}'", 2); +		} + +		return $files_list; +	} + +	/** +	* Get all template events in a template file +	* +	* @param string $file +	* @return array +	* @throws \LogicException +	*/ +	public function crawl_file_for_events($file) +	{ +		if (!file_exists($this->path . $file)) +		{ +			throw new \LogicException("File '{$file}' does not exist", 1); +		} + +		$event_list = array(); +		$file_content = file_get_contents($this->path . $file); + +		$events = explode('<!-- EVENT ', $file_content); +		// Remove the code before the first event +		array_shift($events); +		foreach ($events as $event) +		{ +			$event = explode(' -->', $event, 2); +			$event_list[] = array_shift($event); +		} + +		return $event_list; +	} + +	/** +	* Validates whether all events from $file are in the md file and vice-versa +	* +	* @param string $file +	* @param array $events +	* @return true +	* @throws \LogicException +	*/ +	public function validate_events_from_file($file, array $events) +	{ +		if (empty($this->events_by_file[$file]) && empty($events)) +		{ +			return true; +		} +		else if (empty($this->events_by_file[$file])) +		{ +			$event_list = implode("', '", $events); +			throw new \LogicException("File '{$file}' should not contain events, but contains: " +				. "'{$event_list}'", 1); +		} +		else if (empty($events)) +		{ +			$event_list = implode("', '", $this->events_by_file[$file]); +			throw new \LogicException("File '{$file}' contains no events, but should contain: " +				. "'{$event_list}'", 1); +		} + +		$missing_events_from_file = array(); +		foreach ($this->events_by_file[$file] as $event) +		{ +			if (!in_array($event, $events)) +			{ +				$missing_events_from_file[] = $event; +			} +		} + +		if (!empty($missing_events_from_file)) +		{ +			$event_list = implode("', '", $missing_events_from_file); +			throw new \LogicException("File '{$file}' does not contain events: '{$event_list}'", 2); +		} + +		$missing_events_from_md = array(); +		foreach ($events as $event) +		{ +			if (!in_array($event, $this->events_by_file[$file])) +			{ +				$missing_events_from_md[] = $event; +			} +		} + +		if (!empty($missing_events_from_md)) +		{ +			$event_list = implode("', '", $missing_events_from_md); +			throw new \LogicException("File '{$file}' contains additional events: '{$event_list}'", 3); +		} + +		return true; +	} + +	/** +	* Returns a list of files in $dir +	* +	* Works recursive with any depth +	* +	* @param	string	$dir	Directory to go through +	* @return	array	List of files (including directories) +	*/ +	public function get_recursive_file_list($dir) +	{ +		try +		{ +			$iterator = new \RecursiveIteratorIterator( +				new \phpbb\recursive_dot_prefix_filter_iterator( +					new \RecursiveDirectoryIterator( +						$dir, +						\FilesystemIterator::SKIP_DOTS +					) +				), +				\RecursiveIteratorIterator::SELF_FIRST +			); +		} +		catch (\Exception $e) +		{ +			return array(); +		} + +		$files = array(); +		foreach ($iterator as $file_info) +		{ +			/** @var \RecursiveDirectoryIterator $file_info */ +			if ($file_info->isDir()) +			{ +				continue; +			} + +			$relative_path = $iterator->getInnerIterator()->getSubPathname(); + +			if (substr($relative_path, -5) == '.html') +			{ +				$files[] = str_replace(DIRECTORY_SEPARATOR, '/', $relative_path); +			} +		} + +		return $files; +	} +} diff --git a/phpBB/phpbb/event/php_exporter.php b/phpBB/phpbb/event/php_exporter.php new file mode 100644 index 0000000000..35144eeeec --- /dev/null +++ b/phpBB/phpbb/event/php_exporter.php @@ -0,0 +1,610 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license 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; + +/** +* Class php_exporter +* Crawls through a list of files and grabs all php-events +*/ +class php_exporter +{ +	/** @var string Path where we look for files*/ +	protected $path; + +	/** @var string phpBB Root Path */ +	protected $root_path; + +	/** @var string */ +	protected $current_file; + +	/** @var string */ +	protected $current_event; + +	/** @var int */ +	protected $current_event_line; + +	/** @var array */ +	protected $events; + +	/** @var array */ +	protected $file_lines; + +	/** +	* @param string $phpbb_root_path +	* @param mixed $extension	String 'vendor/ext' to filter, null for phpBB core +	*/ +	public function __construct($phpbb_root_path, $extension = null) +	{ +		$this->root_path = $phpbb_root_path; +		$this->path = $phpbb_root_path; +		$this->events = $this->file_lines = array(); +		$this->current_file = $this->current_event = ''; +		$this->current_event_line = 0; + +		$this->path = $this->root_path; +		if ($extension) +		{ +			$this->path .= 'ext/' . $extension . '/'; +		} +	} + +	/** +	* Get the list of all events +	* +	* @return array		Array with events: name => details +	*/ +	public function get_events() +	{ +		return $this->events; +	} + +	/** +	* Set current event data +	* +	* @param string	$name	Name of the current event (used for error messages) +	* @param int	$line	Line where the current event is placed in +	* @return null +	*/ +	public function set_current_event($name, $line) +	{ +		$this->current_event = $name; +		$this->current_event_line = $line; +	} + +	/** +	* Set the content of this file +	* +	* @param array $content		Array with the lines of the file +	* @return null +	*/ +	public function set_content($content) +	{ +		$this->file_lines = $content; +	} + +	/** +	* Crawl the phpBB/ directory for php events +	* @return int	The number of events found +	*/ +	public function crawl_phpbb_directory_php() +	{ +		$files = $this->get_recursive_file_list(); +		$this->events = array(); +		foreach ($files as $file) +		{ +			$this->crawl_php_file($file); +		} +		ksort($this->events); + +		return sizeof($this->events); +	} + +	/** +	* Returns a list of files in $dir +	* +	* @return	array	List of files (including the path) +	*/ +	public function get_recursive_file_list() +	{ +		try +		{ +			$iterator = new \RecursiveIteratorIterator( +				new \phpbb\event\recursive_event_filter_iterator( +					new \RecursiveDirectoryIterator( +						$this->path, +						\FilesystemIterator::SKIP_DOTS +					), +					$this->path +				), +				\RecursiveIteratorIterator::LEAVES_ONLY +			); +		} +		catch (\Exception $e) +		{ +			return array(); +		} + +		$files = array(); +		foreach ($iterator as $file_info) +		{ +			/** @var \RecursiveDirectoryIterator $file_info */ +			$relative_path = $iterator->getInnerIterator()->getSubPathname(); +			$files[] = str_replace(DIRECTORY_SEPARATOR, '/', $relative_path); +		} + +		return $files; +	} + +	/** +	* Format the php events as a wiki table +	* @return string +	*/ +	public function export_events_for_wiki() +	{ +		$wiki_page = '= PHP Events (Hook Locations) =' . "\n"; +		$wiki_page .= '{| class="sortable zebra" cellspacing="0" cellpadding="5"' . "\n"; +		$wiki_page .= '! Identifier !! Placement !! Arguments !! Added in Release !! Explanation' . "\n"; +		foreach ($this->events as $event) +		{ +			$wiki_page .= '|- id="' . $event['event'] . '"' . "\n"; +			$wiki_page .= '| [[#' . $event['event'] . '|' . $event['event'] . ']] || ' . $event['file'] . ' || ' . implode(', ', $event['arguments']) . ' || ' . $event['since'] . ' || ' . $event['description'] . "\n"; +		} +		$wiki_page .= '|}' . "\n"; + +		return $wiki_page; +	} + +	/** +	* @param string $file +	* @return int Number of events found in this file +	* @throws \LogicException +	*/ +	public function crawl_php_file($file) +	{ +		$this->current_file = $file; +		$this->file_lines = array(); +		$content = file_get_contents($this->path . $this->current_file); +		$num_events_found = 0; + +		if (strpos($content, "dispatcher->trigger_event('") || strpos($content, "dispatcher->dispatch('")) +		{ +			$this->set_content(explode("\n", $content)); +			for ($i = 0, $num_lines = sizeof($this->file_lines); $i < $num_lines; $i++) +			{ +				$event_line = false; +				$found_trigger_event = strpos($this->file_lines[$i], "dispatcher->trigger_event('"); +				$arguments = array(); +				if ($found_trigger_event !== false) +				{ +					$event_line = $i; +					$this->set_current_event($this->get_event_name($event_line, false), $event_line); + +					// Find variables of the event +					$arguments = $this->get_vars_from_array(); +					$doc_vars = $this->get_vars_from_docblock(); +					$this->validate_vars_docblock_array($arguments, $doc_vars); +				} +				else +				{ +					$found_dispatch = strpos($this->file_lines[$i], "dispatcher->dispatch('"); +					if ($found_dispatch !== false) +					{ +						$event_line = $i; +						$this->set_current_event($this->get_event_name($event_line, true), $event_line); +					} +				} + +				if ($event_line) +				{ +					// Validate @event +					$event_line_num = $this->find_event(); +					$this->validate_event($this->current_event, $this->file_lines[$event_line_num]); + +					// Validate @since +					$since_line_num = $this->find_since(); +					$since = $this->validate_since($this->file_lines[$since_line_num]); + +					// Find event description line +					$description_line_num = $this->find_description(); +					$description = substr(trim($this->file_lines[$description_line_num]), strlen('* ')); + +					if (isset($this->events[$this->current_event])) +					{ +						throw new \LogicException("The event '{$this->current_event}' from file " +							. "'{$this->current_file}:{$event_line_num}' already exists in file " +							. "'{$this->events[$this->current_event]['file']}'", 10); +					} + +					sort($arguments); +					$this->events[$this->current_event] = array( +						'event'			=> $this->current_event, +						'file'			=> $this->current_file, +						'arguments'		=> $arguments, +						'since'			=> $since, +						'description'	=> $description, +					); +					$num_events_found++; +				} +			} +		} + +		return $num_events_found; +	} + +	/** +	* Find the name of the event inside the dispatch() line +	* +	* @param int $event_line +	* @param bool $is_dispatch Do we look for dispatch() or trigger_event() ? +	* @return string	Name of the event +	* @throws \LogicException +	*/ +	public function get_event_name($event_line, $is_dispatch) +	{ +		$event_text_line = $this->file_lines[$event_line]; +		$event_text_line = ltrim($event_text_line, "\t "); + +		if ($is_dispatch) +		{ +			$regex = '#\$([a-z](?:[a-z0-9_]|->)*)'; +			$regex .= '->dispatch\('; +			$regex .= '\'' . $this->preg_match_event_name() . '\''; +			$regex .= '\);#'; +		} +		else +		{ +			$regex = '#extract\(\$([a-z](?:[a-z0-9_]|->)*)'; +			$regex .= '->trigger_event\('; +			$regex .= '\'' . $this->preg_match_event_name() . '\''; +			$regex .= ', compact\(\$vars\)\)\);#'; +		} + +		$match = array(); +		preg_match($regex, $event_text_line, $match); +		if (!isset($match[2])) +		{ +			throw new \LogicException("Can not find event name in line '{$event_text_line}' " +				. "in file '{$this->current_file}:{$event_line}'", 1); +		} + +		return $match[2]; +	} + +	/** +	* Returns a regex match for the event name +	* +	* @return string +	*/ +	protected function preg_match_event_name() +	{ +		return '([a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+)'; +	} + +	/** +	* Find the $vars array +	* +	* @return array		List of variables +	* @throws \LogicException +	*/ +	public function get_vars_from_array() +	{ +		$line = ltrim($this->file_lines[$this->current_event_line - 1], "\t"); +		if ($line === ');') +		{ +			$vars_array = $this->get_vars_from_multi_line_array(); +		} +		else +		{ +			$vars_array = $this->get_vars_from_single_line_array($line); +		} + +		foreach ($vars_array as $var) +		{ +			if (!preg_match('#^([a-zA-Z_][a-zA-Z0-9_]*)$#', $var)) +			{ +				throw new \LogicException("Found invalid var '{$var}' in array for event '{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 3); +			} +		} + +		sort($vars_array); +		return $vars_array; +	} + +	/** +	* Find the variables in single line array +	* +	* @param	string	$line +	* @param	bool	$throw_multiline	Throw an exception when there are too +	*										many arguments in one line. +	* @return array		List of variables +	* @throws \LogicException +	*/ +	public function get_vars_from_single_line_array($line, $throw_multiline = true) +	{ +		$match = array(); +		preg_match('#^\$vars = array\(\'([a-zA-Z0-9_\' ,]+)\'\);$#', $line, $match); + +		if (isset($match[1])) +		{ +			$vars_array = explode("', '", $match[1]); +			if ($throw_multiline && sizeof($vars_array) > 6) +			{ +				throw new \LogicException('Should use multiple lines for $vars definition ' +					. "for event '{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 2); +			} +			return $vars_array; +		} +		else +		{ +			throw new \LogicException("Can not find '\$vars = array();'-line for event '{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 1); +		} +	} + +	/** +	* Find the variables in single line array +	* +	* @return array		List of variables +	* @throws \LogicException +	*/ +	public function get_vars_from_multi_line_array() +	{ +		$current_vars_line = 2; +		$var_lines = array(); +		while (ltrim($this->file_lines[$this->current_event_line - $current_vars_line], "\t") !== '$vars = array(') +		{ +			$var_lines[] = substr(trim($this->file_lines[$this->current_event_line - $current_vars_line]), 0, -1); + +			$current_vars_line++; +			if ($current_vars_line > $this->current_event_line) +			{ +				// Reached the start of the file +				throw new \LogicException("Can not find end of \$vars array for event '{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 2); +			} +		} + +		return $this->get_vars_from_single_line_array('$vars = array(' . implode(", ", $var_lines) . ');', false); +	} + +	/** +	* Find the $vars array +	* +	* @return array		List of variables +	* @throws \LogicException +	*/ +	public function get_vars_from_docblock() +	{ +		$doc_vars = array(); +		$current_doc_line = 1; +		$found_comment_end = false; +		while (ltrim($this->file_lines[$this->current_event_line - $current_doc_line], "\t") !== '/**') +		{ +			if (ltrim($this->file_lines[$this->current_event_line - $current_doc_line], "\t ") === '*/') +			{ +				$found_comment_end = true; +			} + +			if ($found_comment_end) +			{ +				$var_line = trim($this->file_lines[$this->current_event_line - $current_doc_line]); +				$var_line = preg_replace('!\s+!', ' ', $var_line); +				if (strpos($var_line, '* @var ') === 0) +				{ +					$doc_line = explode(' ', $var_line, 5); +					if (sizeof($doc_line) !== 5) +					{ +						throw new \LogicException("Found invalid line '{$this->file_lines[$this->current_event_line - $current_doc_line]}' " +						. "for event '{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 1); +					} +					$doc_vars[] = $doc_line[3]; +				} +			} + +			$current_doc_line++; +			if ($current_doc_line > $this->current_event_line) +			{ +				// Reached the start of the file +				throw new \LogicException("Can not find end of docblock for event '{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 2); +			} +		} + +		if (empty($doc_vars)) +		{ +			// Reached the start of the file +			throw new \LogicException("Can not find @var lines for event '{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 3); +		} + +		foreach ($doc_vars as $var) +		{ +			if (!preg_match('#^([a-zA-Z_][a-zA-Z0-9_]*)$#', $var)) +			{ +				throw new \LogicException("Found invalid @var '{$var}' in docblock for event " +					. "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 4); +			} +		} + +		sort($doc_vars); +		return $doc_vars; +	} + +	/** +	* Find the "@since" Information line +	* +	* @return int Absolute line number +	* @throws \LogicException +	*/ +	public function find_since() +	{ +		return $this->find_tag('since', array('event', 'var')); +	} + +	/** +	* Find the "@event" Information line +	* +	* @return int Absolute line number +	*/ +	public function find_event() +	{ +		return $this->find_tag('event', array()); +	} + +	/** +	* Find a "@*" Information line +	* +	* @param string $find_tag		Name of the tag we are trying to find +	* @param array $disallowed_tags		List of tags that must not appear between +	*									the tag and the actual event +	* @return int Absolute line number +	* @throws \LogicException +	*/ +	public function find_tag($find_tag, $disallowed_tags) +	{ +		$find_tag_line = 0; +		$found_comment_end = false; +		while (strpos(ltrim($this->file_lines[$this->current_event_line - $find_tag_line], "\t "), '* @' . $find_tag . ' ') !== 0) +		{ +			if ($found_comment_end && ltrim($this->file_lines[$this->current_event_line - $find_tag_line], "\t") === '/**') +			{ +				// Reached the start of this doc block +				throw new \LogicException("Can not find '@{$find_tag}' information for event " +					. "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 1); +			} + +			foreach ($disallowed_tags as $disallowed_tag) +			{ +				if ($found_comment_end && strpos(ltrim($this->file_lines[$this->current_event_line - $find_tag_line], "\t "), '* @' . $disallowed_tag) === 0) +				{ +					// Found @var after the @since +					throw new \LogicException("Found '@{$disallowed_tag}' information after '@{$find_tag}' for event " +						. "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 3); +				} +			} + +			if (ltrim($this->file_lines[$this->current_event_line - $find_tag_line], "\t ") === '*/') +			{ +				$found_comment_end = true; +			} + +			$find_tag_line++; +			if ($find_tag_line >= $this->current_event_line) +			{ +				// Reached the start of the file +				throw new \LogicException("Can not find '@{$find_tag}' information for event " +					. "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 2); +			} +		} + +		return $this->current_event_line - $find_tag_line; +	} + +	/** +	* Find a "@*" Information line +	* +	* @return int Absolute line number +	* @throws \LogicException +	*/ +	public function find_description() +	{ +		$find_desc_line = 0; +		while (ltrim($this->file_lines[$this->current_event_line - $find_desc_line], "\t") !== '/**') +		{ +			$find_desc_line++; +			if ($find_desc_line > $this->current_event_line) +			{ +				// Reached the start of the file +				throw new \LogicException("Can not find a description for event " +					. "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 1); +			} +		} + +		$find_desc_line = $this->current_event_line - $find_desc_line + 1; + +		$desc = trim($this->file_lines[$find_desc_line]); +		if (strpos($desc, '* @') === 0 || $desc[0] !== '*' || substr($desc, 1) == '') +		{ +			// First line of the doc block is a @-line, empty or only contains "*" +			throw new \LogicException("Can not find a description for event " +				. "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 2); +		} + +		return $find_desc_line; +	} + +	/** +	* Validate "@since" Information +	* +	* @param string $line +	* @return string +	* @throws \LogicException +	*/ +	public function validate_since($line) +	{ +		$match = array(); +		preg_match('#^\* @since (\d+\.\d+\.\d+(?:-(?:a|b|RC|pl)\d+)?)$#', ltrim($line, "\t "), $match); +		if (!isset($match[1])) +		{ +			throw new \LogicException("Invalid '@since' information for event " +				. "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'"); +		} + +		return $match[1]; +	} + +	/** +	* Validate "@event" Information +	* +	* @param string $event_name +	* @param string $line +	* @return string +	* @throws \LogicException +	*/ +	public function validate_event($event_name, $line) +	{ +		$event = substr(ltrim($line, "\t "), strlen('* @event ')); + +		if ($event !== trim($event)) +		{ +			throw new \LogicException("Invalid '@event' information for event " +				. "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 1); +		} + +		if ($event !== $event_name) +		{ +			throw new \LogicException("Event name does not match '@event' tag for event " +				. "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 2); +		} + +		return $event; +	} + +	/** +	* Validates that two arrays contain the same strings +	* +	* @param array $vars_array		Variables found in the array line +	* @param array $vars_docblock	Variables found in the doc block +	* @return null +	* @throws \LogicException +	*/ +	public function validate_vars_docblock_array($vars_array, $vars_docblock) +	{ +		$vars_array = array_unique($vars_array); +		$vars_docblock = array_unique($vars_docblock); +		$sizeof_vars_array = sizeof($vars_array); + +		if ($sizeof_vars_array !== sizeof($vars_docblock) || $sizeof_vars_array !== sizeof(array_intersect($vars_array, $vars_docblock))) +		{ +			throw new \LogicException("\$vars array does not match the list of '@var' tags for event " +				. "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'"); +		} +	} +} diff --git a/phpBB/phpbb/event/recursive_event_filter_iterator.php b/phpBB/phpbb/event/recursive_event_filter_iterator.php new file mode 100644 index 0000000000..64e2e56f6a --- /dev/null +++ b/phpBB/phpbb/event/recursive_event_filter_iterator.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\event; + +/** +* This filter ignores directories and files starting with a dot. +* It also skips some directories that do not contain events anyway, +* such as e.g. files/, store/ and vendor/ +*/ +class recursive_event_filter_iterator extends \RecursiveFilterIterator +{ +	protected $root_path; + +	/** +	* Construct +	* +	* @param \RecursiveIterator	$iterator +	* @param string				$root_path +	*/ +	public function __construct(\RecursiveIterator $iterator, $root_path) +	{ +		$this->root_path = str_replace(DIRECTORY_SEPARATOR, '/', $root_path); +		parent::__construct($iterator); +	} + +	/** +	* Return the inner iterator's children contained in a recursive_event_filter_iterator +	* +	* @return recursive_event_filter_iterator +	*/ +	public function getChildren() +	{ +		return new self($this->getInnerIterator()->getChildren(), $this->root_path); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function accept() +	{ +		$relative_path = str_replace(DIRECTORY_SEPARATOR, '/', $this->current()); +		$filename = $this->current()->getFilename(); + +		return (substr($relative_path, -4) === '.php' || $this->current()->isDir()) +			&& $filename[0] !== '.' +			&& strpos($relative_path, $this->root_path . 'cache/') !== 0 +			&& strpos($relative_path, $this->root_path . 'develop/') !== 0 +			&& strpos($relative_path, $this->root_path . 'docs/') !== 0 +			&& strpos($relative_path, $this->root_path . 'ext/') !== 0 +			&& strpos($relative_path, $this->root_path . 'files/') !== 0 +			&& strpos($relative_path, $this->root_path . 'includes/utf/') !== 0 +			&& strpos($relative_path, $this->root_path . 'language/') !== 0 +			&& strpos($relative_path, $this->root_path . 'phpbb/db/migration/data/') !== 0 +			&& strpos($relative_path, $this->root_path . 'phpbb/event/') !== 0 +			&& strpos($relative_path, $this->root_path . 'store/') !== 0 +			&& strpos($relative_path, $this->root_path . 'tests/') !== 0 +			&& strpos($relative_path, $this->root_path . 'vendor/') !== 0 +		; +	} +} | 
