[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/src/Application/ -> ApiApplication.php (source)

   1  <?php
   2  
   3  /**
   4   * Joomla! Content Management System
   5   *
   6   * @copyright  (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
   7   * @license    GNU General Public License version 2 or later; see LICENSE
   8   */
   9  
  10  namespace Joomla\CMS\Application;
  11  
  12  use Joomla\Application\Web\WebClient;
  13  use Joomla\CMS\Access\Exception\AuthenticationFailed;
  14  use Joomla\CMS\Component\ComponentHelper;
  15  use Joomla\CMS\Factory;
  16  use Joomla\CMS\Plugin\PluginHelper;
  17  use Joomla\CMS\Router\ApiRouter;
  18  use Joomla\CMS\Router\Exception\RouteNotFoundException;
  19  use Joomla\CMS\Uri\Uri;
  20  use Joomla\DI\Container;
  21  use Joomla\Input\Json as JInputJson;
  22  use Joomla\Registry\Registry;
  23  use Negotiation\Accept;
  24  use Negotiation\Exception\InvalidArgument;
  25  use Negotiation\Negotiator;
  26  
  27  // phpcs:disable PSR1.Files.SideEffects
  28  \defined('JPATH_PLATFORM') or die;
  29  // phpcs:enable PSR1.Files.SideEffects
  30  
  31  /**
  32   * Joomla! API Application class
  33   *
  34   * @since  4.0.0
  35   */
  36  final class ApiApplication extends CMSApplication
  37  {
  38      /**
  39       * Maps extension types to their
  40       *
  41       * @var    array
  42       * @since  4.0.0
  43       */
  44      protected $formatMapper = array();
  45  
  46      /**
  47       * The authentication plugin type
  48       *
  49       * @var    string
  50       * @since  4.0.0
  51       */
  52      protected $authenticationPluginType = 'api-authentication';
  53  
  54      /**
  55       * Class constructor.
  56       *
  57       * @param   JInputJson  $input      An optional argument to provide dependency injection for the application's input
  58       *                                  object.  If the argument is a JInput object that object will become the
  59       *                                  application's input object, otherwise a default input object is created.
  60       * @param   Registry    $config     An optional argument to provide dependency injection for the application's config
  61       *                                  object.  If the argument is a Registry object that object will become the
  62       *                                  application's config object, otherwise a default config object is created.
  63       * @param   WebClient   $client     An optional argument to provide dependency injection for the application's client
  64       *                                  object.  If the argument is a WebClient object that object will become the
  65       *                                  application's client object, otherwise a default client object is created.
  66       * @param   Container   $container  Dependency injection container.
  67       *
  68       * @since   4.0.0
  69       */
  70      public function __construct(JInputJson $input = null, Registry $config = null, WebClient $client = null, Container $container = null)
  71      {
  72          // Register the application name
  73          $this->name = 'api';
  74  
  75          // Register the client ID
  76          $this->clientId = 3;
  77  
  78          // Execute the parent constructor
  79          parent::__construct($input, $config, $client, $container);
  80  
  81          $this->addFormatMap('application/json', 'json');
  82          $this->addFormatMap('application/vnd.api+json', 'jsonapi');
  83  
  84          // Set the root in the URI based on the application name
  85          Uri::root(null, str_ireplace('/' . $this->getName(), '', Uri::base(true)));
  86      }
  87  
  88  
  89      /**
  90       * Method to run the application routines.
  91       *
  92       * Most likely you will want to instantiate a controller and execute it, or perform some sort of task directly.
  93       *
  94       * @return  void
  95       *
  96       * @since   4.0.0
  97       */
  98      protected function doExecute()
  99      {
 100          // Initialise the application
 101          $this->initialiseApp();
 102  
 103          // Mark afterInitialise in the profiler.
 104          JDEBUG ? $this->profiler->mark('afterInitialise') : null;
 105  
 106          // Route the application
 107          $this->route();
 108  
 109          // Mark afterApiRoute in the profiler.
 110          JDEBUG ? $this->profiler->mark('afterApiRoute') : null;
 111  
 112          // Dispatch the application
 113          $this->dispatch();
 114  
 115          // Mark afterDispatch in the profiler.
 116          JDEBUG ? $this->profiler->mark('afterDispatch') : null;
 117      }
 118  
 119      /**
 120       * Adds a mapping from a content type to the format stored. Note the format type cannot be overwritten.
 121       *
 122       * @param   string  $contentHeader  The content header
 123       * @param   string  $format         The content type format
 124       *
 125       * @return  void
 126       *
 127       * @since   4.0.0
 128       */
 129      public function addFormatMap($contentHeader, $format)
 130      {
 131          if (!\array_key_exists($contentHeader, $this->formatMapper)) {
 132              $this->formatMapper[$contentHeader] = $format;
 133          }
 134      }
 135  
 136      /**
 137       * Rendering is the process of pushing the document buffers into the template
 138       * placeholders, retrieving data from the document and pushing it into
 139       * the application response buffer.
 140       *
 141       * @return  void
 142       *
 143       * @since   4.0.0
 144       *
 145       * @note    Rendering should be overridden to get rid of the theme files.
 146       */
 147      protected function render()
 148      {
 149          // Render the document
 150          $this->setBody($this->document->render($this->allowCache()));
 151      }
 152  
 153      /**
 154       * Method to send the application response to the client.  All headers will be sent prior to the main application output data.
 155       *
 156       * @param   array  $options  An optional argument to enable CORS. (Temporary)
 157       *
 158       * @return  void
 159       *
 160       * @since   4.0.0
 161       */
 162      protected function respond($options = array())
 163      {
 164          // Set the Joomla! API signature
 165          $this->setHeader('X-Powered-By', 'JoomlaAPI/1.0', true);
 166  
 167          $forceCORS = (int) $this->get('cors');
 168  
 169          if ($forceCORS) {
 170              /**
 171              * Enable CORS (Cross-origin resource sharing)
 172              * Obtain allowed CORS origin from Global Settings.
 173              * Set to * (=all) if not set.
 174              */
 175              $allowedOrigin = $this->get('cors_allow_origin', '*');
 176              $this->setHeader('Access-Control-Allow-Origin', $allowedOrigin, true);
 177              $this->setHeader('Access-Control-Allow-Headers', 'Authorization');
 178  
 179              if ($this->input->server->getString('HTTP_ORIGIN', null) !== null) {
 180                  $this->setHeader('Access-Control-Allow-Origin', $this->input->server->getString('HTTP_ORIGIN'), true);
 181                  $this->setHeader('Access-Control-Allow-Credentials', 'true', true);
 182              }
 183          }
 184  
 185          // Parent function can be overridden later on for debugging.
 186          parent::respond();
 187      }
 188  
 189      /**
 190       * Gets the name of the current template.
 191       *
 192       * @param   boolean  $params  True to return the template parameters
 193       *
 194       * @return  string|\stdClass
 195       *
 196       * @since   4.0.0
 197       */
 198      public function getTemplate($params = false)
 199      {
 200          // The API application should not need to use a template
 201          if ($params) {
 202              $template              = new \stdClass();
 203              $template->template    = 'system';
 204              $template->params      = new Registry();
 205              $template->inheritable = 0;
 206              $template->parent      = '';
 207  
 208              return $template;
 209          }
 210  
 211          return 'system';
 212      }
 213  
 214      /**
 215       * Route the application.
 216       *
 217       * Routing is the process of examining the request environment to determine which
 218       * component should receive the request. The component optional parameters
 219       * are then set in the request object to be processed when the application is being
 220       * dispatched.
 221       *
 222       * @return  void
 223       *
 224       * @since   4.0.0
 225       */
 226      protected function route()
 227      {
 228          $router = $this->getContainer()->get(ApiRouter::class);
 229  
 230          // Trigger the onBeforeApiRoute event.
 231          PluginHelper::importPlugin('webservices');
 232          $this->triggerEvent('onBeforeApiRoute', array(&$router, $this));
 233          $caught404 = false;
 234          $method    = $this->input->getMethod();
 235  
 236          try {
 237              $this->handlePreflight($method, $router);
 238  
 239              $route = $router->parseApiRoute($method);
 240          } catch (RouteNotFoundException $e) {
 241              $caught404 = true;
 242          }
 243  
 244          /**
 245           * Now we have an API perform content negotiation to ensure we have a valid header. Assume if the route doesn't
 246           * tell us otherwise it uses the plain JSON API
 247           */
 248          $priorities = array('application/vnd.api+json');
 249  
 250          if (!$caught404 && \array_key_exists('format', $route['vars'])) {
 251              $priorities = $route['vars']['format'];
 252          }
 253  
 254          $negotiator = new Negotiator();
 255  
 256          try {
 257              $mediaType = $negotiator->getBest($this->input->server->getString('HTTP_ACCEPT'), $priorities);
 258          } catch (InvalidArgument $e) {
 259              $mediaType = null;
 260          }
 261  
 262          // If we can't find a match bail with a 406 - Not Acceptable
 263          if ($mediaType === null) {
 264              throw new Exception\NotAcceptable('Could not match accept header', 406);
 265          }
 266  
 267          /** @var $mediaType Accept */
 268          $format = $mediaType->getValue();
 269  
 270          if (\array_key_exists($mediaType->getValue(), $this->formatMapper)) {
 271              $format = $this->formatMapper[$mediaType->getValue()];
 272          }
 273  
 274          $this->input->set('format', $format);
 275  
 276          if ($caught404) {
 277              throw $e;
 278          }
 279  
 280          $this->input->set('option', $route['vars']['component']);
 281          $this->input->set('controller', $route['controller']);
 282          $this->input->set('task', $route['task']);
 283  
 284          foreach ($route['vars'] as $key => $value) {
 285              if ($key !== 'component') {
 286                  if ($this->input->getMethod() === 'POST') {
 287                      $this->input->post->set($key, $value);
 288                  } else {
 289                      $this->input->set($key, $value);
 290                  }
 291              }
 292          }
 293  
 294          $this->triggerEvent('onAfterApiRoute', array($this));
 295  
 296          if (!isset($route['vars']['public']) || $route['vars']['public'] === false) {
 297              if (!$this->login(array('username' => ''), array('silent' => true, 'action' => 'core.login.api'))) {
 298                  throw new AuthenticationFailed();
 299              }
 300          }
 301      }
 302  
 303      /**
 304       * Handles preflight requests.
 305       *
 306       * @param   String     $method  The REST verb
 307       *
 308       * @param   ApiRouter  $router  The API Routing object
 309       *
 310       * @return  void
 311       *
 312       * @since   4.0.0
 313       */
 314      protected function handlePreflight($method, $router)
 315      {
 316          /**
 317          * If not an OPTIONS request or CORS is not enabled,
 318          * there's nothing useful to do here.
 319          */
 320          if ($method !== 'OPTIONS' || !(int) $this->get('cors')) {
 321              return;
 322          }
 323  
 324          // Extract routes matching current route from all known routes.
 325          $matchingRoutes = $router->getMatchingRoutes();
 326  
 327          // Extract exposed methods from matching routes.
 328          $matchingRoutesMethods = array_unique(
 329              array_reduce(
 330                  $matchingRoutes,
 331                  function ($carry, $route) {
 332                      return array_merge($carry, $route->getMethods());
 333                  },
 334                  []
 335              )
 336          );
 337  
 338          /**
 339          * Obtain allowed CORS origin from Global Settings.
 340          * Set to * (=all) if not set.
 341          */
 342          $allowedOrigin = $this->get('cors_allow_origin', '*');
 343  
 344          /**
 345          * Obtain allowed CORS headers from Global Settings.
 346          * Set to sensible default if not set.
 347          */
 348          $allowedHeaders = $this->get('cors_allow_headers', 'Content-Type,X-Joomla-Token');
 349  
 350          /**
 351          * Obtain allowed CORS methods from Global Settings.
 352          * Set to methods exposed by current route if not set.
 353          */
 354          $allowedMethods = $this->get('cors_allow_methods', implode(',', $matchingRoutesMethods));
 355  
 356          // No use to go through the regular route handling hassle,
 357          // so let's simply output the headers and exit.
 358          $this->setHeader('status', '204');
 359          $this->setHeader('Access-Control-Allow-Origin', $allowedOrigin);
 360          $this->setHeader('Access-Control-Allow-Headers', $allowedHeaders);
 361          $this->setHeader('Access-Control-Allow-Methods', $allowedMethods);
 362          $this->sendHeaders();
 363  
 364          $this->close();
 365      }
 366  
 367      /**
 368       * Returns the application Router object.
 369       *
 370       * @return  ApiRouter
 371       *
 372       * @since      4.0.0
 373       * @deprecated 5.0 Inject the router or load it from the dependency injection container
 374       */
 375      public function getApiRouter()
 376      {
 377          return $this->getContainer()->get(ApiRouter::class);
 378      }
 379  
 380      /**
 381       * Dispatch the application
 382       *
 383       * @param   string  $component  The component which is being rendered.
 384       *
 385       * @return  void
 386       *
 387       * @since   4.0.0
 388       */
 389      public function dispatch($component = null)
 390      {
 391          // Get the component if not set.
 392          if (!$component) {
 393              $component = $this->input->get('option', null);
 394          }
 395  
 396          // Load the document to the API
 397          $this->loadDocument();
 398  
 399          // Set up the params
 400          $document = Factory::getDocument();
 401  
 402          // Register the document object with Factory
 403          Factory::$document = $document;
 404  
 405          $contents = ComponentHelper::renderComponent($component);
 406          $document->setBuffer($contents, 'component');
 407  
 408          // Trigger the onAfterDispatch event.
 409          PluginHelper::importPlugin('system');
 410          $this->triggerEvent('onAfterDispatch');
 411      }
 412  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer