first commit

This commit is contained in:
2025-06-17 11:53:18 +02:00
commit 9f0f7ba12b
8804 changed files with 1369176 additions and 0 deletions

View File

@ -0,0 +1,214 @@
<?php
/**
* Part of the Joomla Framework Application Package
*
* @copyright (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Application;
use Joomla\Application\Event\ApplicationErrorEvent;
use Joomla\Application\Event\ApplicationEvent;
use Joomla\Event\DispatcherAwareInterface;
use Joomla\Event\DispatcherAwareTrait;
use Joomla\Event\EventInterface;
use Joomla\Registry\Registry;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
/**
* Joomla Framework Base Application Class
*
* @since 1.0.0
*/
abstract class AbstractApplication implements
ConfigurationAwareApplicationInterface,
LoggerAwareInterface,
DispatcherAwareInterface
{
use LoggerAwareTrait;
use DispatcherAwareTrait;
/**
* The application configuration object.
*
* @var Registry
* @since 1.0.0
*/
protected $config;
/**
* Class constructor.
*
* @param Registry|null $config An optional argument to provide dependency injection for the
* application's config object. If the argument is a Registry
* object that object will become the application's config object,
* otherwise a default config object is created.
*
* @since 1.0.0
*/
public function __construct(Registry $config = null)
{
$this->config = $config ?: new Registry();
// Set the execution datetime and timestamp;
$this->set('execution.datetime', \gmdate('Y-m-d H:i:s'));
$this->set('execution.timestamp', \time());
$this->set('execution.microtimestamp', \microtime(true));
$this->initialise();
}
/**
* Method to close the application.
*
* @param integer $code The exit code (optional; default is 0).
*
* @return void
*
* @codeCoverageIgnore
* @since 1.0.0
*/
public function close($code = 0)
{
exit($code);
}
/**
* Dispatches an application event if the dispatcher has been set.
*
* @param string $eventName The event to dispatch.
* @param EventInterface|null $event The event object.
*
* @return EventInterface|null The dispatched event or null if no dispatcher is set
*
* @since 2.0.0
*/
protected function dispatchEvent(string $eventName, ?EventInterface $event = null): ?EventInterface
{
try {
$dispatcher = $this->getDispatcher();
} catch (\UnexpectedValueException $exception) {
return null;
}
return $dispatcher->dispatch($eventName, $event ?: new ApplicationEvent($eventName, $this));
}
/**
* Method to run the application routines.
*
* Most likely you will want to instantiate a controller and execute it, or perform some sort of task directly.
*
* @return mixed
*
* @since 1.0.0
*/
abstract protected function doExecute();
/**
* Execute the application.
*
* @return void
*
* @since 1.0.0
*/
public function execute()
{
try {
$this->dispatchEvent(ApplicationEvents::BEFORE_EXECUTE);
// Perform application routines.
$this->doExecute();
$this->dispatchEvent(ApplicationEvents::AFTER_EXECUTE);
} catch (\Throwable $throwable) {
$this->dispatchEvent(ApplicationEvents::ERROR, new ApplicationErrorEvent($throwable, $this));
}
}
/**
* Returns a property of the object or the default value if the property is not set.
*
* @param string $key The name of the property.
* @param mixed $default The default value (optional) if none is set.
*
* @return mixed The value of the configuration.
*
* @since 1.0.0
*/
public function get($key, $default = null)
{
return $this->config->get($key, $default);
}
/**
* Get the logger.
*
* @return LoggerInterface
*
* @since 1.0.0
*/
public function getLogger()
{
// If a logger hasn't been set, use NullLogger
if (!($this->logger instanceof LoggerInterface)) {
$this->setLogger(new NullLogger());
}
return $this->logger;
}
/**
* Custom initialisation method.
*
* Called at the end of the AbstractApplication::__construct method.
* This is for developers to inject initialisation code for their application classes.
*
* @return void
*
* @codeCoverageIgnore
* @since 1.0.0
*/
protected function initialise()
{
}
/**
* Modifies a property of the object, creating it if it does not already exist.
*
* @param string $key The name of the property.
* @param mixed $value The value of the property to set (optional).
*
* @return mixed Previous value of the property
*
* @since 1.0.0
*/
public function set($key, $value = null)
{
$previous = $this->config->get($key);
$this->config->set($key, $value);
return $previous;
}
/**
* Sets the configuration for the application.
*
* @param Registry $config A registry object holding the configuration.
*
* @return $this
*
* @since 1.0.0
*/
public function setConfiguration(Registry $config)
{
$this->config = $config;
return $this;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,60 @@
<?php
/**
* Part of the Joomla Framework Application Package
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Application;
/**
* Class defining the events available in the application.
*
* @since 2.0.0
*/
final class ApplicationEvents
{
/**
* The ERROR event is an event triggered when a Throwable is uncaught.
*
* This event allows you to inspect the Throwable and implement additional error handling/reporting mechanisms.
*
* @var string
* @since 2.0.0
*/
public const ERROR = 'application.error';
/**
* The BEFORE_EXECUTE event is an event triggered before the application is executed.
*
* @var string
* @since 2.0.0
*/
public const BEFORE_EXECUTE = 'application.before_execute';
/**
* The AFTER_EXECUTE event is an event triggered after the application is executed.
*
* @var string
* @since 2.0.0
*/
public const AFTER_EXECUTE = 'application.after_execute';
/**
* The BEFORE_RESPOND event is an event triggered before the application response is sent.
*
* @var string
* @since 2.0.0
*/
public const BEFORE_RESPOND = 'application.before_respond';
/**
* The AFTER_RESPOND event is an event triggered after the application response is sent.
*
* @var string
* @since 2.0.0
*/
public const AFTER_RESPOND = 'application.after_respond';
}

View File

@ -0,0 +1,38 @@
<?php
/**
* Part of the Joomla Framework Application Package
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Application;
/**
* Joomla Framework Application Interface
*
* @since 2.0.0
*/
interface ApplicationInterface
{
/**
* Method to close the application.
*
* @param integer $code The exit code (optional; default is 0).
*
* @return void
*
* @since 2.0.0
*/
public function close($code = 0);
/**
* Execute the application.
*
* @return void
*
* @since 2.0.0
*/
public function execute();
}

View File

@ -0,0 +1,55 @@
<?php
/**
* Part of the Joomla Framework Application Package
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Application;
use Joomla\Registry\Registry;
/**
* Application sub-interface defining an application class which is aware of its configuration
*
* @since 2.0.0
*/
interface ConfigurationAwareApplicationInterface extends ApplicationInterface
{
/**
* Returns a property of the object or the default value if the property is not set.
*
* @param string $key The name of the property.
* @param mixed $default The default value (optional) if none is set.
*
* @return mixed The value of the configuration.
*
* @since 2.0.0
*/
public function get($key, $default = null);
/**
* Modifies a property of the object, creating it if it does not already exist.
*
* @param string $key The name of the property.
* @param mixed $value The value of the property to set (optional).
*
* @return mixed Previous value of the property
*
* @since 2.0.0
*/
public function set($key, $value = null);
/**
* Sets the configuration for the application.
*
* @param Registry $config A registry object holding the configuration.
*
* @return $this
*
* @since 2.0.0
*/
public function setConfiguration(Registry $config);
}

View File

@ -0,0 +1,60 @@
<?php
/**
* Part of the Joomla Framework Application Package
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Application\Controller;
use Psr\Container\ContainerInterface;
/**
* Controller resolver which supports creating controllers from a PSR-11 compatible container
*
* Controllers must be registered in the container using their FQCN as a service key
*
* @since 2.0.0
*/
class ContainerControllerResolver extends ControllerResolver
{
/**
* The container to search for controllers in
*
* @var ContainerInterface
* @since 2.0.0
*/
private $container;
/**
* Constructor
*
* @param ContainerInterface $container The container to search for controllers in
*
* @since 2.0.0
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* Instantiate a controller class
*
* @param string $class The class to instantiate
*
* @return object Controller class instance
*
* @since 2.0.0
*/
protected function instantiateController(string $class): object
{
if ($this->container->has($class)) {
return $this->container->get($class);
}
return parent::instantiateController($class);
}
}

View File

@ -0,0 +1,123 @@
<?php
/**
* Part of the Joomla Framework Application Package
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Application\Controller;
use Joomla\Controller\ControllerInterface;
use Joomla\Router\ResolvedRoute;
/**
* Resolves a controller for the given route.
*
* @since 2.0.0
*/
class ControllerResolver implements ControllerResolverInterface
{
/**
* Resolve the controller for a route
*
* @param ResolvedRoute $route The route to resolve the controller for
*
* @return callable
*
* @throws \InvalidArgumentException
* @since 2.0.0
*/
public function resolve(ResolvedRoute $route): callable
{
$controller = $route->getController();
// Try to resolve a callable defined as an array
if (\is_array($controller)) {
if (isset($controller[0]) && \is_string($controller[0]) && isset($controller[1])) {
if (!\class_exists($controller[0])) {
throw new \InvalidArgumentException(
\sprintf('Cannot resolve controller for URI `%s`', $route->getUri())
);
}
try {
$controller[0] = $this->instantiateController($controller[0]);
} catch (\ArgumentCountError $error) {
throw new \InvalidArgumentException(
\sprintf(
'Controller `%s` has required constructor arguments, cannot instantiate the class',
$controller[0]
),
0,
$error
);
}
}
if (!\is_callable($controller)) {
throw new \InvalidArgumentException(
\sprintf('Cannot resolve controller for URI `%s`', $route->getUri())
);
}
return $controller;
}
// Try to resolve an invocable object
if (\is_object($controller)) {
if (!\is_callable($controller)) {
throw new \InvalidArgumentException(
\sprintf('Cannot resolve controller for URI `%s`', $route->getUri())
);
}
return $controller;
}
// Try to resolve a known function
if (\function_exists($controller)) {
return $controller;
}
// Try to resolve a class name if it implements our ControllerInterface
if (\is_string($controller) && \interface_exists(ControllerInterface::class)) {
if (!\class_exists($controller)) {
throw new \InvalidArgumentException(
\sprintf('Cannot resolve controller for URI `%s`', $route->getUri())
);
}
try {
return [$this->instantiateController($controller), 'execute'];
} catch (\ArgumentCountError $error) {
throw new \InvalidArgumentException(
\sprintf(
'Controller `%s` has required constructor arguments, cannot instantiate the class',
$controller
),
0,
$error
);
}
}
// Unsupported resolution
throw new \InvalidArgumentException(\sprintf('Cannot resolve controller for URI `%s`', $route->getUri()));
}
/**
* Instantiate a controller class
*
* @param string $class The class to instantiate
*
* @return object Controller class instance
*
* @since 2.0.0
*/
protected function instantiateController(string $class): object
{
return new $class();
}
}

View File

@ -0,0 +1,32 @@
<?php
/**
* Part of the Joomla Framework Application Package
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Application\Controller;
use Joomla\Router\ResolvedRoute;
/**
* Interface defining a controller resolver.
*
* @since 2.0.0
*/
interface ControllerResolverInterface
{
/**
* Resolve the controller for a route
*
* @param ResolvedRoute $route The route to resolve the controller for
*
* @return callable
*
* @since 2.0.0
* @throws \InvalidArgumentException
*/
public function resolve(ResolvedRoute $route): callable;
}

View File

@ -0,0 +1,70 @@
<?php
/**
* Part of the Joomla Framework Application Package
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Application\Event;
use Joomla\Application\AbstractApplication;
use Joomla\Application\ApplicationEvents;
/**
* Event class thrown when an application error occurs.
*
* @since 2.0.0
*/
class ApplicationErrorEvent extends ApplicationEvent
{
/**
* The Throwable object with the error data.
*
* @var \Throwable
* @since 2.0.0
*/
private $error;
/**
* Event constructor.
*
* @param \Throwable $error The Throwable object with the error data.
* @param AbstractApplication $application The active application.
*
* @since 2.0.0
*/
public function __construct(\Throwable $error, AbstractApplication $application)
{
parent::__construct(ApplicationEvents::ERROR, $application);
$this->error = $error;
}
/**
* Get the error object.
*
* @return \Throwable
*
* @since 2.0.0
*/
public function getError(): \Throwable
{
return $this->error;
}
/**
* Set the error object.
*
* @param \Throwable $error The error object to set to the event.
*
* @return void
*
* @since 2.0.0
*/
public function setError(\Throwable $error): void
{
$this->error = $error;
}
}

View File

@ -0,0 +1,56 @@
<?php
/**
* Part of the Joomla Framework Application Package
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Application\Event;
use Joomla\Application\AbstractApplication;
use Joomla\Event\Event;
/**
* Base event class for application events.
*
* @since 2.0.0
*/
class ApplicationEvent extends Event
{
/**
* The active application.
*
* @var AbstractApplication
* @since 2.0.0
*/
private $application;
/**
* Event constructor.
*
* @param string $name The event name.
* @param AbstractApplication $application The active application.
*
* @since 2.0.0
*/
public function __construct(string $name, AbstractApplication $application)
{
parent::__construct($name);
$this->application = $application;
}
/**
* Get the active application.
*
* @return AbstractApplication
*
* @since 2.0.0
*/
public function getApplication(): AbstractApplication
{
return $this->application;
}
}

View File

@ -0,0 +1,19 @@
<?php
/**
* Part of the Joomla Framework Application Package
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Application\Exception;
/**
* Exception thrown when the application can't write to the response body
*
* @since 2.0.0
*/
class UnableToWriteBody extends \DomainException
{
}

View File

@ -0,0 +1,62 @@
<?php
/**
* Part of the Joomla Framework Application Package
*
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Application;
use Joomla\Session\SessionInterface;
/**
* Application sub-interface defining a web application class which supports sessions
*
* @since 2.0.0
*/
interface SessionAwareWebApplicationInterface extends WebApplicationInterface
{
/**
* Method to get the application session object.
*
* @return SessionInterface The session object
*
* @since 2.0.0
*/
public function getSession();
/**
* Sets the session for the application to use, if required.
*
* @param SessionInterface $session A session object.
*
* @return $this
*
* @since 2.0.0
*/
public function setSession(SessionInterface $session);
/**
* Checks for a form token in the request.
*
* @param string $method The request method in which to look for the token key.
*
* @return boolean
*
* @since 2.0.0
*/
public function checkToken($method = 'post');
/**
* Method to determine a hash for anti-spoofing variable names
*
* @param boolean $forceNew If true, force a new token to be created
*
* @return string Hashed var name
*
* @since 2.0.0
*/
public function getFormToken($forceNew = false);
}

View File

@ -0,0 +1,111 @@
<?php
/**
* Part of the Joomla Framework Application Package
*
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Application;
use Joomla\Input\Input;
use Joomla\Session\SessionInterface;
/**
* Trait which helps implementing `Joomla\Application\SessionAwareWebApplicationInterface` in a web application class.
*
* @since 2.0.0
*/
trait SessionAwareWebApplicationTrait
{
/**
* The application session object.
*
* @var SessionInterface
* @since 2.0.0
*/
protected $session;
/**
* Method to get the application input object.
*
* @return Input
*
* @since 2.0.0
*/
abstract public function getInput(): Input;
/**
* Method to get the application session object.
*
* @return SessionInterface The session object
*
* @since 2.0.0
*/
public function getSession()
{
if ($this->session === null) {
throw new \RuntimeException(\sprintf('A %s object has not been set.', SessionInterface::class));
}
return $this->session;
}
/**
* Sets the session for the application to use, if required.
*
* @param SessionInterface $session A session object.
*
* @return $this
*
* @since 2.0.0
*/
public function setSession(SessionInterface $session)
{
$this->session = $session;
return $this;
}
/**
* Checks for a form token in the request.
*
* @param string $method The request method in which to look for the token key.
*
* @return boolean
*
* @since 2.0.0
*/
public function checkToken($method = 'post')
{
$token = $this->getFormToken();
// Support a token sent via the X-CSRF-Token header, then fall back to a token in the request
$requestToken = $this->getInput()->server->get(
'HTTP_X_CSRF_TOKEN',
$this->getInput()->$method->get($token, '', 'alnum'),
'alnum'
);
if (!$requestToken) {
return false;
}
return $this->getSession()->hasToken($token);
}
/**
* Method to determine a hash for anti-spoofing variable names
*
* @param boolean $forceNew If true, force a new token to be created
*
* @return string Hashed var name
*
* @since 2.0.0
*/
public function getFormToken($forceNew = false)
{
return $this->getSession()->getToken($forceNew);
}
}

View File

@ -0,0 +1,574 @@
<?php
/**
* Part of the Joomla Framework Application Package
*
* @copyright (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Application\Web;
/**
* Class to model a Web Client.
*
* @property-read integer $platform The detected platform on which the web client runs.
* @property-read boolean $mobile True if the web client is a mobile device.
* @property-read integer $engine The detected rendering engine used by the web client.
* @property-read integer $browser The detected browser used by the web client.
* @property-read string $browserVersion The detected browser version used by the web client.
* @property-read array $languages The priority order detected accepted languages for the client.
* @property-read array $encodings The priority order detected accepted encodings for the client.
* @property-read string $userAgent The web client's user agent string.
* @property-read string $acceptEncoding The web client's accepted encoding string.
* @property-read string $acceptLanguage The web client's accepted languages string.
* @property-read array $detection An array of flags determining whether a detection routine has been run.
* @property-read boolean $robot True if the web client is a robot
* @property-read array $headers An array of all headers sent by client
*
* @since 1.0.0
*/
class WebClient
{
public const WINDOWS = 1;
public const WINDOWS_PHONE = 2;
public const WINDOWS_CE = 3;
public const IPHONE = 4;
public const IPAD = 5;
public const IPOD = 6;
public const MAC = 7;
public const BLACKBERRY = 8;
public const ANDROID = 9;
public const LINUX = 10;
public const TRIDENT = 11;
public const WEBKIT = 12;
public const GECKO = 13;
public const PRESTO = 14;
public const KHTML = 15;
public const AMAYA = 16;
public const IE = 17;
public const FIREFOX = 18;
public const CHROME = 19;
public const SAFARI = 20;
public const OPERA = 21;
public const ANDROIDTABLET = 22;
public const EDGE = 23;
public const BLINK = 24;
public const EDG = 25;
/**
* The detected platform on which the web client runs.
*
* @var integer
* @since 1.0.0
*/
protected $platform;
/**
* True if the web client is a mobile device.
*
* @var boolean
* @since 1.0.0
*/
protected $mobile = false;
/**
* The detected rendering engine used by the web client.
*
* @var integer
* @since 1.0.0
*/
protected $engine;
/**
* The detected browser used by the web client.
*
* @var integer
* @since 1.0.0
*/
protected $browser;
/**
* The detected browser version used by the web client.
*
* @var string
* @since 1.0.0
*/
protected $browserVersion;
/**
* The priority order detected accepted languages for the client.
*
* @var array
* @since 1.0.0
*/
protected $languages = [];
/**
* The priority order detected accepted encodings for the client.
*
* @var array
* @since 1.0.0
*/
protected $encodings = [];
/**
* The web client's user agent string.
*
* @var string
* @since 1.0.0
*/
protected $userAgent;
/**
* The web client's accepted encoding string.
*
* @var string
* @since 1.0.0
*/
protected $acceptEncoding;
/**
* The web client's accepted languages string.
*
* @var string
* @since 1.0.0
*/
protected $acceptLanguage;
/**
* True if the web client is a robot.
*
* @var boolean
* @since 1.0.0
*/
protected $robot = false;
/**
* An array of flags determining whether or not a detection routine has been run.
*
* @var array
* @since 1.0.0
*/
protected $detection = [];
/**
* An array of headers sent by client.
*
* @var array
* @since 1.3.0
*/
protected $headers;
/**
* Class constructor.
*
* @param string $userAgent The optional user-agent string to parse.
* @param string $acceptEncoding The optional client accept encoding string to parse.
* @param string $acceptLanguage The optional client accept language string to parse.
*
* @since 1.0.0
*/
public function __construct($userAgent = null, $acceptEncoding = null, $acceptLanguage = null)
{
// If no explicit user agent string was given attempt to use the implicit one from server environment.
if (empty($userAgent) && isset($_SERVER['HTTP_USER_AGENT'])) {
$this->userAgent = $_SERVER['HTTP_USER_AGENT'];
} else {
$this->userAgent = $userAgent;
}
// If no explicit acceptable encoding string was given attempt to use the implicit one from server environment.
if (empty($acceptEncoding) && isset($_SERVER['HTTP_ACCEPT_ENCODING'])) {
$this->acceptEncoding = $_SERVER['HTTP_ACCEPT_ENCODING'];
} else {
$this->acceptEncoding = $acceptEncoding;
}
// If no explicit acceptable languages string was given attempt to use the implicit one from server environment.
if (empty($acceptLanguage) && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$this->acceptLanguage = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
} else {
$this->acceptLanguage = $acceptLanguage;
}
}
/**
* Magic method to get an object property's value by name.
*
* @param string $name Name of the property for which to return a value.
*
* @return mixed The requested value if it exists.
*
* @since 1.0.0
*/
public function __get($name)
{
switch ($name) {
case 'mobile':
case 'platform':
if (empty($this->detection['platform'])) {
$this->detectPlatform($this->userAgent);
}
break;
case 'engine':
if (empty($this->detection['engine'])) {
$this->detectEngine($this->userAgent);
}
break;
case 'browser':
case 'browserVersion':
if (empty($this->detection['browser'])) {
$this->detectBrowser($this->userAgent);
}
break;
case 'languages':
if (empty($this->detection['acceptLanguage'])) {
$this->detectLanguage($this->acceptLanguage);
}
break;
case 'encodings':
if (empty($this->detection['acceptEncoding'])) {
$this->detectEncoding($this->acceptEncoding);
}
break;
case 'robot':
if (empty($this->detection['robot'])) {
$this->detectRobot($this->userAgent);
}
break;
case 'headers':
if (empty($this->detection['headers'])) {
$this->detectHeaders();
}
break;
}
// Return the property if it exists.
if (\property_exists($this, $name)) {
return $this->$name;
}
$trace = \debug_backtrace();
\trigger_error(
'Undefined property via \__get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'],
E_USER_NOTICE
);
}
/**
* Detects the client browser and version in a user agent string.
*
* @param string $userAgent The user-agent string to parse.
*
* @return void
*
* @since 1.0.0
*/
protected function detectBrowser($userAgent)
{
$patternBrowser = '';
// Attempt to detect the browser type. Obviously we are only worried about major browsers.
if ((\stripos($userAgent, 'MSIE') !== false) && (\stripos($userAgent, 'Opera') === false)) {
$this->browser = self::IE;
$patternBrowser = 'MSIE';
} elseif (\stripos($userAgent, 'Trident') !== false) {
$this->browser = self::IE;
$patternBrowser = ' rv';
} elseif (\stripos($userAgent, 'Edge') !== false) {
$this->browser = self::EDGE;
$patternBrowser = 'Edge';
} elseif (\stripos($userAgent, 'Edg') !== false) {
$this->browser = self::EDG;
$patternBrowser = 'Edg';
} elseif ((\stripos($userAgent, 'Firefox') !== false) && (\stripos($userAgent, 'like Firefox') === false)) {
$this->browser = self::FIREFOX;
$patternBrowser = 'Firefox';
} elseif (\stripos($userAgent, 'OPR') !== false) {
$this->browser = self::OPERA;
$patternBrowser = 'OPR';
} elseif (\stripos($userAgent, 'Chrome') !== false) {
$this->browser = self::CHROME;
$patternBrowser = 'Chrome';
} elseif (\stripos($userAgent, 'Safari') !== false) {
$this->browser = self::SAFARI;
$patternBrowser = 'Safari';
} elseif (\stripos($userAgent, 'Opera') !== false) {
$this->browser = self::OPERA;
$patternBrowser = 'Opera';
}
// If we detected a known browser let's attempt to determine the version.
if ($this->browser) {
// Build the REGEX pattern to match the browser version string within the user agent string.
$pattern = '#(?<browser>Version|' . $patternBrowser . ')[/ :]+(?<version>[0-9.|a-zA-Z.]*)#';
// Attempt to find version strings in the user agent string.
$matches = [];
if (\preg_match_all($pattern, $userAgent, $matches)) {
// Do we have both a Version and browser match?
if (\count($matches['browser']) == 2) {
// See whether Version or browser came first, and use the number accordingly.
if (\strripos($userAgent, 'Version') < \strripos($userAgent, $patternBrowser)) {
$this->browserVersion = $matches['version'][0];
} else {
$this->browserVersion = $matches['version'][1];
}
} elseif (\count($matches['browser']) > 2) {
$key = \array_search('Version', $matches['browser']);
if ($key) {
$this->browserVersion = $matches['version'][$key];
}
} else {
// We only have a Version or a browser so use what we have.
$this->browserVersion = $matches['version'][0];
}
}
}
// Mark this detection routine as run.
$this->detection['browser'] = true;
}
/**
* Method to detect the accepted response encoding by the client.
*
* @param string $acceptEncoding The client accept encoding string to parse.
*
* @return void
*
* @since 1.0.0
*/
protected function detectEncoding($acceptEncoding)
{
// Parse the accepted encodings.
$this->encodings = \array_map('trim', (array) \explode(',', (string) $acceptEncoding));
// Mark this detection routine as run.
$this->detection['acceptEncoding'] = true;
}
/**
* Detects the client rendering engine in a user agent string.
*
* @param string $userAgent The user-agent string to parse.
*
* @return void
*
* @since 1.0.0
*/
protected function detectEngine($userAgent)
{
if (\stripos($userAgent, 'MSIE') !== false || \stripos($userAgent, 'Trident') !== false) {
// Attempt to detect the client engine -- starting with the most popular ... for now.
$this->engine = self::TRIDENT;
} elseif (\stripos($userAgent, 'Edge') !== false || \stripos($userAgent, 'EdgeHTML') !== false) {
$this->engine = self::EDGE;
} elseif (\stripos($userAgent, 'Edg') !== false) {
$this->engine = self::BLINK;
} elseif (\stripos($userAgent, 'Chrome') !== false) {
$result = \explode('/', \stristr($userAgent, 'Chrome'));
$version = \explode(' ', $result[1]);
if ($version[0] >= 28) {
$this->engine = self::BLINK;
} else {
$this->engine = self::WEBKIT;
}
} elseif (\stripos($userAgent, 'AppleWebKit') !== false || \stripos($userAgent, 'blackberry') !== false) {
if (\stripos($userAgent, 'AppleWebKit') !== false) {
$result = \explode('/', \stristr($userAgent, 'AppleWebKit'));
$version = \explode(' ', $result[1]);
if ($version[0] === 537.36) {
// AppleWebKit/537.36 is Blink engine specific, exception is Blink emulated IEMobile, Trident or Edge
$this->engine = self::BLINK;
}
}
// Evidently blackberry uses WebKit and doesn't necessarily report it. Bad RIM.
$this->engine = self::WEBKIT;
} elseif (\stripos($userAgent, 'Gecko') !== false && \stripos($userAgent, 'like Gecko') === false) {
// We have to check for like Gecko because some other browsers spoof Gecko.
$this->engine = self::GECKO;
} elseif (\stripos($userAgent, 'Opera') !== false || \stripos($userAgent, 'Presto') !== false) {
$version = false;
if (\preg_match('/Opera[\/| ]?([0-9.]+)/u', $userAgent, $match)) {
$version = (float) ($match[1]);
}
if (\preg_match('/Version\/([0-9.]+)/u', $userAgent, $match)) {
if ((float) ($match[1]) >= 10) {
$version = (float) ($match[1]);
}
}
if ($version !== false && $version >= 15) {
$this->engine = self::BLINK;
} else {
$this->engine = self::PRESTO;
}
} elseif (\stripos($userAgent, 'KHTML') !== false) {
// *sigh*
$this->engine = self::KHTML;
} elseif (\stripos($userAgent, 'Amaya') !== false) {
// Lesser known engine but it finishes off the major list from Wikipedia :-)
$this->engine = self::AMAYA;
}
// Mark this detection routine as run.
$this->detection['engine'] = true;
}
/**
* Method to detect the accepted languages by the client.
*
* @param mixed $acceptLanguage The client accept language string to parse.
*
* @return void
*
* @since 1.0.0
*/
protected function detectLanguage($acceptLanguage)
{
// Parse the accepted encodings.
$this->languages = \array_map('trim', (array) \explode(',', $acceptLanguage));
// Mark this detection routine as run.
$this->detection['acceptLanguage'] = true;
}
/**
* Detects the client platform in a user agent string.
*
* @param string $userAgent The user-agent string to parse.
*
* @return void
*
* @since 1.0.0
*/
protected function detectPlatform($userAgent)
{
// Attempt to detect the client platform.
if (\stripos($userAgent, 'Windows') !== false) {
$this->platform = self::WINDOWS;
// Let's look at the specific mobile options in the Windows space.
if (\stripos($userAgent, 'Windows Phone') !== false) {
$this->mobile = true;
$this->platform = self::WINDOWS_PHONE;
} elseif (\stripos($userAgent, 'Windows CE') !== false) {
$this->mobile = true;
$this->platform = self::WINDOWS_CE;
}
} elseif (\stripos($userAgent, 'iPhone') !== false) {
// Interestingly 'iPhone' is present in all iOS devices so far including iPad and iPods.
$this->mobile = true;
$this->platform = self::IPHONE;
// Let's look at the specific mobile options in the iOS space.
if (\stripos($userAgent, 'iPad') !== false) {
$this->platform = self::IPAD;
} elseif (\stripos($userAgent, 'iPod') !== false) {
$this->platform = self::IPOD;
}
} elseif (\stripos($userAgent, 'iPad') !== false) {
// In case where iPhone is not mentioed in iPad user agent string
$this->mobile = true;
$this->platform = self::IPAD;
} elseif (\stripos($userAgent, 'iPod') !== false) {
// In case where iPhone is not mentioed in iPod user agent string
$this->mobile = true;
$this->platform = self::IPOD;
} elseif (\preg_match('/macintosh|mac os x/i', $userAgent)) {
// This has to come after the iPhone check because mac strings are also present in iOS devices.
$this->platform = self::MAC;
} elseif (\stripos($userAgent, 'Blackberry') !== false) {
$this->mobile = true;
$this->platform = self::BLACKBERRY;
} elseif (\stripos($userAgent, 'Android') !== false) {
$this->mobile = true;
$this->platform = self::ANDROID;
/*
* Attempt to distinguish between Android phones and tablets
* There is no totally foolproof method but certain rules almost always hold
* Android 3.x is only used for tablets
* Some devices and browsers encourage users to change their UA string to include Tablet.
* Google encourages manufacturers to exclude the string Mobile from tablet device UA strings.
* In some modes Kindle Android devices include the string Mobile but they include the string Silk.
*/
if (
\stripos($userAgent, 'Android 3') !== false || \stripos($userAgent, 'Tablet') !== false
|| \stripos($userAgent, 'Mobile') === false || \stripos($userAgent, 'Silk') !== false
) {
$this->platform = self::ANDROIDTABLET;
}
} elseif (\stripos($userAgent, 'Linux') !== false) {
$this->platform = self::LINUX;
}
// Mark this detection routine as run.
$this->detection['platform'] = true;
}
/**
* Determines if the browser is a robot or not.
*
* @param string $userAgent The user-agent string to parse.
*
* @return void
*
* @since 1.0.0
*/
protected function detectRobot($userAgent)
{
$this->robot = (bool) \preg_match('/http|bot|robot|spider|crawler|curl|^$/i', $userAgent);
$this->detection['robot'] = true;
}
/**
* Fills internal array of headers
*
* @return void
*
* @since 1.3.0
*/
protected function detectHeaders()
{
if (\function_exists('getallheaders')) {
// If php is working under Apache, there is a special function
$this->headers = \getallheaders();
} else {
// Else we fill headers from $_SERVER variable
$this->headers = [];
foreach ($_SERVER as $name => $value) {
if (\substr($name, 0, 5) == 'HTTP_') {
$this->headers[\str_replace(' ', '-', \ucwords(\strtolower(\str_replace('_', ' ', \substr($name, 5)))))] = $value;
}
}
}
// Mark this detection routine as run.
$this->detection['headers'] = true;
}
}

View File

@ -0,0 +1,105 @@
<?php
/**
* Part of the Joomla Framework Application Package
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Application;
use Joomla\Application\Controller\ControllerResolverInterface;
use Joomla\Application\Web\WebClient;
use Joomla\Input\Input;
use Joomla\Registry\Registry;
use Joomla\Router\RouterInterface;
use Psr\Http\Message\ResponseInterface;
/**
* A basic web application class for handing HTTP requests.
*
* @since 2.0.0
*/
class WebApplication extends AbstractWebApplication implements SessionAwareWebApplicationInterface
{
use SessionAwareWebApplicationTrait;
/**
* The application's controller resolver.
*
* @var ControllerResolverInterface
* @since 2.0.0
*/
protected $controllerResolver;
/**
* The application's router.
*
* @var RouterInterface
* @since 2.0.0
*/
protected $router;
/**
* Class constructor.
*
* @param ControllerResolverInterface $controllerResolver The application's controller resolver
* @param RouterInterface $router The application's router
* @param Input ?$input An optional argument to provide dependency injection
* for the application's input object. If the argument
* is an Input object that object will become the
* application's input object, otherwise a default input
* object is created.
* @param Registry ?$config An optional argument to provide dependency injection
* for the application's config object. If the argument
* is a Registry object that object will become the
* application's config object, otherwise a default
* config object is created.
* @param Web\WebClient ?$client An optional argument to provide dependency injection
* for the application's client object. If the argument
* is a Web\WebClient object that object will become the
* application's client object, otherwise a default
* client object is created.
* @param ResponseInterface ?$response An optional argument to provide dependency injection
* for the application's response object. If the
* argument is a ResponseInterface object that object
* will become the application's response object,
* otherwise a default response object is created.
*
* @since 2.0.0
*/
public function __construct(
ControllerResolverInterface $controllerResolver,
RouterInterface $router,
Input $input = null,
Registry $config = null,
WebClient $client = null,
ResponseInterface $response = null
) {
$this->controllerResolver = $controllerResolver;
$this->router = $router;
// Call the constructor as late as possible (it runs `initialise`).
parent::__construct($input, $config, $client, $response);
}
/**
* Method to run the application routines.
*
* @return void
*
* @since 2.0.0
*/
protected function doExecute(): void
{
$route = $this->router->parseRoute($this->get('uri.route'), $this->input->getMethod());
// Add variables to the input if not already set
foreach ($route->getRouteVariables() as $key => $value) {
$this->input->def($key, $value);
}
\call_user_func($this->controllerResolver->resolve($route));
}
}

View File

@ -0,0 +1,184 @@
<?php
/**
* Part of the Joomla Framework Application Package
*
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Application;
use Joomla\Input\Input;
use Psr\Http\Message\ResponseInterface;
/**
* Application sub-interface defining a web application class
*
* @since 2.0.0
*/
interface WebApplicationInterface extends ApplicationInterface
{
/**
* Method to get the application input object.
*
* @return Input
*
* @since 2.0.0
*/
public function getInput(): Input;
/**
* Redirect to another URL.
*
* If the headers have not been sent the redirect will be accomplished using a "301 Moved Permanently" or "303 See Other" code in the header
* pointing to the new location. If the headers have already been sent this will be accomplished using a JavaScript statement.
*
* @param string $url The URL to redirect to. Can only be http/https URL
* @param integer|boolean $status The HTTP status code to be provided. 303 is assumed by default.
*
* @return void
*
* @since 2.0.0
* @throws \InvalidArgumentException
*/
public function redirect($url, $status = 303);
/**
* Set/get cachable state for the response.
*
* If $allow is set, sets the cachable state of the response. Always returns the current state.
*
* @param boolean $allow True to allow browser caching.
*
* @return boolean
*
* @since 2.0.0
*/
public function allowCache($allow = null);
/**
* Method to set a response header.
*
* If the replace flag is set then all headers with the given name will be replaced by the new one.
* The headers are stored in an internal array to be sent when the site is sent to the browser.
*
* @param string $name The name of the header to set.
* @param string $value The value of the header to set.
* @param boolean $replace True to replace any headers with the same name.
*
* @return $this
*
* @since 2.0.0
*/
public function setHeader($name, $value, $replace = false);
/**
* Method to get the array of response headers to be sent when the response is sent to the client.
*
* @return array
*
* @since 2.0.0
*/
public function getHeaders();
/**
* Method to clear any set response headers.
*
* @return $this
*
* @since 2.0.0
*/
public function clearHeaders();
/**
* Send the response headers.
*
* @return $this
*
* @since 2.0.0
*/
public function sendHeaders();
/**
* Set body content. If body content already defined, this will replace it.
*
* @param string $content The content to set as the response body.
*
* @return $this
*
* @since 2.0.0
*/
public function setBody($content);
/**
* Prepend content to the body content
*
* @param string $content The content to prepend to the response body.
*
* @return $this
*
* @since 2.0.0
*/
public function prependBody($content);
/**
* Append content to the body content
*
* @param string $content The content to append to the response body.
*
* @return $this
*
* @since 2.0.0
*/
public function appendBody($content);
/**
* Return the body content
*
* @return mixed The response body as a string.
*
* @since 2.0.0
*/
public function getBody();
/**
* Get the PSR-7 Response Object.
*
* @return ResponseInterface
*
* @since 2.0.0
*/
public function getResponse(): ResponseInterface;
/**
* Check if the value is a valid HTTP status code
*
* @param integer $code The potential status code
*
* @return boolean
*
* @since 2.0.0
*/
public function isValidHttpStatus($code);
/**
* Set the PSR-7 Response Object.
*
* @param ResponseInterface $response The response object
*
* @return void
*
* @since 2.0.0
*/
public function setResponse(ResponseInterface $response): void;
/**
* Determine if we are using a secure (SSL) connection.
*
* @return boolean True if using SSL, false if not.
*
* @since 2.0.0
*/
public function isSslConnection();
}