Pull merge.
[yaffs-website] / web / core / lib / Drupal / Core / Ajax / AjaxResponseAttachmentsProcessor.php
1 <?php
2
3 namespace Drupal\Core\Ajax;
4
5 use Drupal\Core\Asset\AssetCollectionRendererInterface;
6 use Drupal\Core\Asset\AssetResolverInterface;
7 use Drupal\Core\Asset\AttachedAssets;
8 use Drupal\Core\Config\ConfigFactoryInterface;
9 use Drupal\Core\Extension\ModuleHandlerInterface;
10 use Drupal\Core\Render\AttachmentsInterface;
11 use Drupal\Core\Render\AttachmentsResponseProcessorInterface;
12 use Drupal\Core\Render\RendererInterface;
13 use Symfony\Component\HttpFoundation\Request;
14 use Symfony\Component\HttpFoundation\RequestStack;
15
16 /**
17  * Processes attachments of AJAX responses.
18  *
19  * @see \Drupal\Core\Ajax\AjaxResponse
20  * @see \Drupal\Core\Render\MainContent\AjaxRenderer
21  */
22 class AjaxResponseAttachmentsProcessor implements AttachmentsResponseProcessorInterface {
23
24   /**
25    * The asset resolver service.
26    *
27    * @var \Drupal\Core\Asset\AssetResolverInterface
28    */
29   protected $assetResolver;
30
31   /**
32    * A config object for the system performance configuration.
33    *
34    * @var \Drupal\Core\Config\Config
35    */
36   protected $config;
37
38   /**
39    * The CSS asset collection renderer service.
40    *
41    * @var \Drupal\Core\Asset\AssetCollectionRendererInterface
42    */
43   protected $cssCollectionRenderer;
44
45   /**
46    * The JS asset collection renderer service.
47    *
48    * @var \Drupal\Core\Asset\AssetCollectionRendererInterface
49    */
50   protected $jsCollectionRenderer;
51
52   /**
53    * The request stack.
54    *
55    * @var \Symfony\Component\HttpFoundation\RequestStack
56    */
57   protected $requestStack;
58
59   /**
60    * The renderer.
61    *
62    * @var \Drupal\Core\Render\RendererInterface
63    */
64   protected $renderer;
65
66   /**
67    * The module handler.
68    *
69    * @var \Drupal\Core\Extension\ModuleHandlerInterface
70    */
71   protected $moduleHandler;
72
73   /**
74    * Constructs a AjaxResponseAttachmentsProcessor object.
75    *
76    * @param \Drupal\Core\Asset\AssetResolverInterface $asset_resolver
77    *   An asset resolver.
78    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
79    *   A config factory for retrieving required config objects.
80    * @param \Drupal\Core\Asset\AssetCollectionRendererInterface $css_collection_renderer
81    *   The CSS asset collection renderer.
82    * @param \Drupal\Core\Asset\AssetCollectionRendererInterface $js_collection_renderer
83    *   The JS asset collection renderer.
84    * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
85    *   The request stack.
86    * @param \Drupal\Core\Render\RendererInterface $renderer
87    *   The renderer.
88    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
89    *   The module handler.
90    */
91   public function __construct(AssetResolverInterface $asset_resolver, ConfigFactoryInterface $config_factory, AssetCollectionRendererInterface $css_collection_renderer, AssetCollectionRendererInterface $js_collection_renderer, RequestStack $request_stack, RendererInterface $renderer, ModuleHandlerInterface $module_handler) {
92     $this->assetResolver = $asset_resolver;
93     $this->config = $config_factory->get('system.performance');
94     $this->cssCollectionRenderer = $css_collection_renderer;
95     $this->jsCollectionRenderer = $js_collection_renderer;
96     $this->requestStack = $request_stack;
97     $this->renderer = $renderer;
98     $this->moduleHandler = $module_handler;
99   }
100
101   /**
102    * {@inheritdoc}
103    */
104   public function processAttachments(AttachmentsInterface $response) {
105     // @todo Convert to assertion once https://www.drupal.org/node/2408013 lands
106     if (!$response instanceof AjaxResponse) {
107       throw new \InvalidArgumentException('\Drupal\Core\Ajax\AjaxResponse instance expected.');
108     }
109
110     $request = $this->requestStack->getCurrentRequest();
111
112     if ($response->getContent() == '{}') {
113       $response->setData($this->buildAttachmentsCommands($response, $request));
114     }
115
116     return $response;
117   }
118
119   /**
120    * Prepares the AJAX commands to attach assets.
121    *
122    * @param \Drupal\Core\Ajax\AjaxResponse $response
123    *   The AJAX response to update.
124    * @param \Symfony\Component\HttpFoundation\Request $request
125    *   The request object that the AJAX is responding to.
126    *
127    * @return array
128    *   An array of commands ready to be returned as JSON.
129    */
130   protected function buildAttachmentsCommands(AjaxResponse $response, Request $request) {
131     $ajax_page_state = $request->request->get('ajax_page_state');
132
133     // Aggregate CSS/JS if necessary, but only during normal site operation.
134     $optimize_css = !defined('MAINTENANCE_MODE') && $this->config->get('css.preprocess');
135     $optimize_js = !defined('MAINTENANCE_MODE') && $this->config->get('js.preprocess');
136
137     $attachments = $response->getAttachments();
138
139     // Resolve the attached libraries into asset collections.
140     $assets = new AttachedAssets();
141     $assets->setLibraries(isset($attachments['library']) ? $attachments['library'] : [])
142       ->setAlreadyLoadedLibraries(isset($ajax_page_state['libraries']) ? explode(',', $ajax_page_state['libraries']) : [])
143       ->setSettings(isset($attachments['drupalSettings']) ? $attachments['drupalSettings'] : []);
144     $css_assets = $this->assetResolver->getCssAssets($assets, $optimize_css);
145     list($js_assets_header, $js_assets_footer) = $this->assetResolver->getJsAssets($assets, $optimize_js);
146
147     // First, AttachedAssets::setLibraries() ensures duplicate libraries are
148     // removed: it converts it to a set of libraries if necessary. Second,
149     // AssetResolver::getJsSettings() ensures $assets contains the final set of
150     // JavaScript settings. AttachmentsResponseProcessorInterface also mandates
151     // that the response it processes contains the final attachment values, so
152     // update both the 'library' and 'drupalSettings' attachments accordingly.
153     $attachments['library'] = $assets->getLibraries();
154     $attachments['drupalSettings'] = $assets->getSettings();
155     $response->setAttachments($attachments);
156
157     // Render the HTML to load these files, and add AJAX commands to insert this
158     // HTML in the page. Settings are handled separately, afterwards.
159     $settings = [];
160     if (isset($js_assets_header['drupalSettings'])) {
161       $settings = $js_assets_header['drupalSettings']['data'];
162       unset($js_assets_header['drupalSettings']);
163     }
164     if (isset($js_assets_footer['drupalSettings'])) {
165       $settings = $js_assets_footer['drupalSettings']['data'];
166       unset($js_assets_footer['drupalSettings']);
167     }
168
169     // Prepend commands to add the assets, preserving their relative order.
170     $resource_commands = [];
171     if ($css_assets) {
172       $css_render_array = $this->cssCollectionRenderer->render($css_assets);
173       $resource_commands[] = new AddCssCommand($this->renderer->renderPlain($css_render_array));
174     }
175     if ($js_assets_header) {
176       $js_header_render_array = $this->jsCollectionRenderer->render($js_assets_header);
177       $resource_commands[] = new PrependCommand('head', $this->renderer->renderPlain($js_header_render_array));
178     }
179     if ($js_assets_footer) {
180       $js_footer_render_array = $this->jsCollectionRenderer->render($js_assets_footer);
181       $resource_commands[] = new AppendCommand('body', $this->renderer->renderPlain($js_footer_render_array));
182     }
183     foreach (array_reverse($resource_commands) as $resource_command) {
184       $response->addCommand($resource_command, TRUE);
185     }
186
187     // Prepend a command to merge changes and additions to drupalSettings.
188     if (!empty($settings)) {
189       // During Ajax requests basic path-specific settings are excluded from
190       // new drupalSettings values. The original page where this request comes
191       // from already has the right values. An Ajax request would update them
192       // with values for the Ajax request and incorrectly override the page's
193       // values.
194       // @see system_js_settings_alter()
195       unset($settings['path']);
196       $response->addCommand(new SettingsCommand($settings, TRUE), TRUE);
197     }
198
199     $commands = $response->getCommands();
200     $this->moduleHandler->alter('ajax_render', $commands);
201
202     return $commands;
203   }
204
205 }