/** * {@inheritdoc} */ public function build() { // Grab test attachment fixtures from // Drupal\render_attached_test\Controller\RenderAttachedTestController. $controller = new RenderAttachedTestController(); $attached = BubbleableMetadata::mergeAttachments($controller->feed(), $controller->head()); $attached = BubbleableMetadata::mergeAttachments($attached, $controller->header()); $attached = BubbleableMetadata::mergeAttachments($attached, $controller->teapotHeaderStatus()); // Return some arbitrary markup so the block doesn't disappear. $attached['#markup'] = 'Markup from attached_rendering_block.'; return $attached; }
/** * {@inheritdoc} */ public function build() { // Grab test attachment fixtures from // Drupal\render_attached_test\Controller\TestController. $controller = new TestController(); $attached = BubbleableMetadata::mergeAttachments($controller->feed(), $controller->head()); $attached = BubbleableMetadata::mergeAttachments($attached, $controller->header()); $attached = BubbleableMetadata::mergeAttachments($attached, $controller->teapotHeaderStatus()); // Use drupal_process_attached() to attach all the #attached stuff. drupal_process_attached($attached); // Return some arbitrary markup so the block doesn't disappear. return ['#markup' => 'Headers handled by drupal_process_attached().']; }
/** * 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->getAttachments(), $attachments); $this->setAttachments($attachments); } return $this; }
/** * Tests http_header asset merging. * * @covers ::mergeAttachments * * @dataProvider providerTestMergeAttachmentsHttpHeaderMerging */ function testMergeAttachmentsHttpHeaderMerging($a, $b, $expected) { $this->assertSame($expected, BubbleableMetadata::mergeAttachments($a, $b)); }
/** * {@inheritdoc} */ public function processAttachments(AttachmentsInterface $response) { // @todo Convert to assertion once https://www.drupal.org/node/2408013 lands if (!$response instanceof HtmlResponse) { throw new \InvalidArgumentException('\\Drupal\\Core\\Render\\HtmlResponse instance expected.'); } // First, render the actual placeholders; this may cause additional // attachments to be added to the response, which the attachment // placeholders rendered by renderHtmlResponseAttachmentPlaceholders() will // need to include. // // @todo Exceptions should not be used for code flow control. However, the // Form API does not integrate with the HTTP Kernel based architecture of // Drupal 8. In order to resolve this issue properly it is necessary to // completely separate form submission from rendering. // @see https://www.drupal.org/node/2367555 try { $response = $this->renderPlaceholders($response); } catch (EnforcedResponseException $e) { return $e->getResponse(); } // Get a reference to the attachments. $attached = $response->getAttachments(); // Send a message back if the render array has unsupported #attached types. $unsupported_types = array_diff(array_keys($attached), ['html_head', 'feed', 'html_head_link', 'http_header', 'library', 'html_response_attachment_placeholders', 'placeholders', 'drupalSettings']); if (!empty($unsupported_types)) { throw new \LogicException(sprintf('You are not allowed to use %s in #attached.', implode(', ', $unsupported_types))); } // If we don't have any placeholders, there is no need to proceed. if (!empty($attached['html_response_attachment_placeholders'])) { // Get the placeholders from attached and then remove them. $attachment_placeholders = $attached['html_response_attachment_placeholders']; unset($attached['html_response_attachment_placeholders']); $assets = AttachedAssets::createFromRenderArray(['#attached' => $attached]); // Take Ajax page state into account, to allow for something like // Turbolinks to be implemented without altering core. // @see https://github.com/rails/turbolinks/ $ajax_page_state = $this->requestStack->getCurrentRequest()->get('ajax_page_state'); $assets->setAlreadyLoadedLibraries(isset($ajax_page_state) ? explode(',', $ajax_page_state['libraries']) : []); $variables = $this->processAssetLibraries($assets, $attachment_placeholders); // $variables now contains the markup to load the asset libraries. Update // $attached with the final list of libraries and JavaScript settings, so // that $response can be updated with those. Then the response object will // list the final, processed attachments. $attached['library'] = $assets->getLibraries(); $attached['drupalSettings'] = $assets->getSettings(); // Since we can only replace content in the HTML head section if there's a // placeholder for it, we can safely avoid processing the render array if // it's not present. if (!empty($attachment_placeholders['head'])) { // 'feed' is a special case of 'html_head_link'. We process them into // 'html_head_link' entries and merge them. if (!empty($attached['feed'])) { $attached = BubbleableMetadata::mergeAttachments($attached, $this->processFeed($attached['feed'])); unset($attached['feed']); } // 'html_head_link' is a special case of 'html_head' which can be present // as a head element, but also as a Link: HTTP header depending on // settings in the render array. Processing it can add to both the // 'html_head' and 'http_header' keys of '#attached', so we must address // it before 'html_head'. if (!empty($attached['html_head_link'])) { // Merge the processed 'html_head_link' into $attached so that its // 'html_head' and 'http_header' values are present for further // processing. $attached = BubbleableMetadata::mergeAttachments($attached, $this->processHtmlHeadLink($attached['html_head_link'])); unset($attached['html_head_link']); } // Now we can process 'html_head', which contains both 'feed' and // 'html_head_link'. if (!empty($attached['html_head'])) { $variables['head'] = $this->processHtmlHead($attached['html_head']); } } // Now replace the attachment placeholders. $this->renderHtmlResponseAttachmentPlaceholders($response, $attachment_placeholders, $variables); } // Set the HTTP headers and status code on the response if any bubbled. if (!empty($attached['http_header'])) { $this->setHeaders($response, $attached['http_header']); } // AttachmentsResponseProcessorInterface mandates that the response it // processes contains the final attachment values. $response->setAttachments($attached); return $response; }
/** * {@inheritdoc} */ public function processAttachments(AttachmentsInterface $response) { // @todo Convert to assertion once https://www.drupal.org/node/2408013 lands if (!$response instanceof HtmlResponse) { throw new \InvalidArgumentException('\\Drupal\\Core\\Render\\HtmlResponse instance expected.'); } // First, render the actual placeholders; this may cause additional // attachments to be added to the response, which the attachment // placeholders rendered by renderHtmlResponseAttachmentPlaceholders() will // need to include. // // @todo Exceptions should not be used for code flow control. However, the // Form API does not integrate with the HTTP Kernel based architecture of // Drupal 8. In order to resolve this issue properly it is necessary to // completely separate form submission from rendering. // @see https://www.drupal.org/node/2367555 try { $response = $this->renderPlaceholders($response); } catch (EnforcedResponseException $e) { return $e->getResponse(); } // Get a reference to the attachments. $attached = $response->getAttachments(); // Send a message back if the render array has unsupported #attached types. $unsupported_types = array_diff(array_keys($attached), ['html_head', 'feed', 'html_head_link', 'http_header', 'library', 'html_response_attachment_placeholders', 'placeholders', 'drupalSettings']); if (!empty($unsupported_types)) { throw new \LogicException(sprintf('You are not allowed to use %s in #attached.', implode(', ', $unsupported_types))); } // Get the placeholders from attached and then remove them. $attachment_placeholders = $attached['html_response_attachment_placeholders']; unset($attached['html_response_attachment_placeholders']); $variables = $this->processAssetLibraries($attached, $attachment_placeholders); // Since we can only replace content in the HTML head section if there's a // placeholder for it, we can safely avoid processing the render array if // it's not present. if (!empty($attachment_placeholders['head'])) { // 'feed' is a special case of 'html_head_link'. We process them into // 'html_head_link' entries and merge them. if (!empty($attached['feed'])) { $attached = BubbleableMetadata::mergeAttachments($attached, $this->processFeed($attached['feed'])); } // 'html_head_link' is a special case of 'html_head' which can be present // as a head element, but also as a Link: HTTP header depending on // settings in the render array. Processing it can add to both the // 'html_head' and 'http_header' keys of '#attached', so we must address // it before 'html_head'. if (!empty($attached['html_head_link'])) { // Merge the processed 'html_head_link' into $attached so that its // 'html_head' and 'http_header' values are present for further // processing. $attached = BubbleableMetadata::mergeAttachments($attached, $this->processHtmlHeadLink($attached['html_head_link'])); } // Now we can process 'html_head', which contains both 'feed' and // 'html_head_link'. if (!empty($attached['html_head'])) { $html_head = $this->processHtmlHead($attached['html_head']); // Invoke hook_html_head_alter(). $this->moduleHandler->alter('html_head', $html_head); // Store the result in $variables so it can be inserted into the // placeholder. $variables['head'] = $html_head; } } // Now replace the attachment placeholders. $this->renderHtmlResponseAttachmentPlaceholders($response, $attachment_placeholders, $variables); // Set the HTTP headers and status code on the response if any bubbled. if (!empty($attached['http_header'])) { $this->setHeaders($response, $attached['http_header']); } return $response; }
/** * Additional #pre_render callback for 'text_format' elements. */ function preRenderTextFormat(array $element) { // Allow modules to programmatically enforce no client-side editor by // setting the #editor property to FALSE. if (isset($element['#editor']) && !$element['#editor']) { return $element; } // filter_process_format() copies properties to the expanded 'value' child // element, including the #pre_render property. Skip this text format // widget, if it contains no 'format'. if (!isset($element['format'])) { return $element; } $format_ids = array_keys($element['format']['format']['#options']); // Early-return if no text editor is associated with any of the text formats. $editors = Editor::loadMultiple($format_ids); foreach ($editors as $key => $editor) { $definition = $this->pluginManager->getDefinition($editor->getEditor()); if (!in_array($element['#base_type'], $definition['supported_element_types'])) { unset($editors[$key]); } } if (count($editors) === 0) { return $element; } // Use a hidden element for a single text format. $field_id = $element['value']['#id']; if (!$element['format']['format']['#access']) { // Use the first (and only) available text format. $format_id = $format_ids[0]; $element['format']['editor'] = array('#type' => 'hidden', '#name' => $element['format']['format']['#name'], '#value' => $format_id, '#attributes' => array('data-editor-for' => $field_id)); } else { $element['format']['format']['#attributes']['class'][] = 'editor'; $element['format']['format']['#attributes']['data-editor-for'] = $field_id; } // Hide the text format's filters' guidelines of those text formats that have // a text editor associated: they're rather useless when using a text editor. foreach ($editors as $format_id => $editor) { $element['format']['guidelines'][$format_id]['#access'] = FALSE; } // Attach Text Editor module's (this module) library. $element['#attached']['library'][] = 'editor/drupal.editor'; // Attach attachments for all available editors. $element['#attached'] = BubbleableMetadata::mergeAttachments($element['#attached'], $this->pluginManager->getAttachments($format_ids)); // Apply XSS filters when editing content if necessary. Some types of text // editors cannot guarantee that the end user won't become a victim of XSS. if (!empty($element['value']['#value'])) { $original = $element['value']['#value']; $format = FilterFormat::load($element['format']['format']['#value']); // Ensure XSS-safety for the current text format/editor. $filtered = editor_filter_xss($original, $format); if ($filtered !== FALSE) { $element['value']['#value'] = $filtered; } // Only when the user has access to multiple text formats, we must add data- // attributes for the original value and change tracking, because they are // only necessary when the end user can switch between text formats/editors. if ($element['format']['format']['#access']) { $element['value']['#attributes']['data-editor-value-is-changed'] = 'false'; $element['value']['#attributes']['data-editor-value-original'] = $original; } } return $element; }