/** * {@inheritdoc} */ public function process($text, $langcode) { $response = new FilterProcessResult($text); // Use a look ahead to match the capture groups in any order. if (preg_match_all('/<p>(?<json>{(?=.*preview_thumbnail\\b)(?=.*settings\\b)(?=.*video_url\\b)(?=.*settings_summary)(.*)})<\\/p>/', $text, $matches)) { foreach ($matches['json'] as $delta => $match) { // Ensure the JSON string is valid. $embed_data = json_decode($match, TRUE); if (!is_array($embed_data)) { continue; } // If the URL can't matched to a provider or the settings are invalid, // ignore it. $provider = $this->providerManager->loadProviderFromInput($embed_data['video_url']); if (!$provider || !$this->validSettings($embed_data['settings'])) { continue; } $embed_code = $provider->renderEmbedCode($embed_data['settings']['width'], $embed_data['settings']['height'], $embed_data['settings']['autoplay']); // Add the container to make the video responsive if it's been //configured as such. This usually is attached to field output in the // case of a formatter, but a custom container must be used where one is // not present. if ($embed_data['settings']['responsive']) { $embed_code = ['#type' => 'container', '#attributes' => ['class' => ['video-embed-field-responsive-video']], 'children' => $embed_code]; } // Replace the JSON settings with a video. $text = str_replace($matches[0][$delta], $this->renderer->renderRoot($embed_code), $text); } } // Add the required responsive video library and update the response text. $response->setProcessedText($text); $response->addAttachments(['library' => ['video_embed_field/responsive-video']]); return $response; }
/** * {@inheritdoc} */ public function viewElements(FieldItemListInterface $items, $langcode) { $element = []; $thumbnails = $this->thumbnailFormatter->viewElements($items, $langcode); $videos = $this->videoFormatter->viewElements($items, $langcode); foreach ($items as $delta => $item) { $element[$delta] = ['#type' => 'container', '#attributes' => ['data-video-embed-field-modal' => (string) $this->renderer->renderRoot($videos[$delta]), 'class' => ['video-embed-field-launch-modal']], '#attached' => ['library' => ['video_embed_field/colorbox']], 'children' => $thumbnails[$delta]]; } return $element; }
/** * @param \Symfony\Component\HttpFoundation\Response $response */ protected function injectToolbar(Response $response) { $content = $response->getContent(); $pos = mb_strripos($content, '</body>'); if (FALSE !== $pos) { if ($token = $response->headers->get('X-Debug-Token')) { $loader = ['#theme' => 'webprofiler_loader', '#token' => $token, '#profiler_url' => $this->urlGenerator->generate('webprofiler.toolbar', ['profile' => $token])]; $content = mb_substr($content, 0, $pos) . $this->renderer->renderRoot($loader) . mb_substr($content, $pos); $response->setContent($content); } } }
/** * {@inheritdoc} */ public function renderResponse(array $main_content, Request $request, RouteMatchInterface $route_match) { $json = []; $json['content'] = (string) $this->renderer->renderRoot($main_content); if (!empty($main_content['#title'])) { $json['title'] = (string) $main_content['#title']; } else { $json['title'] = (string) $this->titleResolver->getTitle($request, $route_match->getRouteObject()); } $response = new CacheableJsonResponse($json, 200); $response->addCacheableDependency(CacheableMetadata::createFromRenderArray($main_content)); return $response; }
/** * Asserts that a block is built/rendered/cached with expected cacheability. * * @param string[] $expected_keys * The expected cache keys. * @param string[] $expected_contexts * The expected cache contexts. * @param string[] $expected_tags * The expected cache tags. * @param int $expected_max_age * The expected max-age. */ protected function assertBlockRenderedWithExpectedCacheability(array $expected_keys, array $expected_contexts, array $expected_tags, $expected_max_age) { $required_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions']; // Check that the expected cacheability metadata is present in: // - the built render array; $this->pass('Built render array'); $build = $this->getBlockRenderArray(); $this->assertIdentical($expected_keys, $build['#cache']['keys']); $this->assertIdentical($expected_contexts, $build['#cache']['contexts']); $this->assertIdentical($expected_tags, $build['#cache']['tags']); $this->assertIdentical($expected_max_age, $build['#cache']['max-age']); $this->assertFalse(isset($build['#create_placeholder'])); // - the rendered render array; $this->pass('Rendered render array'); $this->renderer->renderRoot($build); // - the render cache item. $this->pass('Render cache item'); $final_cache_contexts = Cache::mergeContexts($expected_contexts, $required_cache_contexts); $cid = implode(':', $expected_keys) . ':' . implode(':', \Drupal::service('cache_contexts_manager')->convertTokensToKeys($final_cache_contexts)->getKeys()); $cache_item = $this->container->get('cache.render')->get($cid); $this->assertTrue($cache_item, 'The block render element has been cached with the expected cache ID.'); $this->assertIdentical(Cache::mergeTags($expected_tags, ['rendered']), $cache_item->tags); $this->assertIdentical($final_cache_contexts, $cache_item->data['#cache']['contexts']); $this->assertIdentical($expected_tags, $cache_item->data['#cache']['tags']); $this->assertIdentical($expected_max_age, $cache_item->data['#cache']['max-age']); $this->container->get('cache.render')->delete($cid); }
/** * {@inheritdoc} */ public function viewElements(FieldItemListInterface $items, $langcode) { $element = []; $thumbnails = $this->thumbnailFormatter->viewElements($items, $langcode); $videos = $this->videoFormatter->viewElements($items, $langcode); foreach ($items as $delta => $item) { // Support responsive videos within the colorbox modal. if ($this->getSetting('responsive')) { $videos[$delta]['#attributes']['class'][] = 'video-embed-field-responsive-modal'; $videos[$delta]['#attributes']['style'] = sprintf('width:%dpx;', $this->getSetting('modal_max_width')); } $element[$delta] = ['#type' => 'container', '#attributes' => ['data-video-embed-field-modal' => (string) $this->renderer->renderRoot($videos[$delta]), 'class' => ['video-embed-field-launch-modal']], '#attached' => ['library' => ['video_embed_field/colorbox', 'video_embed_field/responsive-video']], 'children' => $thumbnails[$delta]]; } $this->colorboxAttachment->attach($element); return $element; }
/** * {@inheritdoc} */ public function renderResponse(array $main_content, Request $request, RouteMatchInterface $route_match) { $response = new AjaxResponse(); // First render the main content, because it might provide a title. $content = $this->renderer->renderRoot($main_content); // Attach the library necessary for using the OpenOffCanvasDialogCommand and // set the attachments for this Ajax response. $main_content['#attached']['library'][] = 'outside_in/drupal.off_canvas'; $response->setAttachments($main_content['#attached']); // If the main content doesn't provide a title, use the title resolver. $title = isset($main_content['#title']) ? $main_content['#title'] : $this->titleResolver->getTitle($request, $route_match->getRouteObject()); // Determine the title: use the title provided by the main content if any, // otherwise get it from the routing information. $options = $request->request->get('dialogOptions', []); $response->addCommand(new OpenOffCanvasDialogCommand($title, $content, $options)); return $response; }
/** * Tests block view altering. */ public function testBlockViewBuilderAlter() { // Establish baseline. $build = $this->getBlockRenderArray(); $this->assertIdentical((string) $this->renderer->renderRoot($build), 'Llamas > unicorns!'); // Enable the block view alter hook that adds a suffix, for basic testing. \Drupal::state()->set('block_test_view_alter_suffix', TRUE); Cache::invalidateTags($this->block->getCacheTagsToInvalidate()); $build = $this->getBlockRenderArray(); $this->assertTrue(isset($build['#suffix']) && $build['#suffix'] === '<br>Goodbye!', 'A block with content is altered.'); $this->assertIdentical((string) $this->renderer->renderRoot($build), 'Llamas > unicorns!<br>Goodbye!'); \Drupal::state()->set('block_test_view_alter_suffix', FALSE); // Force a request via GET so we can test the render cache. $request = \Drupal::request(); $request_method = $request->server->get('REQUEST_METHOD'); $request->setMethod('GET'); \Drupal::state()->set('block_test.content', NULL); Cache::invalidateTags($this->block->getCacheTagsToInvalidate()); $default_keys = array('entity_view', 'block', 'test_block'); $default_tags = array('block_view', 'config:block.block.test_block'); // Advanced: cached block, but an alter hook adds an additional cache key. $alter_add_key = $this->randomMachineName(); \Drupal::state()->set('block_test_view_alter_cache_key', $alter_add_key); $cid = 'entity_view:block:test_block:' . $alter_add_key . ':' . implode(':', \Drupal::service('cache_contexts_manager')->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'])->getKeys()); $expected_keys = array_merge($default_keys, array($alter_add_key)); $build = $this->getBlockRenderArray(); $this->assertIdentical($expected_keys, $build['#cache']['keys'], 'An altered cacheable block has the expected cache keys.'); $this->assertIdentical((string) $this->renderer->renderRoot($build), ''); $cache_entry = $this->container->get('cache.render')->get($cid); $this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.'); $expected_tags = array_merge($default_tags, ['rendered']); sort($expected_tags); $this->assertIdentical($cache_entry->tags, $expected_tags, 'The block render element has been cached with the expected cache tags.'); $this->container->get('cache.render')->delete($cid); // Advanced: cached block, but an alter hook adds an additional cache tag. $alter_add_tag = $this->randomMachineName(); \Drupal::state()->set('block_test_view_alter_cache_tag', $alter_add_tag); $expected_tags = Cache::mergeTags($default_tags, array($alter_add_tag)); $build = $this->getBlockRenderArray(); sort($build['#cache']['tags']); $this->assertIdentical($expected_tags, $build['#cache']['tags'], 'An altered cacheable block has the expected cache tags.'); $this->assertIdentical((string) $this->renderer->renderRoot($build), ''); $cache_entry = $this->container->get('cache.render')->get($cid); $this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.'); $expected_tags = array_merge($default_tags, [$alter_add_tag, 'rendered']); sort($expected_tags); $this->assertIdentical($cache_entry->tags, $expected_tags, 'The block render element has been cached with the expected cache tags.'); $this->container->get('cache.render')->delete($cid); // Advanced: cached block, but an alter hook adds a #pre_render callback to // alter the eventual content. \Drupal::state()->set('block_test_view_alter_append_pre_render_prefix', TRUE); $build = $this->getBlockRenderArray(); $this->assertFalse(isset($build['#prefix']), 'The appended #pre_render callback has not yet run before rendering.'); $this->assertIdentical((string) $this->renderer->renderRoot($build), 'Hiya!<br>'); $this->assertTrue(isset($build['#prefix']) && $build['#prefix'] === 'Hiya!<br>', 'A cached block without content is altered.'); // Restore the previous request method. $request->setMethod($request_method); }
/** * Renders an array of assets. * * @param array $all_assets * An array of all unrendered assets keyed by type. * * @return array * An array of all rendered assets keyed by type. */ protected function renderAssets(array $all_assets) { $result = []; foreach ($all_assets as $asset_type => $assets) { $result[$asset_type] = []; foreach ($assets as $asset) { $result[$asset_type][] = $this->renderer->renderRoot($asset); } } return $result; }
/** * Generates representations of a book page and its children. * * The method delegates the generation of output to helper methods. The method * name is derived by prepending 'bookExport' to the camelized form of given * output type. For example, a type of 'html' results in a call to the method * bookExportHtml(). * * @param string $type * A string encoding the type of output requested. The following types are * currently supported in book module: * - html: Printer-friendly HTML. * Other types may be supported in contributed modules. * @param \Drupal\node\NodeInterface $node * The node to export. * * @return array * A render array representing the node and its children in the book * hierarchy in a format determined by the $type parameter. * * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException */ public function bookExport($type, NodeInterface $node) { $method = 'bookExport' . Container::camelize($type); // @todo Convert the custom export functionality to serializer. if (!method_exists($this->bookExport, $method)) { drupal_set_message(t('Unknown export format.')); throw new NotFoundHttpException(); } $exported_book = $this->bookExport->{$method}($node); return new Response($this->renderer->renderRoot($exported_book)); }
/** * {@inheritdoc} */ public function execute() { parent::execute(); $output = $this->view->render(); $header = []; $header['Content-type'] = $this->getMimeType(); $response = new CacheableResponse($this->renderer->renderRoot($output), 200); $cache_metadata = CacheableMetadata::createFromRenderArray($output); $response->addCacheableDependency($cache_metadata); return $response; }
/** * Renders placeholders (#attached['placeholders']). * * First, the HTML response object is converted to an equivalent render array, * with #markup being set to the response's content and #attached being set to * the response's attachments. Among these attachments, there may be * placeholders that need to be rendered (replaced). * * Next, RendererInterface::renderRoot() is called, which renders the * placeholders into their final markup. * * The markup that results from RendererInterface::renderRoot() is now the * original HTML response's content, but with the placeholders rendered. We * overwrite the existing content in the original HTML response object with * this markup. The markup that was rendered for the placeholders may also * have attachments (e.g. for CSS/JS assets) itself, and cacheability metadata * that indicates what that markup depends on. That metadata is also added to * the HTML response object. * * @param \Drupal\Core\Render\HtmlResponse $response * The HTML response whose placeholders are being replaced. * * @return \Drupal\Core\Render\HtmlResponse * The updated HTML response, with replaced placeholders. * * @see \Drupal\Core\Render\Renderer::replacePlaceholders() * @see \Drupal\Core\Render\Renderer::renderPlaceholder() */ protected function renderPlaceholders(HtmlResponse $response) { $build = ['#markup' => Markup::create($response->getContent()), '#attached' => $response->getAttachments()]; // RendererInterface::renderRoot() renders the $build render array and // updates it in place. We don't care about the return value (which is just // $build['#markup']), but about the resulting render array. // @todo Simplify this when https://www.drupal.org/node/2495001 lands. $this->renderer->renderRoot($build); // Update the Response object now that the placeholders have been rendered. $placeholders_bubbleable_metadata = BubbleableMetadata::createFromRenderArray($build); $response->setContent($build['#markup'])->addCacheableDependency($placeholders_bubbleable_metadata)->setAttachments($placeholders_bubbleable_metadata->getAttachments()); return $response; }
/** * Renders a field. * * If the view mode ID is not an Entity Display view mode ID, then the field * was rendered using a custom render pipeline (not the Entity/Field API * render pipeline). * * An example could be Views' render pipeline. In that case, the view mode ID * would probably contain the View's ID, display and the row index. * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity being edited. * @param string $field_name * The name of the field that is being edited. * @param string $langcode * The name of the language for which the field is being edited. * @param string $view_mode_id * The view mode the field should be rerendered in. Either an Entity Display * view mode ID, or a custom one. See hook_quickedit_render_field(). * * @return \Drupal\Component\Render\MarkupInterface * Rendered HTML. * * @see hook_quickedit_render_field() */ protected function renderField(EntityInterface $entity, $field_name, $langcode, $view_mode_id) { $entity_view_mode_ids = array_keys($this->entityManager()->getViewModes($entity->getEntityTypeId())); if (in_array($view_mode_id, $entity_view_mode_ids)) { $entity = \Drupal::entityManager()->getTranslationFromContext($entity, $langcode); $output = $entity->get($field_name)->view($view_mode_id); } else { // Each part of a custom (non-Entity Display) view mode ID is separated // by a dash; the first part must be the module name. $mode_id_parts = explode('-', $view_mode_id, 2); $module = reset($mode_id_parts); $args = array($entity, $field_name, $view_mode_id, $langcode); $output = $this->moduleHandler()->invoke($module, 'quickedit_render_field', $args); } return $this->renderer->renderRoot($output); }
/** * {@inheritdoc} * * The entire HTML: takes a #type 'page' and wraps it in a #type 'html'. */ public function renderResponse(array $main_content, Request $request, RouteMatchInterface $route_match) { list($page, $title) = $this->prepare($main_content, $request, $route_match); if (!isset($page['#type']) || $page['#type'] !== 'page') { throw new \LogicException('Must be #type page'); } $page['#title'] = $title; // Now render the rendered page.html.twig template inside the html.html.twig // template, and use the bubbled #attached metadata from $page to ensure we // load all attached assets. $html = ['#type' => 'html', 'page' => $page]; // The special page regions will appear directly in html.html.twig, not in // page.html.twig, hence add them here, just before rendering html.html.twig. $this->buildPageTopAndBottom($html); // @todo https://www.drupal.org/node/2495001 Make renderRoot return a // cacheable render array directly. $this->renderer->renderRoot($html); $content = $this->renderCache->getCacheableRenderArray($html); // Also associate the "rendered" cache tag. This allows us to invalidate the // entire render cache, regardless of the cache bin. $content['#cache']['tags'][] = 'rendered'; $response = new HtmlResponse($content, 200, ['Content-Type' => 'text/html; charset=UTF-8']); return $response; }