Merge remote-tracking branch 'imkingdavid/feature/controller-new' into develop

* imkingdavid/feature/controller-new: (67 commits)
  [feature/controller] Fix misnamed route for functional test
  [feature/controller] Fix comments, check against more general HttpException
  [feature/controller] Check for proper status codes from controllers
  [feature/controller] Correctly create Symfony object from globals
  [feature/controller] Add documentation about input being HTML-escaped
  [feature/controller] Create Symfony Request in new function
  [feature/controller] Remove unused language strings
  [feature/controller] Don't use $user->lang() before container compilation
  [feature/controller] Update routing documentation for using query string
  [feature/controller] Remove now-unused code
  [feature/controller] Remove url rewriting until we use pathinfo in controllers
  [feature/controller] Fix functional tests to use query string for controllers
  [feature/controller] Allow injecting Symfony Request into controllers
  [feature/controller] Use query string, not path info, for controller access
  [feature/controller] Fix line endings and permissions, and check responses
  [feature/controller] Remove URL rewriting by default
  [feature/controller] Add controller functional test with template
  [feature/controller] Use warning instead of echo for copy() and unlink()
  [feature/controller] Flip method parameters, require $message
  [feature/controller] Rename $root_path class property to $phpbb_root_path
  ...
This commit is contained in:
Igor Wiedler 2012-11-19 22:28:12 +01:00
commit b453f359ff
45 changed files with 1267 additions and 219 deletions

31
phpBB/app.php Normal file
View file

@ -0,0 +1,31 @@
<?php
/**
*
* @package phpBB3
* @copyright (c) 2012 phpBB Group
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
*/
/**
*/
/**
* @ignore
*/
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_url_matcher.' . $phpEx);
// Start session management
$user->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);

View file

@ -8,10 +8,6 @@
* Minimum Requirement: PHP 5.3.3 * 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')) if (!defined('IN_PHPBB'))
@ -95,6 +91,7 @@ $phpbb_container = phpbb_create_dumped_container_unless_debug(
), ),
array( array(
new phpbb_di_pass_collection_pass(), new phpbb_di_pass_collection_pass(),
new phpbb_di_pass_kernel_pass(),
), ),
$phpbb_root_path, $phpbb_root_path,
$phpEx $phpEx

View file

