Skip to content
Snippets Groups Projects
Select Git revision
  • dc53be5b5a511ff1a1bbb6deaf29b8ba5eaa2fe0
  • 11.x default protected
  • 10.6.x protected
  • 10.5.x protected
  • 11.2.x protected
  • 11.1.x protected
  • 10.4.x protected
  • 11.0.x protected
  • 10.3.x protected
  • 7.x protected
  • 10.2.x protected
  • 10.1.x protected
  • 9.5.x protected
  • 10.0.x protected
  • 9.4.x protected
  • 9.3.x protected
  • 9.2.x protected
  • 9.1.x protected
  • 8.9.x protected
  • 9.0.x protected
  • 8.8.x protected
  • 10.5.1 protected
  • 11.2.2 protected
  • 11.2.1 protected
  • 11.2.0 protected
  • 10.5.0 protected
  • 11.2.0-rc2 protected
  • 10.5.0-rc1 protected
  • 11.2.0-rc1 protected
  • 10.4.8 protected
  • 11.1.8 protected
  • 10.5.0-beta1 protected
  • 11.2.0-beta1 protected
  • 11.2.0-alpha1 protected
  • 10.4.7 protected
  • 11.1.7 protected
  • 10.4.6 protected
  • 11.1.6 protected
  • 10.3.14 protected
  • 10.4.5 protected
  • 11.0.13 protected
41 results

AjaxResponse.php

