/** * {@inheritdoc} */ public function getDescription() { $locked = $this->tempStore->getMetadata($this->entity->id()); $account = $this->entityTypeManager->getStorage('user')->load($locked->owner); $username = array('#theme' => 'username', '#account' => $account); return $this->t('By breaking this lock, any unsaved changes made by @user will be lost.', array('@user' => $this->renderer->render($username))); }
/** * {@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; }
/** * Loops through and displays all form errors. * * @param array $form * An associative array containing the structure of the form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. */ protected function displayErrorMessages(array $form, FormStateInterface $form_state) { $error_links = []; $errors = $form_state->getErrors(); // Loop through all form errors and check if we need to display a link. foreach ($errors as $name => $error) { $form_element = FormElementHelper::getElementByName($name, $form); $title = FormElementHelper::getElementTitle($form_element); // Only show links to erroneous elements that are visible. $is_visible_element = Element::isVisibleElement($form_element); // Only show links for elements that have a title themselves or have // children with a title. $has_title = !empty($title); // Only show links for elements with an ID. $has_id = !empty($form_element['#id']); // Do not show links to elements with suppressed messages. Most often // their parent element is used for inline errors. if (!empty($form_element['#error_no_message'])) { unset($errors[$name]); } elseif ($is_visible_element && $has_title && $has_id) { $error_links[] = $this->l($title, Url::fromRoute('<none>', [], ['fragment' => $form_element['#id'], 'external' => TRUE])); unset($errors[$name]); } } // Set normal error messages for all remaining errors. foreach ($errors as $error) { $this->drupalSetMessage($error, 'error'); } if (!empty($error_links)) { $render_array = [['#markup' => $this->formatPlural(count($error_links), '1 error has been found: ', '@count errors have been found: ')], ['#theme' => 'item_list', '#items' => $error_links, '#context' => ['list_style' => 'comma-list']]]; $message = $this->renderer->renderPlain($render_array); $this->drupalSetMessage($message, 'error'); } }
/** * {@inheritdoc} */ public function getDescription() { $locked = $this->rulesUiHandler->getLockMetaData(); $account = $this->entityTypeManager->getStorage('user')->load($locked->owner); $username = ['#theme' => 'username', '#account' => $account]; return $this->t('By breaking this lock, any unsaved changes made by @user will be lost.', ['@user' => $this->renderer->render($username)]); }
/** * {@inheritdoc} */ protected function setUp() { $this->viewStorage = $this->getMock('Drupal\\Core\\Entity\\EntityStorageInterface'); $this->executableFactory = $this->getMockBuilder('Drupal\\views\\ViewExecutableFactory')->disableOriginalConstructor()->getMock(); $this->renderer = $this->getMock('\\Drupal\\Core\\Render\\RendererInterface'); $this->renderer->expects($this->any())->method('render')->will($this->returnCallback(function (array &$elements) { $elements['#attached'] = []; return isset($elements['#markup']) ? $elements['#markup'] : ''; })); $this->renderer->expects($this->any())->method('executeInRenderContext')->willReturnCallback(function (RenderContext $context, callable $callable) { return $callable(); }); $this->currentPath = $this->getMockBuilder('Drupal\\Core\\Path\\CurrentPathStack')->disableOriginalConstructor()->getMock(); $this->redirectDestination = $this->getMock('\\Drupal\\Core\\Routing\\RedirectDestinationInterface'); $this->viewAjaxController = new ViewAjaxController($this->viewStorage, $this->executableFactory, $this->renderer, $this->currentPath, $this->redirectDestination); $element_info_manager = $this->getMock('\\Drupal\\Core\\Render\\ElementInfoManagerInterface'); $element_info_manager->expects($this->any())->method('getInfo')->with('markup')->willReturn(['#pre_render' => [[Markup::class, 'ensureMarkupIsSafe']], '#defaults_loaded' => TRUE]); $request_stack = new RequestStack(); $request_stack->push(new Request()); $args = [$this->getMock('\\Drupal\\Core\\Controller\\ControllerResolverInterface'), $this->getMock('\\Drupal\\Core\\Theme\\ThemeManagerInterface'), $element_info_manager, $this->getMock('\\Drupal\\Core\\Render\\RenderCacheInterface'), $request_stack, ['required_cache_contexts' => ['languages:language_interface', 'theme']]]; $this->renderer = $this->getMockBuilder('Drupal\\Core\\Render\\Renderer')->setConstructorArgs($args)->setMethods(NULL)->getMock(); $container = new ContainerBuilder(); $container->set('renderer', $this->renderer); \Drupal::setContainer($container); }
/** * Displays add links for the available bundles. * * Redirects to the add form if there's only one bundle available. * * @param string $entity_type_id * The entity type ID. * @param \Symfony\Component\HttpFoundation\Request $request * The request. * * @return \Symfony\Component\HttpFoundation\RedirectResponse|array * If there's only one available bundle, a redirect response. * Otherwise, a render array with the add links for each bundle. */ public function addPage($entity_type_id, Request $request) { $entity_type = $this->entityTypeManager()->getDefinition($entity_type_id); $bundle_type = $entity_type->getBundleEntityType(); $bundle_key = $entity_type->getKey('bundle'); $form_route_name = 'entity.' . $entity_type_id . '.add_form'; $build = ['#theme' => 'entity_add_list', '#cache' => ['tags' => $entity_type->getListCacheTags()], '#bundle_type' => $bundle_type]; $bundles = $this->entityTypeBundleInfo->getBundleInfo($entity_type_id); // Filter out the bundles the user doesn't have access to. $access_control_handler = $this->entityTypeManager()->getAccessControlHandler($bundle_type); foreach ($bundles as $bundle_name => $bundle_info) { $access = $access_control_handler->createAccess($bundle_name, NULL, [], TRUE); if (!$access->isAllowed()) { unset($bundles[$bundle_name]); } $this->renderer->addCacheableDependency($build, $access); } // Redirect if there's only one bundle available. if (count($bundles) == 1) { $bundle_names = array_keys($bundles); $bundle_name = reset($bundle_names); return $this->redirect($form_route_name, [$bundle_key => $bundle_name]); } // Prepare the #bundles array for the template. $bundles = $this->loadBundleDescriptions($bundles, $bundle_type); foreach ($bundles as $bundle_name => $bundle_info) { $build['#bundles'][$bundle_name] = ['label' => $bundle_info['label'], 'description' => $bundle_info['description'], 'add_link' => Link::createFromRoute($bundle_info['label'], $form_route_name, [$bundle_key => $bundle_name])]; } return $build; }
/** * {@inheritdoc} * * For anonymous users, the "active" class will be calculated on the server, * because most sites serve each anonymous user the same cached page anyway. * For authenticated users, the "active" class will be calculated on the * client (through JavaScript), only data- attributes are added to links to * prevent breaking the render cache. The JavaScript is added in * system_page_attachments(). * * @see system_page_attachments() */ public function generate($text, Url $url, $collect_cacheability_metadata = FALSE) { // Performance: avoid Url::toString() needing to retrieve the URL generator // service from the container. $url->setUrlGenerator($this->urlGenerator); // Start building a structured representation of our link to be altered later. $variables = array('text' => is_array($text) ? $this->renderer->render($text) : $text, 'url' => $url, 'options' => $url->getOptions()); // Merge in default options. $variables['options'] += array('attributes' => array(), 'query' => array(), 'language' => NULL, 'set_active_class' => FALSE, 'absolute' => FALSE); // Add a hreflang attribute if we know the language of this link's url and // hreflang has not already been set. if (!empty($variables['options']['language']) && !isset($variables['options']['attributes']['hreflang'])) { $variables['options']['attributes']['hreflang'] = $variables['options']['language']->getId(); } // Set the "active" class if the 'set_active_class' option is not empty. if (!empty($variables['options']['set_active_class']) && !$url->isExternal()) { // Add a "data-drupal-link-query" attribute to let the // drupal.active-link library know the query in a standardized manner. if (!empty($variables['options']['query'])) { $query = $variables['options']['query']; ksort($query); $variables['options']['attributes']['data-drupal-link-query'] = Json::encode($query); } // Add a "data-drupal-link-system-path" attribute to let the // drupal.active-link library know the path in a standardized manner. if ($url->isRouted() && !isset($variables['options']['attributes']['data-drupal-link-system-path'])) { // @todo System path is deprecated - use the route name and parameters. $system_path = $url->getInternalPath(); // Special case for the front page. $variables['options']['attributes']['data-drupal-link-system-path'] = $system_path == '' ? '<front>' : $system_path; } } // Remove all HTML and PHP tags from a tooltip, calling expensive strip_tags() // only when a quick strpos() gives suspicion tags are present. if (isset($variables['options']['attributes']['title']) && strpos($variables['options']['attributes']['title'], '<') !== FALSE) { $variables['options']['attributes']['title'] = strip_tags($variables['options']['attributes']['title']); } // Allow other modules to modify the structure of the link. $this->moduleHandler->alter('link', $variables); // Move attributes out of options since generateFromRoute() doesn't need // them. Include a placeholder for the href. $attributes = array('href' => '') + $variables['options']['attributes']; unset($variables['options']['attributes']); $url->setOptions($variables['options']); if (!$collect_cacheability_metadata) { $url_string = $url->toString($collect_cacheability_metadata); } else { $generated_url = $url->toString($collect_cacheability_metadata); $url_string = $generated_url->getGeneratedUrl(); $generated_link = GeneratedLink::createFromObject($generated_url); } // The result of the URL generator is a plain-text URL to use as the href // attribute, and it is escaped by \Drupal\Core\Template\Attribute. $attributes['href'] = $url_string; $result = SafeMarkup::format('<a@attributes>@text</a>', array('@attributes' => new Attribute($attributes), '@text' => $variables['text'])); return $collect_cacheability_metadata ? $generated_link->setGeneratedLink($result) : $result; }
/** * {@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; }
/** * Tests bubbling of cacheable metadata for URLs. * * @param bool $collect_bubbleable_metadata * Whether bubbleable metadata should be collected. * @param int $invocations * The expected amount of invocations for the ::bubble() method. * @param array $options * The URL options. * * @covers ::bubble * * @dataProvider providerUrlBubbleableMetadataBubbling */ public function testUrlBubbleableMetadataBubbling($collect_bubbleable_metadata, $invocations, array $options) { $self = $this; $this->renderer->expects($this->exactly($invocations))->method('render')->willReturnCallback(function ($build) use($self) { $self->assertTrue(!empty($build['#cache'])); }); $url = new Url('test_1', [], $options); $url->setUrlGenerator($this->generator); $url->toString($collect_bubbleable_metadata); }
/** * {@inheritdoc} */ public function execute($entity = NULL) { if (empty($this->configuration['node'])) { $this->configuration['node'] = $entity; } $message = $this->token->replace($this->configuration['message'], $this->configuration); $build = ['#markup' => $message]; // @todo Fix in https://www.drupal.org/node/2577827 drupal_set_message($this->renderer->renderPlain($build)); }
/** * Generates the toolbar. * * @param Profile $profile * * @return array */ public function toolbarAction(Profile $profile) { $this->profiler->disable(); $templates = $this->templateManager->getTemplates($profile); $rendered = ''; foreach ($templates as $name => $template) { $rendered .= $template->renderBlock('toolbar', ['collector' => $profile->getcollector($name), 'token' => $profile->getToken(), 'name' => $name]); } $toolbar = ['#theme' => 'webprofiler_toolbar', '#toolbar' => $rendered, '#token' => $profile->getToken()]; return new Response($this->renderer->render($toolbar)); }
/** * Bubbles the bubbleable metadata to the current render context. * * @param \Drupal\Core\GeneratedUrl $generated_url * The generated URL whose bubbleable metadata to bubble. * @param array $options * (optional) The URL options. Defaults to none. */ protected function bubble(GeneratedUrl $generated_url, array $options = []) { // Bubbling metadata makes sense only if the code is executed inside a // render context. All code running outside controllers has no render // context by default, so URLs used there are not supposed to affect the // response cacheability. if ($this->renderer->hasRenderContext()) { $build = []; $generated_url->applyTo($build); $this->renderer->render($build); } }
/** * {@inheritdoc} */ public function execute($comment = NULL) { $build = $this->viewBuilder->view($comment); $text = $this->renderer->renderPlain($build); foreach ($this->configuration['keywords'] as $keyword) { if (strpos($text, $keyword) !== FALSE) { $comment->setPublished(FALSE); $comment->save(); break; } } }
/** * Helper function for self::listing() to build table rows. * * @param array[] $hierarchy * Keys are plugin IDs, and values are arrays of the same structure as this * parameter. The depth is unlimited. * @param integer $depth * The depth of $hierarchy's top-level items as seen from the original * hierarchy's root (this function is recursive), starting with 0. * * @return array * A render array. */ protected function buildListingLevel(array $hierarchy, $depth) { $rows = []; foreach ($hierarchy as $plugin_id => $children) { $definition = $this->paymentStatusManager->getDefinition($plugin_id); $operations_provider = $this->paymentStatusManager->getOperationsProvider($plugin_id); $indentation = ['#theme' => 'indentation', '#size' => $depth]; $rows[$plugin_id] = ['label' => ['#markup' => $this->renderer->render($indentation) . $definition['label']], 'description' => ['#markup' => $definition['description']], 'operations' => ['#type' => 'operations', '#links' => $operations_provider ? $operations_provider->getOperations($plugin_id) : []]]; $rows = array_merge($rows, $this->buildListingLevel($children, $depth + 1)); } return $rows; }
/** * @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; }
protected function setUp() { $this->viewStorage = $this->getMock('Drupal\\Core\\Entity\\EntityStorageInterface'); $this->executableFactory = $this->getMockBuilder('Drupal\\views\\ViewExecutableFactory')->disableOriginalConstructor()->getMock(); $this->renderer = $this->getMock('\\Drupal\\Core\\Render\\RendererInterface'); $this->renderer->expects($this->any())->method('render')->will($this->returnCallback(function (array &$elements) { $elements['#attached'] = []; return isset($elements['#markup']) ? $elements['#markup'] : ''; })); $this->currentPath = $this->getMockBuilder('Drupal\\Core\\Path\\CurrentPathStack')->disableOriginalConstructor()->getMock(); $this->redirectDestination = $this->getMock('\\Drupal\\Core\\Routing\\RedirectDestinationInterface'); $this->viewAjaxController = new ViewAjaxController($this->viewStorage, $this->executableFactory, $this->renderer, $this->currentPath, $this->redirectDestination); }
/** * Generates a response object after handing the un/flag request. * * Depending on the wrapper format of the request, it will either redirect * or return an ajax response. * * @param \Drupal\flag\FlagInterface $flag * The flag entity. * @param \Drupal\Core\Entity\EntityInterface $entity * The entity object. * @param \Symfony\Component\HttpFoundation\Request $request * The request. * * @return \Drupal\Core\Ajax\AjaxResponse|\Symfony\Component\HttpFoundation\RedirectResponse * The response object. */ protected function generateResponse(FlagInterface $flag, EntityInterface $entity, Request $request) { if ($request->get(MainContentViewSubscriber::WRAPPER_FORMAT) == 'drupal_ajax') { // Create a new AJAX response. $response = new AjaxResponse(); // Get the link type plugin. $link_type = $flag->getLinkTypePlugin(); // Generate the link render array and get the link CSS ID. $link = $link_type->getLink($flag, $entity); $link_id = '#' . $link['link']['#attributes']['id']; // Create a new JQuery Replace command to update the link display. $replace = new ReplaceCommand($link_id, $this->renderer->renderPlain($link)); $response->addCommand($replace); return $response; } else { // Redirect back to the entity. A passed in destination query parameter // will automatically override this. $url_info = $entity->toUrl(); return $this->redirect($url_info->getRouteName(), $url_info->getRouteParameters()); } }
/** * Wraps a controller execution in a render context. * * @param callable $controller * The controller to execute. * @param array $arguments * The arguments to pass to the controller. * * @return mixed * The return value of the controller. * * @throws \LogicException * When early rendering has occurred in a controller that returned a * Response or domain object that cares about attachments or cacheability. * * @see \Symfony\Component\HttpKernel\HttpKernel::handleRaw() */ protected function wrapControllerExecutionInRenderContext($controller, array $arguments) { $context = new RenderContext(); $response = $this->renderer->executeInRenderContext($context, function () use($controller, $arguments) { // Now call the actual controller, just like HttpKernel does. return call_user_func_array($controller, $arguments); }); // If early rendering happened, i.e. if code in the controller called // drupal_render() outside of a render context, then the bubbleable metadata // for that is stored in the current render context. if (!$context->isEmpty()) { /** @var \Drupal\Core\Render\BubbleableMetadata $early_rendering_bubbleable_metadata */ $early_rendering_bubbleable_metadata = $context->pop(); // If a render array or AjaxResponse is returned by the controller, merge // the "lost" bubbleable metadata. if (is_array($response)) { BubbleableMetadata::createFromRenderArray($response)->merge($early_rendering_bubbleable_metadata)->applyTo($response); } elseif ($response instanceof AjaxResponse) { $response->addAttachments($early_rendering_bubbleable_metadata->getAttachments()); // @todo Make AjaxResponse cacheable in // https://www.drupal.org/node/956186. Meanwhile, allow contrib // subclasses to be. if ($response instanceof CacheableResponseInterface) { $response->addCacheableDependency($early_rendering_bubbleable_metadata); } } elseif ($response instanceof AttachmentsInterface || $response instanceof CacheableResponseInterface || $response instanceof CacheableDependencyInterface) { throw new \LogicException(sprintf('The controller result claims to be providing relevant cache metadata, but leaked metadata was detected. Please ensure you are not rendering content too early. Returned object class: %s.', get_class($response))); } else { // A Response or domain object is returned that does not care about // attachments nor cacheability; for instance, a RedirectResponse. It is // safe to discard any early rendering metadata. } } return $response; }
/** * {@inheritdoc} */ public function view(EntityInterface $entity, $view_mode = 'full', $langcode = NULL) { if ($entity->status()) { $message = $this->contactMessageStorage->create([ 'contact_form' => $entity->id(), ]); $form = $this->entityFormBuilder->getForm($message); $form['#title'] = $entity->label(); $form['#cache']['contexts'][] = 'user.permissions'; $this->renderer->addCacheableDependency($form, $this->config); } else { // Form disabled, display a custom message using a template. $form['disabled_form_error'] = array( '#theme' => 'contact_storage_disabled_form', '#contact_form' => $entity, '#redirect_uri' => $entity->getThirdPartySetting('contact_storage', 'redirect_uri', ''), '#disabled_form_message' => $entity->getThirdPartySetting('contact_storage', 'disabled_form_message', t('This contact form has been disabled.')), ); } // Add required cacheability metadata from the contact form entity, so that // changing it invalidates the cache. $this->renderer->addCacheableDependency($form, $entity); return $form; }
/** * Wrapper around render() for twig printed output. * * If an object is passed which does not implement __toString(), * RenderableInterface or toString() then an exception is thrown; * Other objects are casted to string. However in the case that the * object is an instance of a Twig_Markup object it is returned directly * to support auto escaping. * * If an array is passed it is rendered via render() and scalar values are * returned directly. * * @param mixed $arg * String, Object or Render Array. * * @throws \Exception * When $arg is passed as an object which does not implement __toString(), * RenderableInterface or toString(). * * @return mixed * The rendered output or an Twig_Markup object. * * @see render * @see TwigNodeVisitor */ public function renderVar($arg) { // Check for a numeric zero int or float. if ($arg === 0 || $arg === 0.0) { return 0; } // Return early for NULL and empty arrays. if ($arg == NULL) { return NULL; } // Optimize for scalars as it is likely they come from the escape filter. if (is_scalar($arg)) { return $arg; } if (is_object($arg)) { $this->bubbleArgMetadata($arg); if ($arg instanceof RenderableInterface) { $arg = $arg->toRenderable(); } elseif (method_exists($arg, '__toString')) { return (string) $arg; } elseif (method_exists($arg, 'toString')) { return $arg->toString(); } else { throw new \Exception('Object of type ' . get_class($arg) . ' cannot be printed.'); } } // This is a render array, with special simple cases already handled. // Early return if this element was pre-rendered (no need to re-render). if (isset($arg['#printed']) && $arg['#printed'] == TRUE && isset($arg['#markup']) && strlen($arg['#markup']) > 0) { return $arg['#markup']; } $arg['#printed'] = FALSE; return $this->renderer->render($arg); }
/** * {@inheritdoc} */ protected function getRevisionDescription(ContentEntityInterface $revision, $is_default = FALSE) { /** @var \Drupal\Core\Entity\ContentEntityInterface|\Drupal\user\EntityOwnerInterface|\Drupal\Core\Entity\RevisionLogInterface $revision */ if ($revision instanceof RevisionLogInterface) { // Use revision link to link to revisions that are not active. $date = $this->dateFormatter->format($revision->getRevisionCreationTime(), 'short'); $link = $revision->toLink($date, 'revision'); // @todo: Simplify this when https://www.drupal.org/node/2334319 lands. $username = ['#theme' => 'username', '#account' => $revision->getRevisionUser()]; $username = $this->renderer->render($username); } else { $link = $revision->toLink($revision->label(), 'revision'); $username = ''; } $markup = ''; if ($revision instanceof RevisionLogInterface) { $markup = $revision->getRevisionLogMessage(); } if ($username) { $template = '{% trans %}{{ date }} by {{ username }}{% endtrans %}{% if message %}<p class="revision-log">{{ message }}</p>{% endif %}'; } else { $template = '{% trans %} {{ date }} {% endtrans %}{% if message %}<p class="revision-log">{{ message }}</p>{% endif %}'; } $column = ['data' => ['#type' => 'inline_template', '#template' => $template, '#context' => ['date' => $link->toString(), 'username' => $username, 'message' => ['#markup' => $markup, '#allowed_tags' => Xss::getHtmlTagList()]]]]; return $column; }
/** * 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); }
/** * Pre-render callback to build the page title. * * @param array $page * A page render array. * * @return array * The changed page render array. */ public function buildTitle(array $page) { $entity_type = $page['#entity_type']; $entity = $page['#' . $entity_type]; // If the entity's label is rendered using a field formatter, set the // rendered title field formatter as the page title instead of the default // plain text title. This allows attributes set on the field to propagate // correctly (e.g. RDFa, in-place editing). if ($entity instanceof FieldableEntityInterface) { $label_field = $entity->getEntityType()->getKey('label'); if (isset($page[$label_field])) { $page['#title'] = $this->renderer->render($page[$label_field]); } } return $page; }
/** * {@inheritdoc} */ public function render() { $build = array(); $build['#markup'] = $this->renderer->executeInRenderContext(new RenderContext(), function () { return $this->view->style_plugin->render(); }); $this->view->element['#content_type'] = $this->getMimeType(); $this->view->element['#cache_properties'][] = '#content_type'; // Encode and wrap the output in a pre tag if this is for a live preview. if (!empty($this->view->live_preview)) { $build['#prefix'] = '<pre>'; $build['#plain_text'] = $build['#markup']; $build['#suffix'] = '</pre>'; unset($build['#markup']); } elseif ($this->view->getRequest()->getFormat($this->view->element['#content_type']) !== 'html') { // This display plugin is primarily for returning non-HTML formats. // However, we still invoke the renderer to collect cacheability metadata. // Because the renderer is designed for HTML rendering, it filters // #markup for XSS unless it is already known to be safe, but that filter // only works for HTML. Therefore, we mark the contents as safe to bypass // the filter. So long as we are returning this in a non-HTML response // (checked above), this is safe, because an XSS attack only works when // executed by an HTML agent. // @todo Decide how to support non-HTML in the render API in // https://www.drupal.org/node/2501313. $build['#markup'] = ViewsRenderPipelineMarkup::create($build['#markup']); } parent::applyDisplayCachablityMetadata($build); return $build; }
/** * Wraps a controller execution in a render context. * * @param callable $controller * The controller to execute. * @param array $arguments * The arguments to pass to the controller. * * @return mixed * The return value of the controller. * * @throws \LogicException * When early rendering has occurred in a controller that returned a * Response or domain object that cares about attachments or cacheability. * * @see \Symfony\Component\HttpKernel\HttpKernel::handleRaw() */ protected function wrapControllerExecutionInRenderContext($controller, array $arguments) { $context = new RenderContext(); $response = $this->renderer->executeInRenderContext($context, function () use($controller, $arguments) { // Now call the actual controller, just like HttpKernel does. return call_user_func_array($controller, $arguments); }); // If early rendering happened, i.e. if code in the controller called // drupal_render() outside of a render context, then the bubbleable metadata // for that is stored in the current render context. if (!$context->isEmpty()) { // If a render array is returned by the controller, merge the "lost" // bubbleable metadata. if (is_array($response)) { $early_rendering_bubbleable_metadata = $context->pop(); BubbleableMetadata::createFromRenderArray($response)->merge($early_rendering_bubbleable_metadata)->applyTo($response); } elseif ($response instanceof AttachmentsInterface || $response instanceof CacheableResponseInterface || $response instanceof CacheableDependencyInterface) { throw new \LogicException(sprintf('The controller result claims to be providing relevant cache metadata, but leaked metadata was detected. Please ensure you are not rendering content too early. Returned object class: %s.', get_class($response))); } else { // A Response or domain object is returned that does not care about // attachments nor cacheability. E.g. a RedirectResponse. It is safe to // discard any early rendering metadata. } } 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) { // 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; }
/** * Gets a given layout with empty regions and relevant metadata. * * @param \Drupal\page_manager\PageVariantInterface $page_variant * The page variant entity. * @param string $layout_id * The machine name of the requested layout. * * @return \Symfony\Component\HttpFoundation\JsonResponse */ public function getLayout(PageVariantInterface $page_variant, $layout_id) { /** @var \Drupal\panels\Plugin\DisplayVariant\PanelsDisplayVariant $variant_plugin */ $variant_plugin = $page_variant->getVariantPlugin(); // Build the requested layout. $configuration = $variant_plugin->getConfiguration(); $configuration['layout'] = $layout_id; $variant_plugin->setConfiguration($configuration); // Inherit our PageVariant's contexts before rendering. $variant_plugin->setContexts($page_variant->getContexts()); $regions = $variant_plugin->getRegionNames(); $region_data = []; $region_content = []; // Compile region content and metadata. foreach ($regions as $id => $label) { // Wrap the region with a class/data attribute that our app can use. $region_name = Html::getClass("block-region-{$id}"); $region_content[$id] = ['#prefix' => '<div class="' . $region_name . '" data-region-name="' . $id . '">', '#suffix' => '</div>']; // Format region metadata. $region_data[] = ['name' => $id, 'label' => $label]; } $build = $variant_plugin->getLayout()->build($region_content); // Get the current layout. $current_layout = $variant_plugin->getLayout()->getPluginId(); // Get a list of all available layouts. $layouts = $this->layoutPluginManager->getLayoutOptions(); $data = ['id' => $layout_id, 'label' => $layouts[$layout_id], 'current' => $current_layout == $layout_id, 'html' => $this->renderer->render($build), 'regions' => $region_data]; // Update temp store. $this->savePageVariant($page_variant); // Return a structured JSON response for our Backbone App. return new JsonResponse($data); }
/** * Save data to the cache. * * A plugin should override this to provide specialized caching behavior. */ public function cacheSet($type) { switch ($type) { case 'query': // Not supported currently, but this is certainly where we'd put it. break; case 'results': $data = array('result' => $this->prepareViewResult($this->view->result), 'total_rows' => isset($this->view->total_rows) ? $this->view->total_rows : 0, 'current_page' => $this->view->getCurrentPage()); \Drupal::cache($this->resultsBin)->set($this->generateResultsKey(), $data, $this->cacheSetExpire($type), $this->getCacheTags()); break; case 'output': // Make a copy of the output so it is not modified. If we render the // display output directly an empty string will be returned when the // view is actually rendered. If we try to set '#printed' to FALSE there // are problems with asset bubbling. $output = $this->view->display_handler->output; $this->renderer->render($output); // Also assign the cacheable render array back to the display handler so // that is used to render the view for this request and rendering does // not happen twice. $this->storage = $this->view->display_handler->output = $this->renderer->getCacheableRenderArray($output); \Drupal::cache($this->outputBin)->set($this->generateOutputKey(), $this->storage, $this->cacheSetExpire($type), Cache::mergeTags($this->storage['#cache']['tags'], ['rendered'])); break; } }
/** * Generates themed output early in a page request. * * @see \Drupal\system\Tests\Theme\ThemeEarlyInitializationTest::testRequestListener() */ public function onRequest(GetResponseEvent $event) { if ($this->currentRouteMatch->getRouteName() === 'theme_test.request_listener') { // First, force the theme registry to be rebuilt on this page request. // This allows us to test a full initialization of the theme system in // the code below. drupal_theme_rebuild(); // Next, initialize the theme system by storing themed text in a global // variable. We will use this later in // theme_test_request_listener_page_callback() to test that even when the // theme system is initialized this early, it is still capable of // returning output and theming the page as a whole. $more_link = array('#type' => 'more_link', '#url' => Url::fromRoute('user.page'), '#attributes' => array('title' => 'Themed output generated in a KernelEvents::REQUEST listener')); $GLOBALS['theme_test_output'] = $this->renderer->renderPlain($more_link); } }