@ -5,6 +5,7 @@
"symfony/dependency-injection": "2.1.*", "symfony/dependency-injection": "2.1.*",
"symfony/event-dispatcher": "2.1.*", "symfony/event-dispatcher": "2.1.*",
"symfony/http-kernel": "2.1.*", "symfony/http-kernel": "2.1.*",
"symfony/routing": "2.1.*",
"symfony/yaml": "2.1.*" "symfony/yaml": "2.1.*"
}, },
"require-dev": { "require-dev": {

62
phpBB/composer.lock generated
View file

@ -1,5 +1,5 @@
{ {
"hash": "407cc89f4bb0e409146c863dee51b0ae", "hash": "efb4768ba71d7cd2c84baa0610d84067",
"packages": [ "packages": [
{ {
"name": "symfony/config", "name": "symfony/config",
@ -272,6 +272,64 @@
"description": "Symfony HttpKernel Component", "description": "Symfony HttpKernel Component",
"homepage": "http://symfony.com" "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", "name": "symfony/yaml",
"version": "v2.1.3", "version": "v2.1.3",
@ -331,7 +389,7 @@
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://github.com/fabpot/Goutte/zipball/f2940f9c7c1f409159f5e9f512e575946c5cff48", "url": "https://github.com/fabpot/Goutte/archive/f2940f9c7c1f409159f5e9f512e575946c5cff48.zip",
"reference": "f2940f9c7c1f409159f5e9f512e575946c5cff48", "reference": "f2940f9c7c1f409159f5e9f512e575946c5cff48",
"shasum": "" "shasum": ""
}, },

9
phpBB/config/routing.yml Normal file
View file

@ -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.
#

View file

@ -44,6 +44,30 @@ services:
- @cache.driver - @cache.driver
- %tables.config% - %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: cron.task_collection:
class: phpbb_di_service_collection class: phpbb_di_service_collection
arguments: arguments:
@ -93,6 +117,43 @@ services:
- .%core.php_ext% - .%core.php_ext%
- @cache.driver - @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: request:
class: phpbb_request class: phpbb_request

View file

@ -64,6 +64,7 @@ if (isset($_GET['avatar']))
), ),
array( array(
new phpbb_di_pass_collection_pass(), new phpbb_di_pass_collection_pass(),
new phpbb_di_pass_kernel_pass(),
), ),
$phpbb_root_path, $phpbb_root_path,
$phpEx $phpEx

View file

@ -215,6 +215,7 @@ class phpbb_cache_driver_file extends phpbb_cache_driver_base
while (($entry = readdir($dir)) !== false) while (($entry = readdir($dir)) !== false)
{ {
if (strpos($entry, 'container_') !== 0 && if (strpos($entry, 'container_') !== 0 &&
strpos($entry, 'url_matcher') !== 0 &&
strpos($entry, 'sql_') !== 0 && strpos($entry, 'sql_') !== 0 &&
strpos($entry, 'data_') !== 0 && strpos($entry, 'data_') !== 0 &&
strpos($entry, 'ctpl_') !== 0 && strpos($entry, 'ctpl_') !== 0 &&

View file

@ -163,6 +163,7 @@ abstract class phpbb_cache_driver_memory extends phpbb_cache_driver_base
while (($entry = readdir($dir)) !== false) while (($entry = readdir($dir)) !== false)
{ {
if (strpos($entry, 'container_') !== 0 && if (strpos($entry, 'container_') !== 0 &&
strpos($entry, 'url_matcher') !== 0 &&
strpos($entry, 'sql_') !== 0 && strpos($entry, 'sql_') !== 0 &&
strpos($entry, 'data_') !== 0 && strpos($entry, 'data_') !== 0 &&
strpos($entry, 'ctpl_') !== 0 && strpos($entry, 'ctpl_') !== 0 &&

View file

@ -0,0 +1,24 @@
<?php
/**
*
* @package controller
* @copyright (c) 2012 phpBB Group
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
*/
/**
* @ignore
*/
if (!defined('IN_PHPBB'))
{
exit;
}
/**
* Controller exception class
* @package phpBB3
*/
class phpbb_controller_exception extends RuntimeException
{
}

View file

@ -0,0 +1,117 @@
<?php
/**
*
* @package controller
* @copyright (c) 2012 phpBB Group
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
*/
/**
* @ignore
*/
if (!defined('IN_PHPBB'))
{
exit;
}
use Symfony\Component\HttpFoundation\Response;
/**
* Controller helper class, contains methods that do things for controllers
* @package phpBB3
*/
class phpbb_controller_helper
{
/**
* Template object
* @var phpbb_template
*/
protected $template;
/**
* User object
* @var phpbb_user
*/
protected $user;
/**
* phpBB root path
* @var string
*/
protected $phpbb_root_path;
/**
* PHP extension
* @var string
*/
protected $php_ext;
/**
* Constructor
*
* @param phpbb_template $template Template object
* @param phpbb_user $user User object
* @param string $phpbb_root_path phpBB root path
* @param string $php_ext PHP extension
*/
public function __construct(phpbb_template $template, phpbb_user $user, $phpbb_root_path, $php_ext)
{
$this->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);
}
}

View file

@ -0,0 +1,82 @@
<?php
/**
*
* @package controller
* @copyright (c) 2012 phpBB Group
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
*/
/**
* @ignore
*/
if (!defined('IN_PHPBB'))
{
exit;
}
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Loader\YamlFileLoader;
use Symfony\Component\Config\FileLocator;
/**
* Controller interface
* @package phpBB3
*/
class phpbb_controller_provider
{
/**
* YAML file(s) containing route information
* @var array
*/
protected $routing_paths;
/**
* Construct method
*
* @param array() $routing_paths Array of strings containing paths
* to YAML files holding route information
*/
public function __construct($routing_paths = array())
{
$this->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;
}
}

View file

@ -0,0 +1,128 @@
<?php
/**
*
* @package controller
* @copyright (c) 2012 phpBB Group
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
*/
/**
* @ignore
*/
if (!defined('IN_PHPBB'))
{
exit;
}
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Controller manager class
* @package phpBB3
*/
class phpbb_controller_resolver implements ControllerResolverInterface
{
/**
* User object
* @var phpbb_user
*/
protected $user;
/**
* ContainerInterface object
* @var ContainerInterface
*/
protected $container;
/**
* Construct method
*
* @param phpbb_user $user User Object
* @param ContainerInterface $container ContainerInterface object
*/
public function __construct(phpbb_user $user, ContainerInterface $container)
{
$this->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;
}
}

View file

@ -0,0 +1,68 @@
<?php
/**
*
* @package phpBB3
* @copyright (c) 2012 phpBB Group
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
*/
/**
* @ignore
*/
if (!defined('IN_PHPBB'))
{
exit;
}
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
class phpbb_di_pass_kernel_pass implements CompilerPassInterface
{
/**
* Modify the container before it is passed to the rest of the code
*
* @param ContainerBuilder $container ContainerBuilder object
* @return null
*/
public function process(ContainerBuilder $container)
{
$definition = $container->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));
}
}
}

View file

@ -0,0 +1,85 @@
<?php
/**
*
* @package phpBB3
* @copyright (c) 2012 phpBB Group
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
*/
/**
* @ignore
*/
if (!defined('IN_PHPBB'))
{
exit;
}
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpFoundation\Response;
class phpbb_event_kernel_exception_subscriber implements EventSubscriberInterface
{
/**
* Template object
* @var phpbb_template
*/
protected $template;
/**
* User object
* @var phpbb_user
*/
protected $user;
/**
* Construct method
*
* @param phpbb_template $template Template object
* @param phpbb_user $user User object
*/
public function __construct(phpbb_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)
{
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',
);
}
}

View file

@ -0,0 +1,83 @@
<?php
/**
*
* @package phpBB3
* @copyright (c) 2012 phpBB Group
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
*/
/**
* @ignore
*/
if (!defined('IN_PHPBB'))
{
exit;
}
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 phpbb_event_kernel_request_subscriber implements EventSubscriberInterface
{
/**
* Extension finder object
* @var phpbb_extension_finder
*/
protected $finder;
/**
* PHP extension
* @var string
*/
protected $php_ext;
/**
* Root path
* @var string
*/
protected $root_path;
/**
* Construct method
*
* @param phpbb_extension_finder $finder Extension finder object
* @param string $root_path Root path
* @param string $php_ext PHP extension
*/
public function __construct(phpbb_extension_finder $finder, $root_path, $php_ext)
{
$this->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',
);
}
}

View file

@ -0,0 +1,43 @@
<?php
/**
*
* @package phpBB3
* @copyright (c) 2012 phpBB Group
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
*/
/**
* @ignore
*/
if (!defined('IN_PHPBB'))
{
exit;
}
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
class phpbb_event_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 => 'on_kernel_terminate',
);
}
}

View file

@ -7,6 +7,8 @@
* *
*/ */
use Symfony\Component\HttpFoundation\Request;
/** /**
* @ignore * @ignore
*/ */
@ -5231,8 +5233,12 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0
/** /**
* Generate page footer * 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 $db, $config, $template, $user, $auth, $cache, $starttime, $phpbb_root_path, $phpEx;
global $request, $phpbb_dispatcher; 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(); 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; 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);
}

View file

@ -0,0 +1,106 @@
<?php
/**
*
* @package phpBB3
* @copyright (c) 2005 phpBB Group
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
*/
use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
/**
* @ignore
*/
if (!defined('IN_PHPBB'))
{
exit;
}
/**
* Create a new UrlMatcher class and dump it into the cache file
*
* @param phpbb_extension_finder $finder Extension finder
* @param RequestContext $context Symfony RequestContext object
* @param string $root_path Root path
* @param string $php_ext PHP extension
* @return null
*/
function phpbb_get_url_matcher(phpbb_extension_finder $finder, RequestContext $context, $root_path, $php_ext)
{
if (defined('DEBUG'))
{
return phpbb_create_url_matcher($finder, $context);
}
if (!phpbb_url_matcher_dumped($root_path, $php_ext))
{
phpbb_create_dumped_url_matcher($finder, $root_path, $php_ext);
}
return phpbb_load_url_matcher($context, $root_path, $php_ext);
}
/**
* Create a new UrlMatcher class and dump it into the cache file
*
* @param phpbb_extension_finder $finder Extension finder
* @param string $root_path Root path
* @param string $php_ext PHP extension
* @return null
*/
function phpbb_create_dumped_url_matcher(phpbb_extension_finder $finder, $root_path, $php_ext)
{
$provider = new phpbb_controller_provider();
$routes = $provider->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);
}

View file

@ -17,48 +17,12 @@ define('IN_PHPBB', true);
$phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './'; $phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './';
$phpEx = substr(strrchr(__FILE__, '.'), 1); $phpEx = substr(strrchr(__FILE__, '.'), 1);
include($phpbb_root_path . 'common.' . $phpEx); include($phpbb_root_path . 'common.' . $phpEx);
include($phpbb_root_path . 'includes/functions_display.' . $phpEx);
// Start session management // Start session management
$user->session_begin(); $user->session_begin();
$auth->acl($user->data); $auth->acl($user->data);
$user->setup(); $user->setup('viewforum');
// 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');
display_forums('', $config['load_moderators']); display_forums('', $config['load_moderators']);

View file

@ -113,6 +113,7 @@ $phpbb_container = phpbb_create_dumped_container_unless_debug(
), ),
array( array(
new phpbb_di_pass_collection_pass(), new phpbb_di_pass_collection_pass(),
new phpbb_di_pass_kernel_pass(),
), ),
$phpbb_root_path, $phpbb_root_path,
$phpEx $phpEx

49
phpBB/language/en/app.php Normal file
View file

@ -0,0 +1,49 @@
<?php
/**
*
* app [English]
*
* @package language
* @copyright (c) 2012 phpBB Group
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
*/
/**
* DO NOT CHANGE
*/
if (!defined('IN_PHPBB'))
{
exit;
}
if (empty($lang) || !is_array($lang))
{
$lang = array();
}
// DEVELOPERS PLEASE NOTE
//
// All language files should use UTF-8 as their encoding and the files must not contain a BOM.
//
// Placeholders can now contain order information, e.g. instead of
// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows
// translators to re-order the output of data while ensuring it remains correct
//
// You do not need this where single placeholders are used, e.g. 'Message %d' is fine
// equally where a string contains only two placeholders which are used to wrap text
// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine
//
// Some characters you may want to copy&paste:
// » “ ” …
//
$lang = array_merge($lang, array(
'CONTROLLER_ARGUMENT_VALUE_MISSING' => 'Missing value for argument #%1$s: <strong>%3$s</strong> in class <strong>%2$s</strong>',
'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 "<strong>%s</strong>" must have a service specified in ./config/routing.yml.',
'CONTROLLER_SERVICE_UNDEFINED' => 'The service for controller "<strong>%s</strong>" is not defined in ./config/services.yml.',
'CONTROLLER_RETURN_TYPE_INVALID' => 'The controller object <strong>%s</strong> must return a Symfony\Component\HttpFoundation\Response object.',
));

View file

@ -0,0 +1,3 @@
core_controller:
pattern: /core_foo
defaults: { _controller: core_foo.controller:bar }

View file

@ -0,0 +1,3 @@
services:
core_foo.controller:
class: phpbb_controller_foo

View file

@ -0,0 +1,76 @@
<?php
/**
*
* @package testing
* @copyright (c) 2012 phpBB Group
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
*/
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
class phpbb_controller_test extends phpbb_test_case
{
public function setUp()
{
$this->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'));
}
}

View file

@ -0,0 +1,3 @@
controller1:
pattern: /foo
defaults: { _controller: foo.controller:handle }

View file

@ -0,0 +1,3 @@
services:
foo.controller:
class: phpbb_ext_foo_controller

View file

@ -0,0 +1,16 @@
<?php
use Symfony\Component\HttpFoundation\Response;
class phpbb_ext_foo_controller
{
/**
* Handle method
*
* @return null
*/
public function handle()
{
return new Response('Test', 200);
}
}

View file

@ -0,0 +1,16 @@
<?php
use Symfony\Component\HttpFoundation\Response;
class phpbb_controller_foo
{
/**
* Bar method
*
* @return null
*/
public function bar()
{
return new Response('bar()', 200);
}
}

View file

@ -13,6 +13,14 @@
class phpbb_functional_extension_controller_test extends phpbb_functional_test_case class phpbb_functional_extension_controller_test extends phpbb_functional_test_case
{ {
protected $phpbb_extension_manager; protected $phpbb_extension_manager;
static protected $fixtures = array(
'foo/bar/config/routing.yml',
'foo/bar/config/services.yml',
'foo/bar/controller/controller.php',
'foo/bar/styles/prosilver/template/foo_bar_body.html',
);
/** /**
* This should only be called once before the tests are run. * This should only be called once before the tests are run.
* This is used to copy the fixtures to the phpBB install * This is used to copy the fixtures to the phpBB install
@ -22,15 +30,11 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c
global $phpbb_root_path; global $phpbb_root_path;
parent::setUpBeforeClass(); parent::setUpBeforeClass();
// these directories need to be created before the files can be copied
$directories = array( $directories = array(
$phpbb_root_path . 'ext/error/class/',
$phpbb_root_path . 'ext/error/classtype/',
$phpbb_root_path . 'ext/error/disabled/',
$phpbb_root_path . 'ext/foo/bar/', $phpbb_root_path . 'ext/foo/bar/',
$phpbb_root_path . 'ext/foo/bar/styles/prosilver/template/', $phpbb_root_path . 'ext/foo/bar/config/',
$phpbb_root_path . 'ext/foobar/', $phpbb_root_path . 'ext/foo/bar/controller/',
$phpbb_root_path . 'ext/foobar/styles/prosilver/template/', $phpbb_root_path . 'ext/foo/bar/styles/prosilver/template',
); );
foreach ($directories as $dir) foreach ($directories as $dir)
@ -41,30 +45,36 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c
} }
} }
$fixtures = array( foreach (self::$fixtures as $fixture)
'error/class/controller.php',
'error/class/ext.php',
'error/classtype/controller.php',
'error/classtype/ext.php',
'error/disabled/controller.php',
'error/disabled/ext.php',
'foo/bar/controller.php',
'foo/bar/ext.php',
'foo/bar/styles/prosilver/template/foobar_body.html',
'foobar/controller.php',
'foobar/ext.php',
'foobar/styles/prosilver/template/foobar_body.html',
);
foreach ($fixtures as $fixture)
{ {
if (!copy("tests/functional/fixtures/ext/$fixture", "{$phpbb_root_path}ext/$fixture")) copy(
{ "tests/functional/fixtures/ext/$fixture",
echo 'Could not copy file ' . $fixture; "{$phpbb_root_path}ext/$fixture");
}
} }
} }
/**
* This should only be called once after the tests are run.
* This is used to remove the fixtures from the phpBB install
*/
static public function tearDownAfterClass()
{
global $phpbb_root_path;
foreach (self::$fixtures as $fixture)
{
unlink("{$phpbb_root_path}ext/$fixture");
}
rmdir("{$phpbb_root_path}ext/foo/bar/config");
rmdir("{$phpbb_root_path}ext/foo/bar/controller");
rmdir("{$phpbb_root_path}ext/foo/bar/styles/prosilver/template");
rmdir("{$phpbb_root_path}ext/foo/bar/styles/prosilver");
rmdir("{$phpbb_root_path}ext/foo/bar/styles");
rmdir("{$phpbb_root_path}ext/foo/bar");
rmdir("{$phpbb_root_path}ext/foo");
}
public function setUp() public function setUp()
{ {
parent::setUp(); parent::setUp();
@ -75,72 +85,67 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c
} }
/** /**
* Check an extension at ./ext/foobar/ which should have the class * Check a controller for extension foo/bar.
* phpbb_ext_foobar_controller
*/
public function test_foobar()
{
$this->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
*/ */
public function test_foo_bar() public function test_foo_bar()
{ {
$this->phpbb_extension_manager->enable('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->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'); $this->phpbb_extension_manager->purge('foo/bar');
} }
/** /**
* Check the error produced by extension at ./ext/error/class which has class * Check the output of a controller using the template system
* phpbb_ext_foobar_controller
*/ */
public function test_error_class_name() public function test_controller_with_template()
{ {
$this->phpbb_extension_manager->enable('error/class'); $this->phpbb_extension_manager->enable('foo/bar');
$crawler = $this->request('GET', 'index.php?ext=error/class'); $crawler = $this->request('GET', 'app.php?controller=foo/template');
$this->assertContains("The extension error/class is missing a controller class and cannot be accessed through the front-end.", $crawler->filter('#message')->text()); $this->assert_response_success();
$this->phpbb_extension_manager->purge('error/class'); $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 * Check the error produced by calling a controller without a required
* phpbb_ext_error_classtype_controller but does not implement phpbb_extension_controller_interface * argument.
*/ */
public function test_error_class_type() public function test_missing_argument()
{ {
$this->phpbb_extension_manager->enable('error/classtype'); $this->phpbb_extension_manager->enable('foo/bar');
$crawler = $this->request('GET', 'index.php?ext=error/classtype'); $crawler = $this->request('GET', 'app.php?controller=foo/baz');
$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->assertEquals(500, $this->client->getResponse()->getStatus());
$this->phpbb_extension_manager->purge('error/classtype'); $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) * Check the status code resulting from an exception thrown by a controller
* a disabled extension
*/ */
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->phpbb_extension_manager->enable('foo/bar');
$this->assertContains("The extension error/disabled is not enabled", $crawler->filter('#message')->text()); $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) * Check the error produced by extension at ./ext/does/not/exist.
* not existant *
* 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'); $crawler = $this->request('GET', 'app.php?controller=does/not/exist');
$this->assertContains("The extension error/404 does not exist.", $crawler->filter('#message')->text()); $this->assertEquals(404, $this->client->getResponse()->getStatus());
$this->assertContains('No route found for "GET /does/not/exist"', $crawler->filter('body')->text());
} }
} }

View file

@ -1,14 +0,0 @@
<?php
class phpbb_ext_foobar_controller extends phpbb_extension_controller
{
public function handle()
{
$this->template->set_filenames(array(
'body' => 'index_body.html'
));
page_header('Test extension');
page_footer();
}
}

View file

@ -1,6 +0,0 @@
<?php
class phpbb_ext_error_class_ext extends phpbb_extension_base
{
}

View file

@ -1,15 +0,0 @@
<?php
class phpbb_ext_error_classtype_controller
{
public function handle()
{
global $template;
$template->set_filenames(array(
'body' => 'index_body.html'
));
page_header('Test extension');
page_footer();
}
}

View file

@ -1,6 +0,0 @@
<?php
class phpbb_ext_error_classtype_ext extends phpbb_extension_base
{
}

View file

@ -1,14 +0,0 @@
<?php
class phpbb_ext_error_disabled_controller extends phpbb_extension_controller
{
public function handle()
{
$this->template->set_filenames(array(
'body' => 'index_body.html'
));
page_header('Test extension');
page_footer();
}
}

View file

@ -1,6 +0,0 @@
<?php
class phpbb_ext_error_disabled_ext extends phpbb_extension_base
{
}

View file

@ -0,0 +1,15 @@
foo_bar_controller:
pattern: /foo/bar
defaults: { _controller: foo_bar.controller:handle }
foo_baz_controller:
pattern: /foo/baz
defaults: { _controller: foo_bar.controller:baz }
foo_template_controller:
pattern: /foo/template
defaults: { _controller: foo_bar.controller:template }
foo_exception_controller:
pattern: /foo/exception
defaults: { _controller: foo_bar.controller:exception }

View file

@ -0,0 +1,6 @@
services:
foo_bar.controller:
class: phpbb_ext_foo_bar_controller
arguments:
- @controller.helper
- @template

View file

@ -1,14 +0,0 @@
<?php
class phpbb_ext_foo_bar_controller extends phpbb_extension_controller
{
public function handle()
{
$this->template->set_filenames(array(
'body' => 'foobar_body.html'
));
page_header('Test extension');
page_footer();
}
}

View file

@ -0,0 +1,35 @@
<?php
use Symfony\Component\HttpFoundation\Response;
class phpbb_ext_foo_bar_controller
{
protected $template;
public function __construct(phpbb_controller_helper $helper, phpbb_template $template)
{
$this->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');
}
}

View file

@ -2,5 +2,5 @@
class phpbb_ext_foo_bar_ext extends phpbb_extension_base class phpbb_ext_foo_bar_ext extends phpbb_extension_base
{ {
} }

View file

@ -1,5 +1,3 @@
<!-- INCLUDE overall_header.html --> <!-- INCLUDE overall_header.html -->
<div id="content">{A_VARIABLE}</div>
<div id="welcome">This is for testing purposes.</div>
<!-- INCLUDE overall_footer.html --> <!-- INCLUDE overall_footer.html -->

View file

@ -1,14 +0,0 @@
<?php
class phpbb_ext_foobar_controller extends phpbb_extension_controller
{
public function handle()
{
$this->template->set_filenames(array(
'body' => 'foobar_body.html'
));
page_header('Test extension');
page_footer();
}
}

View file

@ -1,6 +0,0 @@
<?php
class phpbb_ext_foobar_ext extends phpbb_extension_base
{
}

View file

@ -1,5 +0,0 @@
<!-- INCLUDE overall_header.html -->
<div id="welcome">This is for testing purposes.</div>
<!-- INCLUDE overall_footer.html -->