primo commit

This commit is contained in:
2024-12-17 17:34:10 +01:00
commit e650f8df99
16435 changed files with 2451012 additions and 0 deletions

View File

@ -0,0 +1,381 @@
<?php
/**
* @package FOF
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 2, or later
*/
namespace FOF30\Dispatcher;
defined('_JEXEC') || die;
use Exception;
use FOF30\Container\Container;
use FOF30\Controller\Controller;
use FOF30\Dispatcher\Exception\AccessForbidden;
use FOF30\TransparentAuthentication\TransparentAuthentication;
/**
* A generic MVC dispatcher
*
* @property-read \FOF30\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 = null;
/** @var array Local cache of the dispatcher configuration */
protected $config = [];
/** @var Container The container we belong to */
protected $container = null;
/** @var string The view which will be rendered by the dispatcher */
protected $view = null;
/** @var string The layout for rendering the view */
protected $layout = null;
/** @var Controller The controller which will be used */
protected $controller = null;
/** @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 \FOF30\Container\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($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
*/
public function dispatch()
{
// 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
*/
public function &getController()
{
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($event, array $arguments = [])
{
$result = true;
// If there is an object method for this event, call it
if (method_exists($this, $event))
{
switch (count($arguments))
{
case 0:
$result = $this->{$event}();
break;
case 1:
$result = $this->{$event}($arguments[0]);
break;
case 2:
$result = $this->{$event}($arguments[0], $arguments[1]);
break;
case 3:
$result = $this->{$event}($arguments[0], $arguments[1], $arguments[2]);
break;
case 4:
$result = $this->{$event}($arguments[0], $arguments[1], $arguments[2], $arguments[3]);
break;
case 5:
$result = $this->{$event}($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4]);
break;
default:
$result = call_user_func_array([$this, $event], $arguments);
break;
}
}
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);
if (!empty($results))
{
foreach ($results as $result)
{
if ($result === false)
{
return false;
}
}
}
return true;
}
/**
* Handles the transparent authentication log in
*/
protected function transparentAuthenticationLogin()
{
/** @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()
{
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);
}
}
}

View File

@ -0,0 +1,31 @@
<?php
/**
* @package FOF
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 2, or later
*/
namespace FOF30\Dispatcher\Exception;
defined('_JEXEC') || die;
use Exception;
use Joomla\CMS\Language\Text;
use RuntimeException;
/**
* Exception thrown when the access to the requested resource is forbidden under the current execution context
*/
class AccessForbidden extends RuntimeException
{
public function __construct($message = "", $code = 403, Exception $previous = null)
{
if (empty($message))
{
$message = Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN');
}
parent::__construct($message, $code, $previous);
}
}

View File

@ -0,0 +1,81 @@
<?php
/**
* @package FOF
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 2, or later
*/
namespace FOF30\Dispatcher\Mixin;
defined('_JEXEC') || die;
use Joomla\CMS\Uri\Uri;
/**
* Lets you create view aliases. When you access a view alias the real view is loaded instead. You can optionally have
* an HTTPS 301 redirection for GET requests to URLs that use the view name alias.
*
* IMPORTANT: This is a mixin (or, as we call it in PHP, a trait). Traits require PHP 5.4 or later. If you opt to use
* this trait your component will no longer work under PHP 5.3.
*
* Usage:
*
* • Override $viewNameAliases with your view names map.
* • If you want to issue HTTP 301 for GET requests set $permanentAliasRedirectionOnGET to true.
* • If you have an onBeforeDispatch method remember to alias and call this traits' onBeforeDispatch method at the top.
*
* Regarding the last point, if you've never used traits before, the code looks like this. Top of the class:
* use ViewAliases {
* onBeforeDispatch as onBeforeDispatchViewAliases;
* }
* and inside your custom onBeforeDispatch method, the first statement should be:
* $this->onBeforeDispatchViewAliases();
* Simple!
*/
trait ViewAliases
{
/**
* Maps view name aliases to actual views. The format is 'alias' => 'RealView'.
*
* @var array
*/
protected $viewNameAliases = [];
/**
* If set to true, any GET request to the alias view will result in an HTTP 301 permanent redirection to the real
* view name.
*
* This does NOT apply to POST, PUT, DELETE etc URLs. When you submit form data you cannot have a redirection. The
* browser will _typically_ not resend the submitted data.
*
* @var bool
*/
protected $permanentAliasRedirectionOnGET = false;
/**
* Transparently replaces old view names with their counterparts.
*
* If you are overriding this method in your component remember to alias it and call it from your overridden method.
*/
protected function onBeforeDispatch()
{
if (!array_key_exists($this->view, $this->viewNameAliases))
{
return;
}
$this->view = $this->viewNameAliases[$this->view];
$this->container->input->set('view', $this->view);
// Perform HTTP 301 Moved permanently redirection on GET requests if requested to do so
if ($this->permanentAliasRedirectionOnGET && isset($_SERVER['REQUEST_METHOD'])
&& (strtoupper($_SERVER['REQUEST_METHOD']) == 'GET')
)
{
$url = Uri::getInstance();
$url->setVar('view', $this->view);
$this->container->platform->redirect($url, 301);
}
}
}