diff --git a/phpBB/app.php b/phpBB/app.php new file mode 100644 index 0000000000..d93208d585 --- /dev/null +++ b/phpBB/app.php @@ -0,0 +1,31 @@ +session_begin(); +$auth->acl($user->data); +$user->setup('app'); + +$symfony_request = phpbb_create_symfony_request($request); +$http_kernel = $phpbb_container->get('http_kernel'); +$response = $http_kernel->handle($symfony_request); +$response->send(); +$http_kernel->terminate($symfony_request, $response); diff --git a/phpBB/common.php b/phpBB/common.php index c4237dfcf5..e99b9edee5 100644 --- a/phpBB/common.php +++ b/phpBB/common.php @@ -8,10 +8,6 @@ * Minimum Requirement: PHP 5.3.3 */ -use Symfony\Component\Config\FileLocator; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; - /** */ if (!defined('IN_PHPBB')) @@ -95,6 +91,7 @@ $phpbb_container = phpbb_create_dumped_container_unless_debug( ), array( new phpbb_di_pass_collection_pass(), + new phpbb_di_pass_kernel_pass(), ), $phpbb_root_path, $phpEx diff --git a/phpBB/composer.json b/phpBB/composer.json index 380bdc367c..5a03e68f73 100644 --- a/phpBB/composer.json +++ b/phpBB/composer.json @@ -5,6 +5,7 @@ "symfony/dependency-injection": "2.1.*", "symfony/event-dispatcher": "2.1.*", "symfony/http-kernel": "2.1.*", + "symfony/routing": "2.1.*", "symfony/yaml": "2.1.*" }, "require-dev": { diff --git a/phpBB/composer.lock b/phpBB/composer.lock index 69e4a2b4b8..62ece6d505 100644 --- a/phpBB/composer.lock +++ b/phpBB/composer.lock @@ -1,5 +1,5 @@ { - "hash": "407cc89f4bb0e409146c863dee51b0ae", + "hash": "efb4768ba71d7cd2c84baa0610d84067", "packages": [ { "name": "symfony/config", @@ -272,6 +272,64 @@ "description": "Symfony HttpKernel Component", "homepage": "http://symfony.com" }, + { + "name": "symfony/routing", + "version": "v2.1.3", + "target-dir": "Symfony/Component/Routing", + "source": { + "type": "git", + "url": "https://github.com/symfony/Routing", + "reference": "v2.1.3" + }, + "dist": { + "type": "zip", + "url": "https://github.com/symfony/Routing/zipball/v2.1.3", + "reference": "v2.1.3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/config": "2.1.*", + "symfony/yaml": "2.1.*", + "symfony/http-kernel": "2.1.*", + "doctrine/common": ">=2.2,<2.4-dev" + }, + "suggest": { + "symfony/config": "2.1.*", + "symfony/yaml": "2.1.*", + "doctrine/common": ">=2.2,<2.4-dev" + }, + "time": "2012-10-26 02:26:42", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\Routing": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Routing Component", + "homepage": "http://symfony.com" + }, { "name": "symfony/yaml", "version": "v2.1.3", @@ -331,7 +389,7 @@ }, "dist": { "type": "zip", - "url": "https://github.com/fabpot/Goutte/zipball/f2940f9c7c1f409159f5e9f512e575946c5cff48", + "url": "https://github.com/fabpot/Goutte/archive/f2940f9c7c1f409159f5e9f512e575946c5cff48.zip", "reference": "f2940f9c7c1f409159f5e9f512e575946c5cff48", "shasum": "" }, diff --git a/phpBB/config/routing.yml b/phpBB/config/routing.yml new file mode 100644 index 0000000000..d8e890d063 --- /dev/null +++ b/phpBB/config/routing.yml @@ -0,0 +1,9 @@ +# Structure: +# +# foo_controller: +# pattern: /foo +# defaults: { _controller: foo_sevice:method } +# +# The above will be accessed via app.php?controller=foo and it will +# instantiate the "foo_service" service and call the "method" method. +# diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml index 20aa0546d5..37e6c0b5df 100644 --- a/phpBB/config/services.yml +++ b/phpBB/config/services.yml @@ -44,6 +44,30 @@ services: - @cache.driver - %tables.config% + controller.helper: + class: phpbb_controller_helper + arguments: + - @template + - @user + - %core.root_path% + - .%core.php_ext% + + controller.resolver: + class: phpbb_controller_resolver + arguments: + - @user + - @service_container + - @ext.finder + + controller.route_collection: + class: phpbb_controller_route_collection + arguments: + - @ext.finder + - @controller.provider + + controller.provider: + class: phpbb_controller_provider + cron.task_collection: class: phpbb_di_service_collection arguments: @@ -93,6 +117,43 @@ services: - .%core.php_ext% - @cache.driver + ext.finder: + class: phpbb_extension_finder + arguments: + - @ext.manager + - %core.root_path% + - @cache.driver + - .%core.php_ext% + - _ext_finder + + http_kernel: + class: Symfony\Component\HttpKernel\HttpKernel + arguments: + - @dispatcher + - @controller.resolver + + kernel_request_subscriber: + class: phpbb_event_kernel_request_subscriber + arguments: + - @ext.finder + - %core.root_path% + - .%core.php_ext% + tags: + - { name: kernel.event_subscriber } + + kernel_exception_subscriber: + class: phpbb_event_kernel_exception_subscriber + arguments: + - @template + - @user + tags: + - { name: kernel.event_subscriber } + + kernel_terminate_subscriber: + class: phpbb_event_kernel_terminate_subscriber + tags: + - { name: kernel.event_subscriber } + request: class: phpbb_request diff --git a/phpBB/download/file.php b/phpBB/download/file.php index 85f5d11504..73b9e0a9f2 100644 --- a/phpBB/download/file.php +++ b/phpBB/download/file.php @@ -64,6 +64,7 @@ if (isset($_GET['avatar'])) ), array( new phpbb_di_pass_collection_pass(), + new phpbb_di_pass_kernel_pass(), ), $phpbb_root_path, $phpEx diff --git a/phpBB/includes/cache/driver/file.php b/phpBB/includes/cache/driver/file.php index 32bdb1918a..5014ba18af 100644 --- a/phpBB/includes/cache/driver/file.php +++ b/phpBB/includes/cache/driver/file.php @@ -215,6 +215,7 @@ class phpbb_cache_driver_file extends phpbb_cache_driver_base while (($entry = readdir($dir)) !== false) { if (strpos($entry, 'container_') !== 0 && + strpos($entry, 'url_matcher') !== 0 && strpos($entry, 'sql_') !== 0 && strpos($entry, 'data_') !== 0 && strpos($entry, 'ctpl_') !== 0 && diff --git a/phpBB/includes/cache/driver/memory.php b/phpBB/includes/cache/driver/memory.php index 1ea9a3e9e7..f6c42c0ea6 100644 --- a/phpBB/includes/cache/driver/memory.php +++ b/phpBB/includes/cache/driver/memory.php @@ -163,6 +163,7 @@ abstract class phpbb_cache_driver_memory extends phpbb_cache_driver_base while (($entry = readdir($dir)) !== false) { if (strpos($entry, 'container_') !== 0 && + strpos($entry, 'url_matcher') !== 0 && strpos($entry, 'sql_') !== 0 && strpos($entry, 'data_') !== 0 && strpos($entry, 'ctpl_') !== 0 && diff --git a/phpBB/includes/controller/exception.php b/phpBB/includes/controller/exception.php new file mode 100644 index 0000000000..faa8b6b584 --- /dev/null +++ b/phpBB/includes/controller/exception.php @@ -0,0 +1,24 @@ +template = $template; + $this->user = $user; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * Automate setting up the page and creating the response object. + * + * @param string $handle The template handle to render + * @param string $page_title The title of the page to output + * @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 = '', $status_code = 200) + { + page_header($page_title); + + $this->template->set_filenames(array( + 'body' => $template_file, + )); + + page_footer(true, false, false); + + return new Response($this->template->assign_display('body'), $status_code); + } + + /** + * Easily generate a URL + * + * @param array $url_parts Each array element is a 'folder' + * i.e. array('my', 'ext') maps to ./app.php/my/ext + * @param mixed $query The Query string, passed directly into the second + * argument of append_sid() + * @return string A URL that has already been run through append_sid() + */ + public function url(array $url_parts, $query = '') + { + return append_sid($this->phpbb_root_path . implode('/', $url_parts), $query); + } + + /** + * Output an error, effectively the same thing as trigger_error + * + * @param string $message The error message + * @param string $code The error code (e.g. 404, 500, 503, etc.) + * @return Response A Reponse instance + */ + public function error($message, $code = 500) + { + $this->template->assign_vars(array( + 'MESSAGE_TEXT' => $message, + 'MESSAGE_TITLE' => $this->user->lang('INFORMATION'), + )); + + return $this->render('message_body.html', $this->user->lang('INFORMATION'), $code); + } +} diff --git a/phpBB/includes/controller/provider.php b/phpBB/includes/controller/provider.php new file mode 100644 index 0000000000..b2a5b9f6b2 --- /dev/null +++ b/phpBB/includes/controller/provider.php @@ -0,0 +1,82 @@ +routing_paths = $routing_paths; + } + + /** + * Locate paths containing routing files + * This sets an internal property but does not return the paths. + * + * @return The current instance of this object for method chaining + */ + public function import_paths_from_finder(phpbb_extension_finder $finder) + { + // We hardcode the path to the core config directory + // because the finder cannot find it + $this->routing_paths = array_merge(array('config'), array_map('dirname', array_keys($finder + ->directory('config') + ->prefix('routing') + ->suffix('yml') + ->find() + ))); + + return $this; + } + + /** + * Get a list of controllers and return it + * + * @param string $base_path Base path to prepend to file paths + * @return array Array of controllers and their route information + */ + public function find($base_path = '') + { + $routes = new RouteCollection; + foreach ($this->routing_paths as $path) + { + $loader = new YamlFileLoader(new FileLocator($base_path . $path)); + $routes->addCollection($loader->load('routing.yml')); + } + + return $routes; + } +} diff --git a/phpBB/includes/controller/resolver.php b/phpBB/includes/controller/resolver.php new file mode 100644 index 0000000000..ee469aa9c8 --- /dev/null +++ b/phpBB/includes/controller/resolver.php @@ -0,0 +1,128 @@ +user = $user; + $this->container = $container; + } + + /** + * Load a controller callable + * + * @param Symfony\Component\HttpFoundation\Request $request Symfony Request object + * @return bool|Callable Callable or false + * @throws phpbb_controller_exception + */ + public function getController(Request $request) + { + $controller = $request->attributes->get('_controller'); + + if (!$controller) + { + throw new phpbb_controller_exception($this->user->lang['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']); + } + + list($service, $method) = explode(':', $controller); + + if (!$this->container->has($service)) + { + throw new phpbb_controller_exception($this->user->lang('CONTROLLER_SERVICE_UNDEFINED', $service)); + } + + $controller_object = $this->container->get($service); + + return array($controller_object, $method); + } + + /** + * Dependencies should be specified in the service definition and can be + * then accessed in __construct(). Arguments are sent through the URL path + * and should match the parameters of the method you are using as your + * controller. + * + * @param Symfony\Component\HttpFoundation\Request $request Symfony Request object + * @param mixed $controller A callable (controller class, method) + * @return bool False + * @throws phpbb_controller_exception + */ + 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); + + $arguments = array(); + $parameters = $mirror->getParameters(); + $attributes = $request->attributes->all(); + foreach ($parameters as $param) + { + if (array_key_exists($param->name, $attributes)) + { + $arguments[] = $attributes[$param->name]; + } + else if ($param->getClass() && $param->getClass()->isInstance($request)) + { + $arguments[] = $request; + } + else if ($param->isDefaultValueAvailable()) + { + $arguments[] = $param->getDefaultValue(); + } + else + { + throw new phpbb_controller_exception($this->user->lang('CONTROLLER_ARGUMENT_VALUE_MISSING', $param->getPosition() + 1, get_class($object) . ':' . $method, $param->name)); + } + } + + return $arguments; + } +} diff --git a/phpBB/includes/di/pass/kernel_pass.php b/phpBB/includes/di/pass/kernel_pass.php new file mode 100644 index 0000000000..a701ebcfa6 --- /dev/null +++ b/phpBB/includes/di/pass/kernel_pass.php @@ -0,0 +1,68 @@ +getDefinition('dispatcher'); + + foreach ($container->findTaggedServiceIds('kernel.event_listener') as $id => $events) + { + foreach ($events as $event) + { + $priority = isset($event['priority']) ? $event['priority'] : 0; + + if (!isset($event['event'])) + { + throw new InvalidArgumentException(sprintf('Service "%1$s" must define the "event" attribute on "kernel.event_listener" tags.', $id)); + } + + if (!isset($event['method'])) + { + throw new InvalidArgumentException(sprintf('Service "%1$s" must define the "method" attribute on "kernel.event_listener" tags.', $id)); + } + + $definition->addMethodCall('addListenerService', array($event['event'], array($id, $event['method']), $priority)); + } + } + + foreach ($container->findTaggedServiceIds('kernel.event_subscriber') as $id => $attributes) + { + // We must assume that the class value has been correctly filled, even if the service is created by a factory + $class = $container->getDefinition($id)->getClass(); + + $refClass = new ReflectionClass($class); + $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface'; + if (!$refClass->implementsInterface($interface)) + { + throw new InvalidArgumentException(sprintf('Service "%1$s" must implement interface "%2$s".', $id, $interface)); + } + + $definition->addMethodCall('addSubscriberService', array($id, $class)); + } + } +} diff --git a/phpBB/includes/event/kernel_exception_subscriber.php b/phpBB/includes/event/kernel_exception_subscriber.php new file mode 100644 index 0000000000..f90989a74c --- /dev/null +++ b/phpBB/includes/event/kernel_exception_subscriber.php @@ -0,0 +1,85 @@ +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) + { + page_header($this->user->lang('INFORMATION')); + + $exception = $event->getException(); + + $this->template->assign_vars(array( + 'MESSAGE_TITLE' => $this->user->lang('INFORMATION'), + 'MESSAGE_TEXT' => $exception->getMessage(), + )); + + $this->template->set_filenames(array( + 'body' => 'message_body.html', + )); + + page_footer(true, false, false); + + + $status_code = $exception instanceof HttpException ? $exception->getStatusCode() : 500; + $response = new Response($this->template->assign_display('body'), $status_code); + $event->setResponse($response); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::EXCEPTION => 'on_kernel_exception', + ); + } +} diff --git a/phpBB/includes/event/kernel_request_subscriber.php b/phpBB/includes/event/kernel_request_subscriber.php new file mode 100644 index 0000000000..afb8464f80 --- /dev/null +++ b/phpBB/includes/event/kernel_request_subscriber.php @@ -0,0 +1,83 @@ +finder = $finder; + $this->root_path = $root_path; + $this->php_ext = $php_ext; + } + + /** + * This listener is run when the KernelEvents::REQUEST event is triggered + * + * This is responsible for setting up the routing information + * + * @param GetResponseEvent $event + * @return null + */ + public function on_kernel_request(GetResponseEvent $event) + { + $request = $event->getRequest(); + $context = new RequestContext(); + $context->fromRequest($request); + + $matcher = phpbb_get_url_matcher($this->finder, $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/includes/event/kernel_terminate_subscriber.php b/phpBB/includes/event/kernel_terminate_subscriber.php new file mode 100644 index 0000000000..1eaf890e42 --- /dev/null +++ b/phpBB/includes/event/kernel_terminate_subscriber.php @@ -0,0 +1,43 @@ + 'on_kernel_terminate', + ); + } +} diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index ab4c7e1772..d7088ac129 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -7,6 +7,8 @@ * */ +use Symfony\Component\HttpFoundation\Request; + /** * @ignore */ @@ -5231,8 +5233,12 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0 /** * Generate page footer +* +* @param bool $run_cron Whether or not to run the cron +* @param bool $display_template Whether or not to display the template +* @param bool $exit_handler Whether or not to run the exit_handler() */ -function page_footer($run_cron = true) +function page_footer($run_cron = true, $display_template = true, $exit_handler = true) { global $db, $config, $template, $user, $auth, $cache, $starttime, $phpbb_root_path, $phpEx; global $request, $phpbb_dispatcher; @@ -5327,10 +5333,17 @@ function page_footer($run_cron = true) } } - $template->display('body'); + if ($display_template) + { + $template->display('body'); + } garbage_collection(); - exit_handler(); + + if ($exit_handler) + { + exit_handler(); + } } /** @@ -5437,3 +5450,49 @@ function phpbb_to_numeric($input) { return ($input > PHP_INT_MAX) ? (float) $input : (int) $input; } + +/** +* Create a Symfony Request object from phpbb_request object +* +* @param phpbb_request $request Request object +* @return Request A Symfony Request object +*/ +function phpbb_create_symfony_request(phpbb_request $request) +{ + // This function is meant to sanitize the global input arrays + $sanitizer = function(&$value, $key) { + $type_cast_helper = new phpbb_request_type_cast_helper(); + $type_cast_helper->set_var($value, $value, gettype($value), true); + }; + + // We need to re-enable the super globals so we can access them here + $request->enable_super_globals(); + $get_parameters = $_GET; + $post_parameters = $_POST; + $server_parameters = $_SERVER; + $files_parameters = $_FILES; + $cookie_parameters = $_COOKIE; + // And now disable them again for security + $request->disable_super_globals(); + + array_walk_recursive($get_parameters, $sanitizer); + array_walk_recursive($post_parameters, $sanitizer); + + // Until we fix the issue with relative paths, we have to fake path info + // to allow urls like app.php?controller=foo/bar + $controller = $request->variable('controller', ''); + $path_info = '/' . $controller; + $request_uri = $server_parameters['REQUEST_URI']; + + // Remove the query string from REQUEST_URI + if ($pos = strpos($request_uri, '?')) + { + $request_uri = substr($request_uri, 0, $pos); + } + + // Add the path info (i.e. controller route) to the REQUEST_URI + $server_parameters['REQUEST_URI'] = $request_uri . $path_info; + $server_parameters['SCRIPT_NAME'] = ''; + + return new Request($get_parameters, $post_parameters, array(), $cookie_parameters, $files_parameters, $server_parameters); +} diff --git a/phpBB/includes/functions_url_matcher.php b/phpBB/includes/functions_url_matcher.php new file mode 100644 index 0000000000..7280cb74eb --- /dev/null +++ b/phpBB/includes/functions_url_matcher.php @@ -0,0 +1,106 @@ +import_paths_from_finder($finder)->find(); + $dumper = new PhpMatcherDumper($routes); + $cached_url_matcher_dump = $dumper->dump(array( + 'class' => 'phpbb_url_matcher', + )); + + file_put_contents($root_path . 'cache/url_matcher' . $php_ext, $cached_url_matcher_dump); +} + +/** +* Create a non-cached UrlMatcher +* +* @param phpbb_extension_finder $finder Extension finder +* @param RequestContext $context Symfony RequestContext object +* @return UrlMatcher +*/ +function phpbb_create_url_matcher(phpbb_extension_finder $finder, RequestContext $context) +{ + $provider = new phpbb_controller_provider(); + $routes = $provider->import_paths_from_finder($finder)->find(); + return new UrlMatcher($routes, $context); +} + +/** +* Load the cached phpbb_url_matcher class +* +* @param RequestContext $context Symfony RequestContext object +* @param string $root_path Root path +* @param string $php_ext PHP extension +* @return phpbb_url_matcher +*/ +function phpbb_load_url_matcher(RequestContext $context, $root_path, $php_ext) +{ + require($root_path . 'cache/url_matcher' . $php_ext); + return new phpbb_url_matcher($context); +} + +/** +* Determine whether we have our dumped URL matcher +* +* The class is automatically dumped to the cache directory +* +* @param string $root_path Root path +* @param string $php_ext PHP extension +* @return bool True if it exists, false if not +*/ +function phpbb_url_matcher_dumped($root_path, $php_ext) +{ + return file_exists($root_path . 'cache/url_matcher' . $php_ext); +} diff --git a/phpBB/index.php b/phpBB/index.php index 66e1b2114b..845d0f0c02 100644 --- a/phpBB/index.php +++ b/phpBB/index.php @@ -17,48 +17,12 @@ define('IN_PHPBB', true); $phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './'; $phpEx = substr(strrchr(__FILE__, '.'), 1); include($phpbb_root_path . 'common.' . $phpEx); +include($phpbb_root_path . 'includes/functions_display.' . $phpEx); // Start session management $user->session_begin(); $auth->acl($user->data); -$user->setup(); - -// Handle the display of extension front pages -if ($ext = $request->variable('ext', '')) -{ - $class = 'phpbb_ext_' . str_replace('/', '_', $ext) . '_controller'; - - if (!$phpbb_extension_manager->available($ext)) - { - send_status_line(404, 'Not Found'); - trigger_error($user->lang('EXTENSION_DOES_NOT_EXIST', $ext)); - } - else if (!$phpbb_extension_manager->enabled($ext)) - { - send_status_line(404, 'Not Found'); - trigger_error($user->lang('EXTENSION_DISABLED', $ext)); - } - else if (!class_exists($class)) - { - send_status_line(404, 'Not Found'); - trigger_error($user->lang('EXTENSION_CONTROLLER_MISSING', $ext)); - } - - $controller = new $class; - - if (!($controller instanceof phpbb_extension_controller_interface)) - { - send_status_line(500, 'Internal Server Error'); - trigger_error($user->lang('EXTENSION_CLASS_WRONG_TYPE', $class)); - } - - $controller->handle(); - exit_handler(); -} - -include($phpbb_root_path . 'includes/functions_display.' . $phpEx); - -$user->add_lang('viewforum'); +$user->setup('viewforum'); display_forums('', $config['load_moderators']); diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index 297802c210..377e38c423 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -113,6 +113,7 @@ $phpbb_container = phpbb_create_dumped_container_unless_debug( ), array( new phpbb_di_pass_collection_pass(), + new phpbb_di_pass_kernel_pass(), ), $phpbb_root_path, $phpEx diff --git a/phpBB/language/en/app.php b/phpBB/language/en/app.php new file mode 100644 index 0000000000..cb56015c30 --- /dev/null +++ b/phpBB/language/en/app.php @@ -0,0 +1,49 @@ + 'Missing value for argument #%1$s: %3$s in class %2$s', + 'CONTROLLER_NOT_SPECIFIED' => 'No controller has been specified.', + 'CONTROLLER_NOT_FOUND' => 'The requested page could not be found.', + 'CONTROLLER_METHOD_NOT_SPECIFIED' => 'No method was specified for the controller.', + 'CONTROLLER_SERVICE_NOT_GIVEN' => 'The controller "%s" must have a service specified in ./config/routing.yml.', + 'CONTROLLER_SERVICE_UNDEFINED' => 'The service for controller "%s" is not defined in ./config/services.yml.', + 'CONTROLLER_RETURN_TYPE_INVALID' => 'The controller object %s must return a Symfony\Component\HttpFoundation\Response object.', +)); diff --git a/tests/controller/config/routing.yml b/tests/controller/config/routing.yml new file mode 100644 index 0000000000..175b11f130 --- /dev/null +++ b/tests/controller/config/routing.yml @@ -0,0 +1,3 @@ +core_controller: + pattern: /core_foo + defaults: { _controller: core_foo.controller:bar } diff --git a/tests/controller/config/services.yml b/tests/controller/config/services.yml new file mode 100644 index 0000000000..f1bd047489 --- /dev/null +++ b/tests/controller/config/services.yml @@ -0,0 +1,3 @@ +services: + core_foo.controller: + class: phpbb_controller_foo diff --git a/tests/controller/controller_test.php b/tests/controller/controller_test.php new file mode 100644 index 0000000000..198fb3c6dd --- /dev/null +++ b/tests/controller/controller_test.php @@ -0,0 +1,76 @@ +extension_manager = new phpbb_mock_extension_manager( + dirname(__FILE__) . '/', + array( + 'foo' => array( + 'ext_name' => 'foo', + 'ext_active' => '1', + 'ext_path' => 'ext/foo/', + ), + )); + } + + public function test_provider() + { + $provider = new phpbb_controller_provider; + $routes = $provider + ->import_paths_from_finder($this->extension_manager->get_finder()) + ->find('./tests/controller/'); + + // This will need to be updated if any new routes are defined + $this->assertEquals(2, sizeof($routes)); + } + + public function test_controller_resolver() + { + $container = new ContainerBuilder(); + // YamlFileLoader only uses one path at a time, so we need to loop + // through all of the ones we are using. + foreach (array(__DIR__.'/config', __DIR__.'/ext/foo/config') as $path) + { + $loader = new YamlFileLoader($container, new FileLocator($path)); + $loader->load('services.yml'); + } + + // Autoloading classes within the tests folder does not work + // so I'll include them manually. + if (!class_exists('phpbb_ext_foo_controller')) + { + include(__DIR__.'/ext/foo/controller.php'); + } + if (!class_exists('phpbb_controller_foo')) + { + include(__DIR__.'/includes/controller/foo.php'); + } + + $resolver = new phpbb_controller_resolver(new phpbb_user, $container); + $symfony_request = new Request(); + $symfony_request->attributes->set('_controller', 'foo.controller:handle'); + + $this->assertEquals($resolver->getController($symfony_request), array(new phpbb_ext_foo_controller, 'handle')); + + $symfony_request = new Request(); + $symfony_request->attributes->set('_controller', 'core_foo.controller:bar'); + + $this->assertEquals($resolver->getController($symfony_request), array(new phpbb_controller_foo, 'bar')); + } +} diff --git a/tests/controller/ext/foo/config/routing.yml b/tests/controller/ext/foo/config/routing.yml new file mode 100644 index 0000000000..4799fec37d --- /dev/null +++ b/tests/controller/ext/foo/config/routing.yml @@ -0,0 +1,3 @@ +controller1: + pattern: /foo + defaults: { _controller: foo.controller:handle } diff --git a/tests/controller/ext/foo/config/services.yml b/tests/controller/ext/foo/config/services.yml new file mode 100644 index 0000000000..ce0e18c610 --- /dev/null +++ b/tests/controller/ext/foo/config/services.yml @@ -0,0 +1,3 @@ +services: + foo.controller: + class: phpbb_ext_foo_controller diff --git a/tests/controller/ext/foo/controller.php b/tests/controller/ext/foo/controller.php new file mode 100644 index 0000000000..cfc5c20622 --- /dev/null +++ b/tests/controller/ext/foo/controller.php @@ -0,0 +1,16 @@ +phpbb_extension_manager->enable('foobar'); - $crawler = $this->request('GET', 'index.php?ext=foobar'); - $this->assert_response_success(); - $this->assertContains("This is for testing purposes.", $crawler->filter('#page-body')->text()); - $this->phpbb_extension_manager->purge('foobar'); - } - - /** - * Check an extension at ./ext/foo/bar/ which should have the class - * phpbb_ext_foo_bar_controller + * Check a controller for extension foo/bar. */ public function test_foo_bar() { $this->phpbb_extension_manager->enable('foo/bar'); - $crawler = $this->request('GET', 'index.php?ext=foo/bar'); + $crawler = $this->request('GET', 'app.php?controller=foo/bar'); $this->assert_response_success(); - $this->assertContains("This is for testing purposes.", $crawler->filter('#page-body')->text()); + $this->assertContains("foo/bar controller handle() method", $crawler->filter('body')->text()); $this->phpbb_extension_manager->purge('foo/bar'); } /** - * Check the error produced by extension at ./ext/error/class which has class - * phpbb_ext_foobar_controller + * Check the output of a controller using the template system */ - public function test_error_class_name() + public function test_controller_with_template() { - $this->phpbb_extension_manager->enable('error/class'); - $crawler = $this->request('GET', 'index.php?ext=error/class'); - $this->assertContains("The extension error/class is missing a controller class and cannot be accessed through the front-end.", $crawler->filter('#message')->text()); - $this->phpbb_extension_manager->purge('error/class'); + $this->phpbb_extension_manager->enable('foo/bar'); + $crawler = $this->request('GET', 'app.php?controller=foo/template'); + $this->assert_response_success(); + $this->assertContains("I am a variable", $crawler->filter('#content')->text()); + $this->phpbb_extension_manager->purge('foo/bar'); } /** - * Check the error produced by extension at ./ext/error/classtype which has class - * phpbb_ext_error_classtype_controller but does not implement phpbb_extension_controller_interface + * Check the error produced by calling a controller without a required + * argument. */ - public function test_error_class_type() + public function test_missing_argument() { - $this->phpbb_extension_manager->enable('error/classtype'); - $crawler = $this->request('GET', 'index.php?ext=error/classtype'); - $this->assertContains("The extension controller class phpbb_ext_error_classtype_controller is not an instance of the phpbb_extension_controller_interface.", $crawler->filter('#message')->text()); - $this->phpbb_extension_manager->purge('error/classtype'); + $this->phpbb_extension_manager->enable('foo/bar'); + $crawler = $this->request('GET', 'app.php?controller=foo/baz'); + $this->assertEquals(500, $this->client->getResponse()->getStatus()); + $this->assertContains('Missing value for argument #1: test in class phpbb_ext_foo_bar_controller:baz', $crawler->filter('body')->text()); + $this->phpbb_extension_manager->purge('foo/bar'); } /** - * Check the error produced by extension at ./ext/error/disabled that is (obviously) - * a disabled extension + * Check the status code resulting from an exception thrown by a controller */ - public function test_error_ext_disabled() + public function test_exception_should_result_in_500_status_code() { - $crawler = $this->request('GET', 'index.php?ext=error/disabled'); - $this->assertContains("The extension error/disabled is not enabled", $crawler->filter('#message')->text()); + $this->phpbb_extension_manager->enable('foo/bar'); + $crawler = $this->request('GET', 'app.php?controller=foo/exception'); + $this->assertEquals(500, $this->client->getResponse()->getStatus()); + $this->assertContains('Exception thrown from foo/exception route', $crawler->filter('body')->text()); + $this->phpbb_extension_manager->purge('foo/bar'); } /** - * Check the error produced by extension at ./ext/error/404 that is (obviously) - * not existant + * Check the error produced by extension at ./ext/does/not/exist. + * + * If an extension is disabled, its routes are not loaded. Because we + * are not looking for a controller based on a specified extension, + * we don't know the difference between a route in a disabled + * extension and a route that is not defined anyway; it is the same + * error message. */ - public function test_error_ext_missing() + public function test_error_ext_disabled_or_404() { - $crawler = $this->request('GET', 'index.php?ext=error/404'); - $this->assertContains("The extension error/404 does not exist.", $crawler->filter('#message')->text()); + $crawler = $this->request('GET', 'app.php?controller=does/not/exist'); + $this->assertEquals(404, $this->client->getResponse()->getStatus()); + $this->assertContains('No route found for "GET /does/not/exist"', $crawler->filter('body')->text()); } } diff --git a/tests/functional/fixtures/ext/error/class/controller.php b/tests/functional/fixtures/ext/error/class/controller.php deleted file mode 100644 index 74bbbee540..0000000000 --- a/tests/functional/fixtures/ext/error/class/controller.php +++ /dev/null @@ -1,14 +0,0 @@ -template->set_filenames(array( - 'body' => 'index_body.html' - )); - - page_header('Test extension'); - page_footer(); - } -} diff --git a/tests/functional/fixtures/ext/error/class/ext.php b/tests/functional/fixtures/ext/error/class/ext.php deleted file mode 100644 index f97ad2b838..0000000000 --- a/tests/functional/fixtures/ext/error/class/ext.php +++ /dev/null @@ -1,6 +0,0 @@ -set_filenames(array( - 'body' => 'index_body.html' - )); - - page_header('Test extension'); - page_footer(); - } -} diff --git a/tests/functional/fixtures/ext/error/classtype/ext.php b/tests/functional/fixtures/ext/error/classtype/ext.php deleted file mode 100644 index 35b1cd15a2..0000000000 --- a/tests/functional/fixtures/ext/error/classtype/ext.php +++ /dev/null @@ -1,6 +0,0 @@ -template->set_filenames(array( - 'body' => 'index_body.html' - )); - - page_header('Test extension'); - page_footer(); - } -} diff --git a/tests/functional/fixtures/ext/error/disabled/ext.php b/tests/functional/fixtures/ext/error/disabled/ext.php deleted file mode 100644 index aec8051848..0000000000 --- a/tests/functional/fixtures/ext/error/disabled/ext.php +++ /dev/null @@ -1,6 +0,0 @@ -template->set_filenames(array( - 'body' => 'foobar_body.html' - )); - - page_header('Test extension'); - page_footer(); - } -} diff --git a/tests/functional/fixtures/ext/foo/bar/controller/controller.php b/tests/functional/fixtures/ext/foo/bar/controller/controller.php new file mode 100644 index 0000000000..5a91b5f681 --- /dev/null +++ b/tests/functional/fixtures/ext/foo/bar/controller/controller.php @@ -0,0 +1,35 @@ +template = $template; + $this->helper = $helper; + } + + public function handle() + { + return new Response('foo/bar controller handle() method', 200); + } + + public function baz($test) + { + return new Response('Value of "test" URL argument is: ' . $test); + } + + public function template() + { + $this->template->assign_var('A_VARIABLE', 'I am a variable'); + + return $this->helper->render('foo_bar_body.html'); + } + + public function exception() + { + throw new phpbb_controller_exception('Exception thrown from foo/exception route'); + } +} diff --git a/tests/functional/fixtures/ext/foo/bar/ext.php b/tests/functional/fixtures/ext/foo/bar/ext.php index 3a2068631e..74359d51ab 100644 --- a/tests/functional/fixtures/ext/foo/bar/ext.php +++ b/tests/functional/fixtures/ext/foo/bar/ext.php @@ -2,5 +2,5 @@ class phpbb_ext_foo_bar_ext extends phpbb_extension_base { - + } diff --git a/tests/functional/fixtures/ext/foo/bar/styles/prosilver/template/foobar_body.html b/tests/functional/fixtures/ext/foo/bar/styles/prosilver/template/foo_bar_body.html similarity index 56% rename from tests/functional/fixtures/ext/foo/bar/styles/prosilver/template/foobar_body.html rename to tests/functional/fixtures/ext/foo/bar/styles/prosilver/template/foo_bar_body.html index 4addf2666f..8fb6994d3d 100644 --- a/tests/functional/fixtures/ext/foo/bar/styles/prosilver/template/foobar_body.html +++ b/tests/functional/fixtures/ext/foo/bar/styles/prosilver/template/foo_bar_body.html @@ -1,5 +1,3 @@ - -