Current File : /home/pacjaorg/pacjaorg/cop.pacja.org/libraries/fof40/Dispatcher/Dispatcher.php |
<?php
/**
* @package FOF
* @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace FOF40\Dispatcher;
defined('_JEXEC') || die;
use Exception;
use FOF40\Container\Container;
use FOF40\Controller\Controller;
use FOF40\Dispatcher\Exception\AccessForbidden;
use FOF40\TransparentAuthentication\TransparentAuthentication;
/**
* A generic MVC dispatcher
*
* @property-read \FOF40\Input\Input $input The input object (magic __get returns the Input from the Container)
*/
class Dispatcher
{
/** @var string The name of the default view, in case none is specified */
public $defaultView;
/** @var array Local cache of the dispatcher configuration */
protected $config = [];
/** @var Container The container we belong to */
protected $container;
/** @var string The view which will be rendered by the dispatcher */
protected $view;
/** @var string The layout for rendering the view */
protected $layout;
/** @var Controller The controller which will be used */
protected $controller;
/** @var bool Is this user transparently logged in? */
protected $isTransparentlyLoggedIn = false;
/**
* Public constructor
*
* The $config array can contain the following optional values:
* defaultView string The view to render if none is specified in $input
*
* Do note that $config is passed to the Controller and through it to the Model and View. Please see these classes
* for more information on the configuration variables they accept.
*
* @param Container $container
* @param array $config
*/
public function __construct(Container $container, array $config = [])
{
$this->container = $container;
$this->config = $config;
$this->defaultView = $container->appConfig->get('dispatcher.defaultView', $this->defaultView);
if (isset($config['defaultView']))
{
$this->defaultView = $config['defaultView'];
}
$this->supportCustomViewAndTaskParameters();
// Get the default values for the view and layout names
$this->view = $this->input->getCmd('view', null);
$this->layout = $this->input->getCmd('layout', null);
// Not redundant; you may pass an empty but non-null view which is invalid, so we need the fallback
if (empty($this->view))
{
$this->view = $this->defaultView;
$this->container->input->set('view', $this->view);
}
}
/**
* Magic get method. Handles magic properties:
* $this->input mapped to $this->container->input
*
* @param string $name The property to fetch
*
* @return mixed|null
*/
public function __get(string $name)
{
// Handle $this->input
if ($name == 'input')
{
return $this->container->input;
}
// Property not found; raise error
$trace = debug_backtrace();
trigger_error(
'Undefined property via __get(): ' . $name .
' in ' . $trace[0]['file'] .
' on line ' . $trace[0]['line'],
E_USER_NOTICE);
return null;
}
/**
* The main code of the Dispatcher. It spawns the necessary controller and
* runs it.
*
* @return void
*
* @throws AccessForbidden When the access is forbidden
* @throws Exception For displaying an error page
*/
public function dispatch(): void
{
// Load the translations for this component;
$this->container->platform->loadTranslations($this->container->componentName);
// Perform transparent authentication
if ($this->container->platform->getUser()->guest)
{
$this->transparentAuthenticationLogin();
}
// Get the event names (different for CLI)
$onBeforeEventName = 'onBeforeDispatch';
$onAfterEventName = 'onAfterDispatch';
if ($this->container->platform->isCli())
{
$onBeforeEventName = 'onBeforeDispatchCLI';
$onAfterEventName = 'onAfterDispatchCLI';
}
try
{
$result = $this->triggerEvent($onBeforeEventName);
$error = '';
}
catch (\Exception $e)
{
$result = false;
$error = $e->getMessage();
}
if ($result === false)
{
if ($this->container->platform->isCli())
{
$this->container->platform->setHeader('Status', '403 Forbidden', true);
}
$this->transparentAuthenticationLogout();
$this->container->platform->showErrorPage(new AccessForbidden);
}
// Get and execute the controller
$view = $this->input->getCmd('view', $this->defaultView);
$task = $this->input->getCmd('task', 'default');
if (empty($task))
{
$task = 'default';
$this->input->set('task', $task);
}
try
{
$this->controller = $this->container->factory->controller($view, $this->config);
$status = $this->controller->execute($task);
}
catch (Exception $e)
{
$this->container->platform->showErrorPage($e);
// Redundant; just to make code sniffers happy
return;
}
if ($status !== false)
{
try
{
$this->triggerEvent($onAfterEventName);
}
catch (\Exception $e)
{
$status = false;
}
}
if ($status === false)
{
if ($this->container->platform->isCli())
{
$this->container->platform->setHeader('Status', '403 Forbidden', true);
}
$this->transparentAuthenticationLogout();
$this->container->platform->showErrorPage(new AccessForbidden);
}
$this->transparentAuthenticationLogout();
$this->controller->redirect();
}
/**
* Returns a reference to the Controller object currently in use by the dispatcher
*
* @return Controller|null
*/
public function &getController(): ?Controller
{
return $this->controller;
}
/**
* Triggers an object-specific event. The event runs both locally –if a suitable method exists– and through the
* Joomla! plugin system. A true/false return value is expected. The first false return cancels the event.
*
* EXAMPLE
* Component: com_foobar, Object name: item, Event: onBeforeDispatch, Arguments: array(123, 456)
* The event calls:
* 1. $this->onBeforeDispatch(123, 456)
* 2. Joomla! plugin event onComFoobarDispatcherBeforeDispatch($this, 123, 456)
*
* @param string $event The name of the event, typically named onPredicateVerb e.g. onBeforeKick
* @param array $arguments The arguments to pass to the event handlers
*
* @return bool
*/
protected function triggerEvent(string $event, array $arguments = []): bool
{
$result = true;
// If there is an object method for this event, call it
if (method_exists($this, $event))
{
$result = $this->{$event}(...$arguments);
}
if ($result === false)
{
return false;
}
// All other event handlers live outside this object, therefore they need to be passed a reference to this
// objects as the first argument.
array_unshift($arguments, $this);
// If we have an "on" prefix for the event (e.g. onFooBar) remove it and stash it for later.
$prefix = '';
if (substr($event, 0, 2) == 'on')
{
$prefix = 'on';
$event = substr($event, 2);
}
// Get the component/model prefix for the event
$prefix .= 'Com' . ucfirst($this->container->bareComponentName) . 'Dispatcher';
// The event name will be something like onComFoobarItemsBeforeSomething
$event = $prefix . $event;
// Call the Joomla! plugins
$results = $this->container->platform->runPlugins($event, $arguments);
return !in_array(false, $results, true);
}
/**
* Handles the transparent authentication log in
*/
protected function transparentAuthenticationLogin(): void
{
/** @var TransparentAuthentication $transparentAuth */
$transparentAuth = $this->container->transparentAuth;
$authInfo = $transparentAuth->getTransparentAuthenticationCredentials();
if (empty($authInfo))
{
return;
}
$this->isTransparentlyLoggedIn = $this->container->platform->loginUser($authInfo);
}
/**
* Handles the transparent authentication log out
*/
protected function transparentAuthenticationLogout(): void
{
if (!$this->isTransparentlyLoggedIn)
{
return;
}
/** @var TransparentAuthentication $transparentAuth */
$transparentAuth = $this->container->transparentAuth;
if (!$transparentAuth->getLogoutOnExit())
{
return;
}
$this->container->platform->logoutUser();
}
/**
* Adds support for akview/aktask in lieu of view and task.
*
* This is for future-proofing FOF in case Joomla assigns special meaning to view and task, e.g. by trying to find a
* specific controller / task class instead of letting the component's front-end router handle it. If that happens
* FOF components can have a single Joomla-compatible view/task which launches the Dispatcher and perform internal
* routing using akview/aktask.
*
* @return void
* @since 3.6.3
*/
private function supportCustomViewAndTaskParameters()
{
$view = $this->input->getCmd('akview', null);
$task = $this->input->getCmd('aktask', null);
if (!is_null($view))
{
$this->input->remove('akview');
$this->input->set('view', $view);
}
if (!is_null($task))
{
$this->input->remove('aktask');
$this->input->set('task', $task);
}
}
}