Blame
  • Alex Pott's avatar
    Issue #2483433 by damiankloip, Wim Leers, catch, dawehner: Optimize...
    Alex Pott authored
    Issue #2483433 by damiankloip, Wim Leers, catch, dawehner: Optimize CacheableMetadata::merge() + BubbleableMetadata::merge()
    dc53be5b
    History
    Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    AjaxResponse.php 8.52 KiB
    <?php
    
    /**
     * @file
     * Definition of Drupal\Core\Ajax\AjaxResponse.
     */
    
    namespace Drupal\Core\Ajax;
    
    use Drupal\Core\Asset\AttachedAssets;
    use Drupal\Core\Render\BubbleableMetadata;
    use Drupal\Core\Render\Renderer;
    use Symfony\Component\DependencyInjection\ContainerInterface;
    use Symfony\Component\HttpFoundation\JsonResponse;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\Response;
    
    /**
     * JSON response object for AJAX requests.
     *
     * @ingroup ajax
     */
    class AjaxResponse extends JsonResponse {
    
      /**
       * The array of ajax commands.
       *
       * @var array
       */
      protected $commands = array();
    
      /**
       * The attachments for this Ajax response.
       *
       * @var array
       */
      protected $attachments = [
        'library' => [],
        'drupalSettings' => [],
      ];
    
      /**
       * Sets attachments for this Ajax response.
       *
       * When this Ajax response is rendered, it will take care of generating the
       * necessary Ajax commands, if any.
       *
       * @param array $attachments
       *   An #attached array.
       *
       * @return $this
       */
      public function setAttachments(array $attachments) {
        $this->attachments = $attachments;
        return $this;
      }
    
      /**
       * Add an AJAX command to the response.
       *
       * @param \Drupal\Core\Ajax\CommandInterface $command
       *   An AJAX command object implementing CommandInterface.
       * @param bool $prepend
       *   A boolean which determines whether the new command should be executed
       *   before previously added commands. Defaults to FALSE.
       *
       * @return AjaxResponse
       *   The current AjaxResponse.
       */
      public function addCommand(CommandInterface $command, $prepend = FALSE) {
        if ($prepend) {
          array_unshift($this->commands, $command->render());
        }
        else {
          $this->commands[] = $command->render();
        }
        if ($command instanceof CommandWithAttachedAssetsInterface) {
          $assets = $command->getAttachedAssets();
          $attachments = [
            'library' => $assets->getLibraries(),
            'drupalSettings' => $assets->getSettings(),
          ];
          $attachments = BubbleableMetadata::mergeAttachments($this->attachments, $attachments);
          $this->setAttachments($attachments);
        }
    
        return $this;
      }
    
      /**
       * Gets all AJAX commands.
       *
       * @return \Drupal\Core\Ajax\CommandInterface[]
       *   Returns all previously added AJAX commands.
       */
      public function &getCommands() {
        return $this->commands;
      }
    
      /**
       * {@inheritdoc}
       *
       * Sets the response's data to be the array of AJAX commands.
       */
      public function prepare(Request $request) {
        $this->prepareResponse($request);
        return $this;
      }
    
      /**
       * Sets the rendered AJAX right before the response is prepared.
       *
       * @param \Symfony\Component\HttpFoundation\Request $request
       *   The request object.
       */
      public function prepareResponse(Request $request) {
        if ($this->data == '{}') {
          $this->setData($this->ajaxRender($request));
        }
    
        // IE 9 does not support XHR 2 (http://caniuse.com/#feat=xhr2), so
        // for that browser, jquery.form submits requests containing a file upload
        // via an IFRAME rather than via XHR. Since the response is being sent to
        // an IFRAME, it must be formatted as HTML. Specifically:
        // - It must use the text/html content type or else the browser will
        //   present a download prompt. Note: This applies to both file uploads
        //   as well as any ajax request in a form with a file upload form.
        // - It must place the JSON data into a textarea to prevent browser
        //   extensions such as Linkification and Skype's Browser Highlighter
        //   from applying HTML transformations such as URL or phone number to
        //   link conversions on the data values.
        //
        // Since this affects the format of the output, it could be argued that
        // this should be implemented as a separate Accept MIME type. However,
        // that would require separate variants for each type of AJAX request
        // (e.g., drupal-ajax, drupal-dialog, drupal-modal), so for expediency,
        // this browser workaround is implemented via a GET or POST parameter.
        //
        // @see http://malsup.com/jquery/form/#file-upload
        // @see https://www.drupal.org/node/1009382
        // @see https://www.drupal.org/node/2339491
        // @see Drupal.ajax.prototype.beforeSend()
        $accept = $request->headers->get('accept');
    
        if (strpos($accept, 'text/html') !== FALSE) {
          $this->headers->set('Content-Type', 'text/html; charset=utf-8');
    
          // Browser IFRAMEs expect HTML. Browser extensions, such as Linkification
          // and Skype's Browser Highlighter, convert URLs, phone numbers, etc. into
          // links. This corrupts the JSON response. Protect the integrity of the
          // JSON data by making it the value of a textarea.
          // @see http://malsup.com/jquery/form/#file-upload
          // @see https://www.drupal.org/node/1009382
          $this->setContent('<textarea>' . $this->data  . '</textarea>');
        }
      }
    
      /**
       * Prepares the AJAX commands for sending back to the client.
       *
       * @param Request $request
       *   The request object that the AJAX is responding to.
       *
       * @return array
       *   An array of commands ready to be returned as JSON.
       */
      protected function ajaxRender(Request $request) {
        $ajax_page_state = $request->request->get('ajax_page_state');
    
        // Aggregate CSS/JS if necessary, but only during normal site operation.
        $config = \Drupal::config('system.performance');
        $optimize_css = !defined('MAINTENANCE_MODE') && $config->get('css.preprocess');
        $optimize_js = !defined('MAINTENANCE_MODE') && $config->get('js.preprocess');
    
        // Resolve the attached libraries into asset collections.
        $assets = new AttachedAssets();
        $assets->setLibraries(isset($this->attachments['library']) ? $this->attachments['library'] : [])
          ->setAlreadyLoadedLibraries(isset($ajax_page_state) ? explode(',', $ajax_page_state['libraries']) : [])
          ->setSettings(isset($this->attachments['drupalSettings']) ? $this->attachments['drupalSettings'] : []);
        $asset_resolver = \Drupal::service('asset.resolver');
        $css_assets = $asset_resolver->getCssAssets($assets, $optimize_css);
        list($js_assets_header, $js_assets_footer) = $asset_resolver->getJsAssets($assets, $optimize_js);
    
        // Render the HTML to load these files, and add AJAX commands to insert this
        // HTML in the page. Settings are handled separately, afterwards.
        $settings = [];
        if (isset($js_assets_header['drupalSettings'])) {
          $settings = $js_assets_header['drupalSettings']['data'];
          unset($js_assets_header['drupalSettings']);
        }
        if (isset($js_assets_footer['drupalSettings'])) {
          $settings = $js_assets_footer['drupalSettings']['data'];
          unset($js_assets_footer['drupalSettings']);
        }
    
        // Prepend commands to add the assets, preserving their relative order.
        $resource_commands = array();
        $renderer = $this->getRenderer();
        if (!empty($css_assets)) {
          $css_render_array = \Drupal::service('asset.css.collection_renderer')->render($css_assets);
          $resource_commands[] = new AddCssCommand($renderer->render($css_render_array));
        }
        if (!empty($js_assets_header)) {
          $js_header_render_array = \Drupal::service('asset.js.collection_renderer')->render($js_assets_header);
          $resource_commands[] = new PrependCommand('head', $renderer->render($js_header_render_array));
        }
        if (!empty($js_assets_footer)) {
          $js_footer_render_array = \Drupal::service('asset.js.collection_renderer')->render($js_assets_footer);
          $resource_commands[] = new AppendCommand('body', $renderer->render($js_footer_render_array));
        }
        foreach (array_reverse($resource_commands) as $resource_command) {
          $this->addCommand($resource_command, TRUE);
        }
    
        // Prepend a command to merge changes and additions to drupalSettings.
        if (!empty($settings)) {
          // During Ajax requests basic path-specific settings are excluded from
          // new drupalSettings values. The original page where this request comes
          // from already has the right values. An Ajax request would update them
          // with values for the Ajax request and incorrectly override the page's
          // values.
          // @see system_js_settings_alter()
          unset($settings['path']);
          $this->addCommand(new SettingsCommand($settings, TRUE), TRUE);
        }
    
        $commands = $this->commands;
        \Drupal::moduleHandler()->alter('ajax_render', $commands);
    
        return $commands;
      }
    
      /**
       * The renderer service.
       *
       * @return \Drupal\Core\Render\Renderer
       *   The renderer service.
       */
      protected function getRenderer() {
        return \Drupal::service('renderer');
      }
    
    }