Ejemplo n.º 1
0
 /**
  * {@inheritdoc}
  */
 public function processOutbound($route_name, Route $route, array &$parameters, BubbleableMetadata $bubbleable_metadata = NULL)
 {
     if ($route_name === '<current>') {
         if ($current_route = $this->routeMatch->getRouteObject()) {
             $requirements = $current_route->getRequirements();
             // Setting _method and _schema is deprecated since 2.7. Using
             // setMethods() and setSchemes() are now the recommended ways.
             unset($requirements['_method']);
             unset($requirements['_schema']);
             $route->setRequirements($requirements);
             $route->setPath($current_route->getPath());
             $route->setSchemes($current_route->getSchemes());
             $route->setMethods($current_route->getMethods());
             $route->setOptions($current_route->getOptions());
             $route->setDefaults($current_route->getDefaults());
             $parameters = array_merge($parameters, $this->routeMatch->getRawParameters()->all());
             if ($bubbleable_metadata) {
                 $bubbleable_metadata->addCacheContexts(['route']);
             }
         } else {
             // If we have no current route match available, point to the frontpage.
             $route->setPath('/');
         }
     }
 }
 /**
  * Provides test data for createFromRenderArray().
  *
  * @return array
  */
 public function providerTestCreateFromRenderArray()
 {
     $data = [];
     $empty_metadata = new BubbleableMetadata();
     $nonempty_metadata = new BubbleableMetadata();
     $nonempty_metadata->setCacheContexts(['qux'])->setCacheTags(['foo:bar'])->setAssets(['settings' => ['foo' => 'bar']]);
     $empty_render_array = [];
     $nonempty_render_array = ['#cache' => ['contexts' => ['qux'], 'tags' => ['foo:bar'], 'max-age' => Cache::PERMANENT], '#attached' => ['settings' => ['foo' => 'bar']], '#post_render_cache' => []];
     $data[] = [$empty_render_array, $empty_metadata];
     $data[] = [$nonempty_render_array, $nonempty_metadata];
     return $data;
 }
 public function processOutbound($path, &$options = array(), Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL)
 {
     if (array_key_exists('purl_context', $options) && $options['purl_context'] == false) {
         if (count($this->matchedModifiers->getMatched()) && $bubbleable_metadata) {
             $cacheContexts = $bubbleable_metadata->getCacheContexts();
             $cacheContexts[] = 'purl';
             $bubbleable_metadata->setCacheContexts($cacheContexts);
         }
         return $this->contextHelper->processOutbound($this->matchedModifiers->createContexts(Context::EXIT_CONTEXT), $path, $options, $request, $bubbleable_metadata);
     }
     return $this->contextHelper->processOutbound($this->matchedModifiers->createContexts(), $path, $options, $request, $bubbleable_metadata);
 }
Ejemplo n.º 4
0
 /**
  * Implements Drupal\Core\PathProcessor\OutboundPathProcessorInterface::processOutbound().
  */
 public function processOutbound($path, &$options = array(), Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL)
 {
     // Rewrite user/uid to user/username.
     if (preg_match('!^/user/([0-9]+)(/.*)?!', $path, $matches)) {
         if ($account = User::load($matches[1])) {
             $matches += array(2 => '');
             $path = '/user/' . $account->getUsername() . $matches[2];
             if ($bubbleable_metadata) {
                 $bubbleable_metadata->addCacheTags($account->getCacheTags());
             }
         }
     }
     // Rewrite forum/ to community/.
     return preg_replace('@^/forum(.*)@', '/community$1', $path);
 }
Ejemplo n.º 5
0
  /**
   * Adds linkit custom autocomplete functionality to elements.
   *
   * Instead of using the core autocomplete, we use our own.
   *
   * {@inheritdoc}
   *
   * @see \Drupal\Core\Render\Element\FormElement::processAutocomplete
   */
  public static function processLinkitAutocomplete(&$element, FormStateInterface $form_state, &$complete_form) {
    $url = NULL;
    $access = FALSE;

    if (!empty($element['#autocomplete_route_name'])) {
      $parameters = isset($element['#autocomplete_route_parameters']) ? $element['#autocomplete_route_parameters'] : array();
      $url = Url::fromRoute($element['#autocomplete_route_name'], $parameters)->toString(TRUE);
      /** @var \Drupal\Core\Access\AccessManagerInterface $access_manager */
      $access_manager = \Drupal::service('access_manager');
      $access = $access_manager->checkNamedRoute($element['#autocomplete_route_name'], $parameters, \Drupal::currentUser(), TRUE);
    }

    if ($access) {
      $metadata = BubbleableMetadata::createFromRenderArray($element);
      if ($access->isAllowed()) {
        $element['#attributes']['class'][] = 'form-linkit-autocomplete';
        $metadata->addAttachments(['library' => ['linkit/linkit.autocomplete']]);
        // Provide a data attribute for the JavaScript behavior to bind to.
        $element['#attributes']['data-autocomplete-path'] = $url->getGeneratedUrl();
        $metadata = $metadata->merge($url);
      }
      $metadata
        ->merge(BubbleableMetadata::createFromObject($access))
        ->applyTo($element);
    }

    return $element;
  }
Ejemplo n.º 6
0
 /**
  * Tests core token replacements generated from a view.
  */
 function testTokenReplacement()
 {
     $token_handler = \Drupal::token();
     $view = Views::getView('test_tokens');
     $view->setDisplay('page_1');
     $this->executeView($view);
     $expected = array('[view:label]' => 'Test tokens', '[view:description]' => 'Test view to token replacement tests.', '[view:id]' => 'test_tokens', '[view:title]' => 'Test token page', '[view:url]' => $view->getUrl(NULL, 'page_1')->setAbsolute(TRUE)->toString(), '[view:total-rows]' => (string) $view->total_rows, '[view:base-table]' => 'views_test_data', '[view:base-field]' => 'id', '[view:items-per-page]' => '10', '[view:current-page]' => '1', '[view:page-count]' => '1');
     $base_bubbleable_metadata = BubbleableMetadata::createFromObject($view->storage);
     $metadata_tests = [];
     $metadata_tests['[view:label]'] = $base_bubbleable_metadata;
     $metadata_tests['[view:description]'] = $base_bubbleable_metadata;
     $metadata_tests['[view:id]'] = $base_bubbleable_metadata;
     $metadata_tests['[view:title]'] = $base_bubbleable_metadata;
     $metadata_tests['[view:url]'] = $base_bubbleable_metadata;
     $metadata_tests['[view:total-rows]'] = $base_bubbleable_metadata;
     $metadata_tests['[view:base-table]'] = $base_bubbleable_metadata;
     $metadata_tests['[view:base-field]'] = $base_bubbleable_metadata;
     $metadata_tests['[view:items-per-page]'] = $base_bubbleable_metadata;
     $metadata_tests['[view:current-page]'] = $base_bubbleable_metadata;
     $metadata_tests['[view:page-count]'] = $base_bubbleable_metadata;
     foreach ($expected as $token => $expected_output) {
         $bubbleable_metadata = new BubbleableMetadata();
         $output = $token_handler->replace($token, array('view' => $view), [], $bubbleable_metadata);
         $this->assertIdentical($output, $expected_output, format_string('Token %token replaced correctly.', array('%token' => $token)));
         $this->assertEqual($bubbleable_metadata, $metadata_tests[$token]);
     }
 }
Ejemplo n.º 7
0
 /**
  * Tests the generation of all system site information tokens.
  */
 public function testSystemSiteTokenReplacement()
 {
     $url_options = array('absolute' => TRUE, 'language' => $this->interfaceLanguage);
     $slogan = '<blink>Slogan</blink>';
     $safe_slogan = Xss::filterAdmin($slogan);
     // Set a few site variables.
     $config = $this->config('system.site');
     $config->set('name', '<strong>Drupal<strong>')->set('slogan', $slogan)->set('mail', '*****@*****.**')->save();
     // Generate and test tokens.
     $tests = array();
     $tests['[site:name]'] = Html::escape($config->get('name'));
     $tests['[site:slogan]'] = $safe_slogan;
     $tests['[site:mail]'] = $config->get('mail');
     $tests['[site:url]'] = \Drupal::url('<front>', [], $url_options);
     $tests['[site:url-brief]'] = preg_replace(array('!^https?://!', '!/$!'), '', \Drupal::url('<front>', [], $url_options));
     $tests['[site:login-url]'] = \Drupal::url('user.page', [], $url_options);
     $base_bubbleable_metadata = new BubbleableMetadata();
     $metadata_tests = [];
     $metadata_tests['[site:name]'] = BubbleableMetadata::createFromObject(\Drupal::config('system.site'));
     $metadata_tests['[site:slogan]'] = BubbleableMetadata::createFromObject(\Drupal::config('system.site'));
     $metadata_tests['[site:mail]'] = BubbleableMetadata::createFromObject(\Drupal::config('system.site'));
     $bubbleable_metadata = clone $base_bubbleable_metadata;
     $metadata_tests['[site:url]'] = $bubbleable_metadata->addCacheContexts(['url.site']);
     $metadata_tests['[site:url-brief]'] = $bubbleable_metadata;
     $metadata_tests['[site:login-url]'] = $bubbleable_metadata;
     // Test to make sure that we generated something for each token.
     $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
     foreach ($tests as $input => $expected) {
         $bubbleable_metadata = new BubbleableMetadata();
         $output = $this->tokenService->replace($input, array(), array('langcode' => $this->interfaceLanguage->getId()), $bubbleable_metadata);
         $this->assertEqual($output, $expected, new FormattableMarkup('System site information token %token replaced.', ['%token' => $input]));
         $this->assertEqual($bubbleable_metadata, $metadata_tests[$input]);
     }
 }
Ejemplo n.º 8
0
 /**
  * {@inheritdoc}
  */
 public function filter(DataDefinitionInterface $definition, $value, array $arguments, BubbleableMetadata $bubbleable_metadata = NULL)
 {
     if ($definition->getDataType() != 'timestamp') {
         // Convert the date to an timestamp.
         $value = $this->getTypedDataManager()->create($definition, $value)->getDateTime()->getTimestamp();
     }
     $arguments += [0 => 'medium', 1 => '', 2 => NULL, 3 => NULL];
     if ($arguments[0] != 'custom' && $bubbleable_metadata) {
         $config = $this->dateFormatStorage->load($arguments[0]);
         if (!$config) {
             throw new \InvalidArgumentException("Unknown date format {$arguments['0']} given.");
         }
         $bubbleable_metadata->addCacheableDependency($config);
     }
     return $this->dateFormatter->format($value, $arguments[0], $arguments[1], $arguments[2], $arguments[3]);
 }
 /**
  * {@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().'];
 }
Ejemplo n.º 11
0
 /**
  * {@inheritdoc}
  */
 public function processOutbound($route_name, Route $route, array &$parameters, BubbleableMetadata $bubbleable_metadata = NULL)
 {
     if ($route->hasRequirement('_csrf_token')) {
         $path = ltrim($route->getPath(), '/');
         // Replace the path parameters with values from the parameters array.
         foreach ($parameters as $param => $value) {
             $path = str_replace("{{$param}}", $value, $path);
         }
         // Adding this to the parameters means it will get merged into the query
         // string when the route is compiled.
         if (!$bubbleable_metadata) {
             $parameters['token'] = $this->csrfToken->get($path);
         } else {
             // Generate a placeholder and a render array to replace it.
             $placeholder = hash('sha1', $path);
             $placeholder_render_array = ['#lazy_builder' => ['route_processor_csrf:renderPlaceholderCsrfToken', [$path]]];
             // Instead of setting an actual CSRF token as the query string, we set
             // the placeholder, which will be replaced at the very last moment. This
             // ensures links with CSRF tokens don't break cacheability.
             $parameters['token'] = $placeholder;
             $bubbleable_metadata->addAttachments(['placeholders' => [$placeholder => $placeholder_render_array]]);
         }
     }
 }
Ejemplo n.º 12
0
 /**
  * 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;
 }
Ejemplo n.º 13
0
/**
 * Provide replacement values for placeholder tokens.
 *
 * This hook is invoked when someone calls
 * \Drupal\Core\Utility\Token::replace(). That function first scans the text for
 * [type:token] patterns, and splits the needed tokens into groups by type.
 * Then hook_tokens() is invoked on each token-type group, allowing your module
 * to respond by providing replacement text for any of the tokens in the group
 * that your module knows how to process.
 *
 * A module implementing this hook should also implement hook_token_info() in
 * order to list its available tokens on editing screens.
 *
 * @param $type
 *   The machine-readable name of the type (group) of token being replaced, such
 *   as 'node', 'user', or another type defined by a hook_token_info()
 *   implementation.
 * @param $tokens
 *   An array of tokens to be replaced. The keys are the machine-readable token
 *   names, and the values are the raw [type:token] strings that appeared in the
 *   original text.
 * @param array $data
 *   An associative array of data objects to be used when generating replacement
 *   values, as supplied in the $data parameter to
 *  \Drupal\Core\Utility\Token::replace().
 * @param array $options
 *   An associative array of options for token replacement; see
 *   \Drupal\Core\Utility\Token::replace() for possible values.
 * @param \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata
 *   The bubbleable metadata. Prior to invoking this hook,
 *   \Drupal\Core\Utility\Token::generate() collects metadata for all of the
 *   data objects in $data. For any data sources not in $data, but that are
 *   used by the token replacement logic, such as global configuration (e.g.,
 *   'system.site') and related objects (e.g., $node->getOwner()),
 *   implementations of this hook must add the corresponding metadata.
 *   For example:
 *   @code
 *     $bubbleable_metadata->addCacheableDependency(\Drupal::config('system.site'));
 *     $bubbleable_metadata->addCacheableDependency($node->getOwner());
 *   @endcode
 *
 *   Additionally, implementations of this hook, must forward
 *   $bubbleable_metadata to the chained tokens that they invoke.
 *   For example:
 *   @code
 *     if ($created_tokens = $token_service->findWithPrefix($tokens, 'created')) {
 *       $replacements = $token_service->generate('date', $created_tokens, array('date' => $node->getCreatedTime()), $options, $bubbleable_metadata);
 *     }
 *   @endcode
 *
 * @return array
 *   An associative array of replacement values, keyed by the raw [type:token]
 *   strings from the original text.
 *
 * @see hook_token_info()
 * @see hook_tokens_alter()
 */
function hook_tokens($type, $tokens, array $data, array $options, \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata)
{
    $token_service = \Drupal::token();
    $url_options = array('absolute' => TRUE);
    if (isset($options['langcode'])) {
        $url_options['language'] = \Drupal::languageManager()->getLanguage($options['langcode']);
        $langcode = $options['langcode'];
    } else {
        $langcode = NULL;
    }
    $sanitize = !empty($options['sanitize']);
    $replacements = array();
    if ($type == 'node' && !empty($data['node'])) {
        /** @var \Drupal\node\NodeInterface $node */
        $node = $data['node'];
        foreach ($tokens as $name => $original) {
            switch ($name) {
                // Simple key values on the node.
                case 'nid':
                    $replacements[$original] = $node->nid;
                    break;
                case 'title':
                    $replacements[$original] = $sanitize ? Html::escape($node->getTitle()) : $node->getTitle();
                    break;
                case 'edit-url':
                    $replacements[$original] = $node->url('edit-form', $url_options);
                    break;
                    // Default values for the chained tokens handled below.
                // Default values for the chained tokens handled below.
                case 'author':
                    $account = $node->getOwner() ? $node->getOwner() : User::load(0);
                    $replacements[$original] = $sanitize ? Html::escape($account->label()) : $account->label();
                    $bubbleable_metadata->addCacheableDependency($account);
                    break;
                case 'created':
                    $replacements[$original] = format_date($node->getCreatedTime(), 'medium', '', NULL, $langcode);
                    break;
            }
        }
        if ($author_tokens = $token_service->findWithPrefix($tokens, 'author')) {
            $replacements = $token_service->generate('user', $author_tokens, array('user' => $node->getOwner()), $options, $bubbleable_metadata);
        }
        if ($created_tokens = $token_service->findWithPrefix($tokens, 'created')) {
            $replacements = $token_service->generate('date', $created_tokens, array('date' => $node->getCreatedTime()), $options, $bubbleable_metadata);
        }
    }
    return $replacements;
}
Ejemplo n.º 14
0
 /**
  * Pre-render callback: Renders a link into #markup.
  *
  * Doing so during pre_render gives modules a chance to alter the link parts.
  *
  * @param array $element
  *   A structured array whose keys form the arguments to
  *   \Drupal\Core\Utility\LinkGeneratorInterface::generate():
  *   - #title: The link text.
  *   - #url: The URL info either pointing to a route or a non routed path.
  *   - #options: (optional) An array of options to pass to the link generator.
  *
  * @return array
  *   The passed-in element containing a rendered link in '#markup'.
  */
 public static function preRenderLink($element)
 {
     // By default, link options to pass to the link generator are normally set
     // in #options.
     $element += array('#options' => array());
     // However, within the scope of renderable elements, #attributes is a valid
     // way to specify attributes, too. Take them into account, but do not override
     // attributes from #options.
     if (isset($element['#attributes'])) {
         $element['#options'] += array('attributes' => array());
         $element['#options']['attributes'] += $element['#attributes'];
     }
     // This #pre_render callback can be invoked from inside or outside of a Form
     // API context, and depending on that, a HTML ID may be already set in
     // different locations. #options should have precedence over Form API's #id.
     // #attributes have been taken over into #options above already.
     if (isset($element['#options']['attributes']['id'])) {
         $element['#id'] = $element['#options']['attributes']['id'];
     } elseif (isset($element['#id'])) {
         $element['#options']['attributes']['id'] = $element['#id'];
     }
     // Conditionally invoke self::preRenderAjaxForm(), if #ajax is set.
     if (isset($element['#ajax']) && !isset($element['#ajax_processed'])) {
         // If no HTML ID was found above, automatically create one.
         if (!isset($element['#id'])) {
             $element['#id'] = $element['#options']['attributes']['id'] = HtmlUtility::getUniqueId('ajax-link');
         }
         $element = static::preRenderAjaxForm($element);
     }
     if (!empty($element['#url']) && $element['#url'] instanceof CoreUrl) {
         $options = NestedArray::mergeDeep($element['#url']->getOptions(), $element['#options']);
         /** @var \Drupal\Core\Utility\LinkGenerator $link_generator */
         $link_generator = \Drupal::service('link_generator');
         $generated_link = $link_generator->generate($element['#title'], $element['#url']->setOptions($options));
         $element['#markup'] = $generated_link->getGeneratedLink();
         $generated_link->merge(BubbleableMetadata::createFromRenderArray($element))->applyTo($element);
     }
     return $element;
 }
Ejemplo n.º 15
0
 /**
  * {@inheritdoc}
  */
 public function processOutbound($path, &$options = array(), Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL)
 {
     if ($request) {
         // The following values are not supposed to change during a single page
         // request processing.
         if (!isset($this->queryRewrite)) {
             if ($this->currentUser->isAnonymous()) {
                 $languages = $this->languageManager->getLanguages();
                 $config = $this->config->get('language.negotiation')->get('session');
                 $this->queryParam = $config['parameter'];
                 $this->queryValue = $request->query->has($this->queryParam) ? $request->query->get($this->queryParam) : NULL;
                 $this->queryRewrite = isset($languages[$this->queryValue]);
             } else {
                 $this->queryRewrite = FALSE;
             }
         }
         // If the user is anonymous, the user language negotiation method is
         // enabled, and the corresponding option has been set, we must preserve
         // any explicit user language preference even with cookies disabled.
         if ($this->queryRewrite) {
             if (isset($options['query']) && is_string($options['query'])) {
                 $query = array();
                 parse_str($options['query'], $query);
                 $options['query'] = $query;
             }
             if (!isset($options['query'][$this->queryParam])) {
                 $options['query'][$this->queryParam] = $this->queryValue;
             }
             if ($bubbleable_metadata) {
                 // Cached URLs that have been processed by this outbound path
                 // processor must be:
                 $bubbleable_metadata->addCacheTags($this->config->get('language.negotiation')->getCacheTags())->addCacheContexts(['url.query_args:' . $this->queryParam]);
             }
         }
     }
     return $path;
 }
Ejemplo n.º 16
0
 /**
  * Wrapper for handling AJAX forms.
  *
  * Wrapper around \Drupal\Core\Form\FormBuilderInterface::buildForm() to
  * handle some AJAX stuff automatically.
  * This makes some assumptions about the client.
  *
  * @param \Drupal\Core\Form\FormInterface|string $form_class
  *   The value must be one of the following:
  *   - The name of a class that implements \Drupal\Core\Form\FormInterface.
  *   - An instance of a class that implements \Drupal\Core\Form\FormInterface.
  * @param \Drupal\Core\Form\FormStateInterface $form_state
  *   The current state of the form.
  *
  * @return \Drupal\Core\Ajax\AjaxResponse|string|array
  *   Returns one of three possible values:
  *   - A \Drupal\Core\Ajax\AjaxResponse object.
  *   - The rendered form, as a string.
  *   - A render array with the title in #title and the rendered form in the
  *   #markup array.
  */
 protected function ajaxFormWrapper($form_class, FormStateInterface &$form_state)
 {
     /** @var \Drupal\Core\Render\RendererInterface $renderer */
     $renderer = \Drupal::service('renderer');
     // This won't override settings already in.
     if (!$form_state->has('rerender')) {
         $form_state->set('rerender', FALSE);
     }
     $ajax = $form_state->get('ajax');
     // Do not overwrite if the redirect has been disabled.
     if (!$form_state->isRedirectDisabled()) {
         $form_state->disableRedirect($ajax);
     }
     $form_state->disableCache();
     // Builds the form in a render context in order to ensure that cacheable
     // metadata is bubbled up.
     $render_context = new RenderContext();
     $callable = function () use($form_class, &$form_state) {
         return \Drupal::formBuilder()->buildForm($form_class, $form_state);
     };
     $form = $renderer->executeInRenderContext($render_context, $callable);
     if (!$render_context->isEmpty()) {
         BubbleableMetadata::createFromRenderArray($form)->merge($render_context->pop())->applyTo($form);
     }
     $output = $renderer->renderRoot($form);
     drupal_process_attached($form);
     // These forms have the title built in, so set the title here:
     $title = $form_state->get('title') ?: '';
     if ($ajax && (!$form_state->isExecuted() || $form_state->get('rerender'))) {
         // If the form didn't execute and we're using ajax, build up an
         // Ajax command list to execute.
         $response = new AjaxResponse();
         // Attach the library necessary for using the OpenModalDialogCommand and
         // set the attachments for this Ajax response.
         $form['#attached']['library'][] = 'core/drupal.dialog.ajax';
         $response->setAttachments($form['#attached']);
         $display = '';
         $status_messages = array('#type' => 'status_messages');
         if ($messages = $renderer->renderRoot($status_messages)) {
             $display = '<div class="views-messages">' . $messages . '</div>';
         }
         $display .= $output;
         $options = array('dialogClass' => 'views-ui-dialog', 'width' => '75%');
         $response->addCommand(new OpenModalDialogCommand($title, $display, $options));
         if ($section = $form_state->get('#section')) {
             $response->addCommand(new Ajax\HighlightCommand('.' . Html::cleanCssIdentifier($section)));
         }
         return $response;
     }
     return $title ? ['#title' => $title, '#markup' => $output] : $output;
 }
 /**
  * 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;
 }
Ejemplo n.º 18
0
 /**
  * Bubbles Twig template argument's cacheability & attachment metadata.
  *
  * For example: a generated link or generated URL object is passed as a Twig
  * template argument, and its bubbleable metadata must be bubbled.
  *
  * @see \Drupal\Core\GeneratedLink
  * @see \Drupal\Core\GeneratedUrl
  *
  * @param mixed $arg
  *   A Twig template argument that is about to be printed.
  *
  * @see \Drupal\Core\Theme\ThemeManager::render()
  * @see \Drupal\Core\Render\RendererInterface::render()
  */
 protected function bubbleArgMetadata($arg)
 {
     // If it's a renderable, then it'll be up to the generated render array it
     // returns to contain the necessary cacheability & attachment metadata. If
     // it doesn't implement CacheableDependencyInterface or AttachmentsInterface
     // then there is nothing to do here.
     if ($arg instanceof RenderableInterface || !($arg instanceof CacheableDependencyInterface || $arg instanceof AttachmentsInterface)) {
         return;
     }
     $arg_bubbleable = [];
     BubbleableMetadata::createFromObject($arg)->applyTo($arg_bubbleable);
     $this->renderer->render($arg_bubbleable);
 }
Ejemplo n.º 19
0
 /**
  * Loads and renders a view via AJAX.
  *
  * @param \Symfony\Component\HttpFoundation\Request $request
  *   The current request object.
  *
  * @return \Drupal\views\Ajax\ViewAjaxResponse
  *   The view response as ajax response.
  *
  * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
  *   Thrown when the view was not found.
  */
 public function ajaxView(Request $request)
 {
     $name = $request->request->get('view_name');
     $display_id = $request->request->get('view_display_id');
     if (isset($name) && isset($display_id)) {
         $args = $request->request->get('view_args');
         $args = isset($args) && $args !== '' ? explode('/', $args) : array();
         // Arguments can be empty, make sure they are passed on as NULL so that
         // argument validation is not triggered.
         $args = array_map(function ($arg) {
             return $arg == '' ? NULL : $arg;
         }, $args);
         $path = $request->request->get('view_path');
         $dom_id = $request->request->get('view_dom_id');
         $dom_id = isset($dom_id) ? preg_replace('/[^a-zA-Z0-9_-]+/', '-', $dom_id) : NULL;
         $pager_element = $request->request->get('pager_element');
         $pager_element = isset($pager_element) ? intval($pager_element) : NULL;
         $response = new ViewAjaxResponse();
         // Remove all of this stuff from the query of the request so it doesn't
         // end up in pagers and tablesort URLs.
         foreach (array('view_name', 'view_display_id', 'view_args', 'view_path', 'view_dom_id', 'pager_element', 'view_base_path', AjaxResponseSubscriber::AJAX_REQUEST_PARAMETER) as $key) {
             $request->query->remove($key);
             $request->request->remove($key);
         }
         // Load the view.
         if (!($entity = $this->storage->load($name))) {
             throw new NotFoundHttpException();
         }
         $view = $this->executableFactory->get($entity);
         if ($view && $view->access($display_id)) {
             $response->setView($view);
             // Fix the current path for paging.
             if (!empty($path)) {
                 $this->currentPath->setPath('/' . $path, $request);
             }
             // Add all POST data, because AJAX is always a post and many things,
             // such as tablesorts, exposed filters and paging assume GET.
             $request_all = $request->request->all();
             $query_all = $request->query->all();
             $request->query->replace($request_all + $query_all);
             // Overwrite the destination.
             // @see the redirect.destination service.
             $origin_destination = $path;
             // Remove some special parameters you never want to have part of the
             // destination query.
             $used_query_parameters = $request->query->all();
             // @todo Remove this parsing once these are removed from the request in
             //   https://www.drupal.org/node/2504709.
             unset($used_query_parameters[FormBuilderInterface::AJAX_FORM_REQUEST], $used_query_parameters[MainContentViewSubscriber::WRAPPER_FORMAT], $used_query_parameters['ajax_page_state']);
             $query = UrlHelper::buildQuery($used_query_parameters);
             if ($query != '') {
                 $origin_destination .= '?' . $query;
             }
             $this->redirectDestination->set($origin_destination);
             // Override the display's pager_element with the one actually used.
             if (isset($pager_element)) {
                 $response->addCommand(new ScrollTopCommand(".js-view-dom-id-{$dom_id}"));
                 $view->displayHandlers->get($display_id)->setOption('pager_element', $pager_element);
             }
             // Reuse the same DOM id so it matches that in drupalSettings.
             $view->dom_id = $dom_id;
             $context = new RenderContext();
             $preview = $this->renderer->executeInRenderContext($context, function () use($view, $display_id, $args) {
                 return $view->preview($display_id, $args);
             });
             if (!$context->isEmpty()) {
                 $bubbleable_metadata = $context->pop();
                 BubbleableMetadata::createFromRenderArray($preview)->merge($bubbleable_metadata)->applyTo($preview);
             }
             $response->addCommand(new ReplaceCommand(".js-view-dom-id-{$dom_id}", $preview));
             return $response;
         } else {
             throw new AccessDeniedHttpException();
         }
     } else {
         throw new NotFoundHttpException();
     }
 }
Ejemplo n.º 20
0
 /**
  * Pre-render callback: Renders a processed text element into #markup.
  *
  * Runs all the enabled filters on a piece of text.
  *
  * Note: Because filters can inject JavaScript or execute PHP code, security
  * is vital here. When a user supplies a text format, you should validate it
  * using $format->access() before accepting/using it. This is normally done in
  * the validation stage of the Form API. You should for example never make a
  * preview of content in a disallowed format.
  *
  * @param array $element
  *   A structured array with the following key-value pairs:
  *   - #text: containing the text to be filtered
  *   - #format: containing the machine name of the filter format to be used to
  *     filter the text. Defaults to the fallback format.
  *   - #langcode: the language code of the text to be filtered, e.g. 'en' for
  *     English. This allows filters to be language-aware so language-specific
  *     text replacement can be implemented. Defaults to an empty string.
  *   - #filter_types_to_skip: an array of filter types to skip, or an empty
  *     array (default) to skip no filter types. All of the format's filters
  *     will be applied, except for filters of the types that are marked to be
  *     skipped. FilterInterface::TYPE_HTML_RESTRICTOR is the only type that
  *     cannot be skipped.
  *
  * @return array
  *   The passed-in element with the filtered text in '#markup'.
  *
  * @ingroup sanitization
  */
 public static function preRenderText($element)
 {
     $format_id = $element['#format'];
     $filter_types_to_skip = $element['#filter_types_to_skip'];
     $text = $element['#text'];
     $langcode = $element['#langcode'];
     if (!isset($format_id)) {
         $format_id = static::configFactory()->get('filter.settings')->get('fallback_format');
     }
     /** @var \Drupal\filter\Entity\FilterFormat $format **/
     $format = FilterFormat::load($format_id);
     // If the requested text format doesn't exist or its disabled, the text
     // cannot be filtered.
     if (!$format || !$format->status()) {
         $message = !$format ? 'Missing text format: %format.' : 'Disabled text format: %format.';
         static::logger('filter')->alert($message, array('%format' => $format_id));
         $element['#markup'] = '';
         return $element;
     }
     $filter_must_be_applied = function (FilterInterface $filter) use($filter_types_to_skip) {
         $enabled = $filter->status === TRUE;
         $type = $filter->getType();
         // Prevent FilterInterface::TYPE_HTML_RESTRICTOR from being skipped.
         $filter_type_must_be_applied = $type == FilterInterface::TYPE_HTML_RESTRICTOR || !in_array($type, $filter_types_to_skip);
         return $enabled && $filter_type_must_be_applied;
     };
     // Convert all Windows and Mac newlines to a single newline, so filters only
     // need to deal with one possibility.
     $text = str_replace(array("\r\n", "\r"), "\n", $text);
     // Get a complete list of filters, ordered properly.
     /** @var \Drupal\filter\Plugin\FilterInterface[] $filters **/
     $filters = $format->filters();
     // Give filters a chance to escape HTML-like data such as code or formulas.
     foreach ($filters as $filter) {
         if ($filter_must_be_applied($filter)) {
             $text = $filter->prepare($text, $langcode);
         }
     }
     // Perform filtering.
     $metadata = BubbleableMetadata::createFromRenderArray($element);
     foreach ($filters as $filter) {
         if ($filter_must_be_applied($filter)) {
             $result = $filter->process($text, $langcode);
             $metadata = $metadata->merge($result);
             $text = $result->getProcessedText();
         }
     }
     // Filtering and sanitizing have been done in
     // \Drupal\filter\Plugin\FilterInterface. $text is not guaranteed to be
     // safe, but it has been passed through the filter system and checked with
     // a text format, so it must be printed as is. (See the note about security
     // in the method documentation above.)
     $element['#markup'] = FilteredMarkup::create($text);
     // Set the updated bubbleable rendering metadata and the text format's
     // cache tag.
     $metadata->applyTo($element);
     $element['#cache']['tags'] = Cache::mergeTags($element['#cache']['tags'], $format->getCacheTags());
     return $element;
 }
Ejemplo n.º 21
0
 /**
  * @covers ::replace
  * @covers ::replace
  */
 public function testReplaceWithHookTokensAlterWithBubbleableMetadata()
 {
     $this->moduleHandler->expects($this->any())->method('invokeAll')->willReturn([]);
     $this->moduleHandler->expects($this->any())->method('alter')->willReturnCallback(function ($hook_name, array &$replacements, array $context, BubbleableMetadata $bubbleable_metadata) {
         $replacements['[node:title]'] = 'hello world';
         $bubbleable_metadata->addCacheContexts(['custom_context']);
         $bubbleable_metadata->addCacheTags(['node:1']);
         $bubbleable_metadata->setCacheMaxAge(10);
     });
     $node = $this->prophesize('Drupal\\node\\NodeInterface');
     $node->getCacheContexts()->willReturn([]);
     $node->getCacheTags()->willReturn([]);
     $node->getCacheMaxAge()->willReturn(14);
     $node = $node->reveal();
     $bubbleable_metadata = new BubbleableMetadata();
     $bubbleable_metadata->setCacheContexts(['current_user']);
     $bubbleable_metadata->setCacheMaxAge(12);
     $result = $this->token->replace('[node:title]', ['node' => $node], [], $bubbleable_metadata);
     $this->assertEquals('hello world', $result);
     $this->assertEquals(['node:1'], $bubbleable_metadata->getCacheTags());
     $this->assertEquals(['current_user', 'custom_context'], $bubbleable_metadata->getCacheContexts());
     $this->assertEquals(10, $bubbleable_metadata->getCacheMaxAge());
 }
 /**
  * Creates some terms and a node, then tests the tokens generated from them.
  */
 function testTaxonomyTokenReplacement()
 {
     $token_service = \Drupal::token();
     $language_interface = \Drupal::languageManager()->getCurrentLanguage();
     // Create two taxonomy terms.
     $term1 = $this->createTerm($this->vocabulary);
     $term2 = $this->createTerm($this->vocabulary);
     // Edit $term2, setting $term1 as parent.
     $edit = array();
     $edit['name[0][value]'] = '<blink>Blinking Text</blink>';
     $edit['parent[]'] = array($term1->id());
     $this->drupalPostForm('taxonomy/term/' . $term2->id() . '/edit', $edit, t('Save'));
     // Create node with term2.
     $edit = array();
     $node = $this->drupalCreateNode(array('type' => 'article'));
     $edit[$this->fieldName . '[]'] = $term2->id();
     $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
     // Generate and test sanitized tokens for term1.
     $tests = array();
     $tests['[term:tid]'] = $term1->id();
     $tests['[term:name]'] = Html::escape($term1->getName());
     $tests['[term:description]'] = $term1->description->processed;
     $tests['[term:url]'] = $term1->url('canonical', array('absolute' => TRUE));
     $tests['[term:node-count]'] = 0;
     $tests['[term:parent:name]'] = '[term:parent:name]';
     $tests['[term:vocabulary:name]'] = Html::escape($this->vocabulary->label());
     $tests['[term:vocabulary]'] = Html::escape($this->vocabulary->label());
     $base_bubbleable_metadata = BubbleableMetadata::createFromObject($term1);
     $metadata_tests = array();
     $metadata_tests['[term:tid]'] = $base_bubbleable_metadata;
     $metadata_tests['[term:name]'] = $base_bubbleable_metadata;
     $metadata_tests['[term:description]'] = $base_bubbleable_metadata;
     $metadata_tests['[term:url]'] = $base_bubbleable_metadata;
     $metadata_tests['[term:node-count]'] = $base_bubbleable_metadata;
     $metadata_tests['[term:parent:name]'] = $base_bubbleable_metadata;
     $bubbleable_metadata = clone $base_bubbleable_metadata;
     $metadata_tests['[term:vocabulary:name]'] = $bubbleable_metadata->addCacheTags($this->vocabulary->getCacheTags());
     $metadata_tests['[term:vocabulary]'] = $bubbleable_metadata->addCacheTags($this->vocabulary->getCacheTags());
     foreach ($tests as $input => $expected) {
         $bubbleable_metadata = new BubbleableMetadata();
         $output = $token_service->replace($input, array('term' => $term1), array('langcode' => $language_interface->getId()), $bubbleable_metadata);
         $this->assertEqual($output, $expected, format_string('Sanitized taxonomy term token %token replaced.', array('%token' => $input)));
         $this->assertEqual($bubbleable_metadata, $metadata_tests[$input]);
     }
     // Generate and test sanitized tokens for term2.
     $tests = array();
     $tests['[term:tid]'] = $term2->id();
     $tests['[term:name]'] = Html::escape($term2->getName());
     $tests['[term:description]'] = $term2->description->processed;
     $tests['[term:url]'] = $term2->url('canonical', array('absolute' => TRUE));
     $tests['[term:node-count]'] = 1;
     $tests['[term:parent:name]'] = Html::escape($term1->getName());
     $tests['[term:parent:url]'] = $term1->url('canonical', array('absolute' => TRUE));
     $tests['[term:parent:parent:name]'] = '[term:parent:parent:name]';
     $tests['[term:vocabulary:name]'] = Html::escape($this->vocabulary->label());
     // Test to make sure that we generated something for each token.
     $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
     foreach ($tests as $input => $expected) {
         $output = $token_service->replace($input, array('term' => $term2), array('langcode' => $language_interface->getId()));
         $this->assertEqual($output, $expected, format_string('Sanitized taxonomy term token %token replaced.', array('%token' => $input)));
     }
     // Generate and test unsanitized tokens.
     $tests['[term:name]'] = $term2->getName();
     $tests['[term:description]'] = $term2->getDescription();
     $tests['[term:parent:name]'] = $term1->getName();
     $tests['[term:vocabulary:name]'] = $this->vocabulary->label();
     foreach ($tests as $input => $expected) {
         $output = $token_service->replace($input, array('term' => $term2), array('langcode' => $language_interface->getId(), 'sanitize' => FALSE));
         $this->assertEqual($output, $expected, format_string('Unsanitized taxonomy term token %token replaced.', array('%token' => $input)));
     }
     // Generate and test sanitized tokens.
     $tests = array();
     $tests['[vocabulary:vid]'] = $this->vocabulary->id();
     $tests['[vocabulary:name]'] = Html::escape($this->vocabulary->label());
     $tests['[vocabulary:description]'] = Xss::filter($this->vocabulary->getDescription());
     $tests['[vocabulary:node-count]'] = 1;
     $tests['[vocabulary:term-count]'] = 2;
     // Test to make sure that we generated something for each token.
     $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
     foreach ($tests as $input => $expected) {
         $output = $token_service->replace($input, array('vocabulary' => $this->vocabulary), array('langcode' => $language_interface->getId()));
         $this->assertEqual($output, $expected, format_string('Sanitized taxonomy vocabulary token %token replaced.', array('%token' => $input)));
     }
     // Generate and test unsanitized tokens.
     $tests['[vocabulary:name]'] = $this->vocabulary->label();
     $tests['[vocabulary:description]'] = $this->vocabulary->getDescription();
     foreach ($tests as $input => $expected) {
         $output = $token_service->replace($input, array('vocabulary' => $this->vocabulary), array('langcode' => $language_interface->getId(), 'sanitize' => FALSE));
         $this->assertEqual($output, $expected, format_string('Unsanitized taxonomy vocabulary token %token replaced.', array('%token' => $input)));
     }
 }
Ejemplo n.º 23
0
 /**
  * Creates a comment, then tests the tokens generated from it.
  */
 function testCommentTokenReplacement()
 {
     $token_service = \Drupal::token();
     $language_interface = \Drupal::languageManager()->getCurrentLanguage();
     $url_options = array('absolute' => TRUE, 'language' => $language_interface);
     // Change the title of the admin user.
     $this->adminUser->name->value = 'This is a title with some special & > " stuff.';
     $this->adminUser->save();
     $this->drupalLogin($this->adminUser);
     // Set comment variables.
     $this->setCommentSubject(TRUE);
     // Create a node and a comment.
     $node = $this->drupalCreateNode(['type' => 'article', 'title' => '<script>alert("123")</script>']);
     $parent_comment = $this->postComment($node, $this->randomMachineName(), $this->randomMachineName(), TRUE);
     // Post a reply to the comment.
     $this->drupalGet('comment/reply/node/' . $node->id() . '/comment/' . $parent_comment->id());
     $child_comment = $this->postComment(NULL, $this->randomMachineName(), $this->randomMachineName());
     $comment = Comment::load($child_comment->id());
     $comment->setHomepage('http://example.org/');
     // Add HTML to ensure that sanitation of some fields tested directly.
     $comment->setSubject('<blink>Blinking Comment</blink>');
     // Generate and test tokens.
     $tests = array();
     $tests['[comment:cid]'] = $comment->id();
     $tests['[comment:hostname]'] = $comment->getHostname();
     $tests['[comment:author]'] = Html::escape($comment->getAuthorName());
     $tests['[comment:mail]'] = $this->adminUser->getEmail();
     $tests['[comment:homepage]'] = UrlHelper::filterBadProtocol($comment->getHomepage());
     $tests['[comment:title]'] = Html::escape($comment->getSubject());
     $tests['[comment:body]'] = $comment->comment_body->processed;
     $tests['[comment:langcode]'] = $comment->language()->getId();
     $tests['[comment:url]'] = $comment->url('canonical', $url_options + array('fragment' => 'comment-' . $comment->id()));
     $tests['[comment:edit-url]'] = $comment->url('edit-form', $url_options);
     $tests['[comment:created]'] = \Drupal::service('date.formatter')->format($comment->getCreatedTime(), 'medium', array('langcode' => $language_interface->getId()));
     $tests['[comment:created:since]'] = \Drupal::service('date.formatter')->formatTimeDiffSince($comment->getCreatedTime(), array('langcode' => $language_interface->getId()));
     $tests['[comment:changed:since]'] = \Drupal::service('date.formatter')->formatTimeDiffSince($comment->getChangedTimeAcrossTranslations(), array('langcode' => $language_interface->getId()));
     $tests['[comment:parent:cid]'] = $comment->hasParentComment() ? $comment->getParentComment()->id() : NULL;
     $tests['[comment:parent:title]'] = $parent_comment->getSubject();
     $tests['[comment:entity]'] = Html::escape($node->getTitle());
     // Test node specific tokens.
     $tests['[comment:entity:nid]'] = $comment->getCommentedEntityId();
     $tests['[comment:entity:title]'] = Html::escape($node->getTitle());
     $tests['[comment:author:uid]'] = $comment->getOwnerId();
     $tests['[comment:author:name]'] = Html::escape($this->adminUser->getDisplayName());
     $base_bubbleable_metadata = BubbleableMetadata::createFromObject($comment);
     $metadata_tests = [];
     $metadata_tests['[comment:cid]'] = $base_bubbleable_metadata;
     $metadata_tests['[comment:hostname]'] = $base_bubbleable_metadata;
     $bubbleable_metadata = clone $base_bubbleable_metadata;
     $bubbleable_metadata->addCacheableDependency($this->adminUser);
     $metadata_tests['[comment:author]'] = $bubbleable_metadata;
     $bubbleable_metadata = clone $base_bubbleable_metadata;
     $bubbleable_metadata->addCacheableDependency($this->adminUser);
     $metadata_tests['[comment:mail]'] = $bubbleable_metadata;
     $metadata_tests['[comment:homepage]'] = $base_bubbleable_metadata;
     $metadata_tests['[comment:title]'] = $base_bubbleable_metadata;
     $metadata_tests['[comment:body]'] = $base_bubbleable_metadata;
     $metadata_tests['[comment:langcode]'] = $base_bubbleable_metadata;
     $metadata_tests['[comment:url]'] = $base_bubbleable_metadata;
     $metadata_tests['[comment:edit-url]'] = $base_bubbleable_metadata;
     $bubbleable_metadata = clone $base_bubbleable_metadata;
     $metadata_tests['[comment:created]'] = $bubbleable_metadata->addCacheTags(['rendered']);
     $bubbleable_metadata = clone $base_bubbleable_metadata;
     $metadata_tests['[comment:created:since]'] = $bubbleable_metadata->setCacheMaxAge(0);
     $bubbleable_metadata = clone $base_bubbleable_metadata;
     $metadata_tests['[comment:changed:since]'] = $bubbleable_metadata->setCacheMaxAge(0);
     $bubbleable_metadata = clone $base_bubbleable_metadata;
     $metadata_tests['[comment:parent:cid]'] = $bubbleable_metadata->addCacheTags(['comment:1']);
     $metadata_tests['[comment:parent:title]'] = $bubbleable_metadata;
     $bubbleable_metadata = clone $base_bubbleable_metadata;
     $metadata_tests['[comment:entity]'] = $bubbleable_metadata->addCacheTags(['node:2']);
     // Test node specific tokens.
     $metadata_tests['[comment:entity:nid]'] = $bubbleable_metadata;
     $metadata_tests['[comment:entity:title]'] = $bubbleable_metadata;
     $bubbleable_metadata = clone $base_bubbleable_metadata;
     $metadata_tests['[comment:author:uid]'] = $bubbleable_metadata->addCacheTags(['user:2']);
     $metadata_tests['[comment:author:name]'] = $bubbleable_metadata;
     // Test to make sure that we generated something for each token.
     $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
     foreach ($tests as $input => $expected) {
         $bubbleable_metadata = new BubbleableMetadata();
         $output = $token_service->replace($input, array('comment' => $comment), array('langcode' => $language_interface->getId()), $bubbleable_metadata);
         $this->assertEqual($output, $expected, new FormattableMarkup('Comment token %token replaced.', ['%token' => $input]));
         $this->assertEqual($bubbleable_metadata, $metadata_tests[$input]);
     }
     // Test anonymous comment author.
     $author_name = 'This is a random & " > string';
     $comment->setOwnerId(0)->setAuthorName($author_name);
     $input = '[comment:author]';
     $output = $token_service->replace($input, array('comment' => $comment), array('langcode' => $language_interface->getId()));
     $this->assertEqual($output, Html::escape($author_name), format_string('Comment author token %token replaced.', array('%token' => $input)));
     // Load node so comment_count gets computed.
     $node = Node::load($node->id());
     // Generate comment tokens for the node (it has 2 comments, both new).
     $tests = array();
     $tests['[entity:comment-count]'] = 2;
     $tests['[entity:comment-count-new]'] = 2;
     foreach ($tests as $input => $expected) {
         $output = $token_service->replace($input, array('entity' => $node, 'node' => $node), array('langcode' => $language_interface->getId()));
         $this->assertEqual($output, $expected, format_string('Node comment token %token replaced.', array('%token' => $input)));
     }
 }
Ejemplo n.º 24
0
 /**
  * {@inheritdoc}
  */
 public function processOutbound($path, &$options = array(), Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL)
 {
     $url_scheme = 'http';
     $port = 80;
     if ($request) {
         $url_scheme = $request->getScheme();
         $port = $request->getPort();
     }
     $languages = array_flip(array_keys($this->languageManager->getLanguages()));
     // Language can be passed as an option, or we go for current URL language.
     if (!isset($options['language'])) {
         $language_url = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_URL);
         $options['language'] = $language_url;
     } elseif (!is_object($options['language']) || !isset($languages[$options['language']->getId()])) {
         return $path;
     }
     $config = $this->config->get('language.negotiation')->get('url');
     if ($config['source'] == LanguageNegotiationUrl::CONFIG_PATH_PREFIX) {
         if (is_object($options['language']) && !empty($config['prefixes'][$options['language']->getId()])) {
             $options['prefix'] = $config['prefixes'][$options['language']->getId()] . '/';
             if ($bubbleable_metadata) {
                 $bubbleable_metadata->addCacheContexts(['languages:' . LanguageInterface::TYPE_URL]);
             }
         }
     } elseif ($config['source'] == LanguageNegotiationUrl::CONFIG_DOMAIN) {
         if (is_object($options['language']) && !empty($config['domains'][$options['language']->getId()])) {
             // Save the original base URL. If it contains a port, we need to
             // retain it below.
             if (!empty($options['base_url'])) {
                 // The colon in the URL scheme messes up the port checking below.
                 $normalized_base_url = str_replace(array('https://', 'http://'), '', $options['base_url']);
             }
             // Ask for an absolute URL with our modified base URL.
             $options['absolute'] = TRUE;
             $options['base_url'] = $url_scheme . '://' . $config['domains'][$options['language']->getId()];
             // In case either the original base URL or the HTTP host contains a
             // port, retain it.
             if (isset($normalized_base_url) && strpos($normalized_base_url, ':') !== FALSE) {
                 list(, $port) = explode(':', $normalized_base_url);
                 $options['base_url'] .= ':' . $port;
             } elseif ($url_scheme == 'http' && $port != 80 || $url_scheme == 'https' && $port != 443) {
                 $options['base_url'] .= ':' . $port;
             }
             if (isset($options['https'])) {
                 if ($options['https'] === TRUE) {
                     $options['base_url'] = str_replace('http://', 'https://', $options['base_url']);
                 } elseif ($options['https'] === FALSE) {
                     $options['base_url'] = str_replace('https://', 'http://', $options['base_url']);
                 }
             }
             // Add Drupal's subfolder from the base_path if there is one.
             $options['base_url'] .= rtrim(base_path(), '/');
             if ($bubbleable_metadata) {
                 $bubbleable_metadata->addCacheContexts(['languages:' . LanguageInterface::TYPE_URL, 'url.site']);
             }
         }
     }
     return $path;
 }
 /**
  * Tests bubbleable metadata of menu links' outbound route/path processing.
  */
 public function testOutboundPathAndRouteProcessing()
 {
     \Drupal::service('router.builder')->rebuild();
     $request_stack = \Drupal::requestStack();
     /** @var \Symfony\Component\Routing\RequestContext $request_context */
     $request_context = \Drupal::service('router.request_context');
     $request = Request::create('/');
     $request->attributes->set(RouteObjectInterface::ROUTE_NAME, '<front>');
     $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('/'));
     $request_stack->push($request);
     $request_context->fromRequest($request);
     $menu_tree = \Drupal::menuTree();
     $renderer = \Drupal::service('renderer');
     $default_menu_cacheability = (new BubbleableMetadata())->setCacheMaxAge(Cache::PERMANENT)->setCacheTags(['config:system.menu.tools'])->setCacheContexts(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions']);
     User::create(['uid' => 1, 'name' => $this->randomString()])->save();
     User::create(['uid' => 2, 'name' => $this->randomString()])->save();
     // Five test cases, four asserting one outbound path/route processor, and
     // together covering one of each:
     // - no cacheability metadata,
     // - a cache context,
     // - a cache tag,
     // - a cache max-age.
     // Plus an additional test case to verify that multiple links adding
     // cacheability metadata of the same type is working (two links with cache
     // tags).
     $test_cases = [['uri' => 'route:<current>', 'cacheability' => (new BubbleableMetadata())->setCacheContexts(['route'])], ['uri' => 'route:outbound_processing_test.route.csrf', 'cacheability' => (new BubbleableMetadata())->setCacheContexts(['session'])->setAttachments(['placeholders' => []])], ['uri' => 'internal:/', 'cacheability' => new BubbleableMetadata()], ['uri' => 'internal:/user/1', 'cacheability' => (new BubbleableMetadata())->setCacheTags(User::load(1)->getCacheTags())], ['uri' => 'internal:/user/2', 'cacheability' => (new BubbleableMetadata())->setCacheTags(User::load(2)->getCacheTags())]];
     // Test each expectation individually.
     foreach ($test_cases as $expectation) {
         $menu_link_content = MenuLinkContent::create(['link' => ['uri' => $expectation['uri']], 'menu_name' => 'tools']);
         $menu_link_content->save();
         $tree = $menu_tree->load('tools', new MenuTreeParameters());
         $build = $menu_tree->build($tree);
         $renderer->renderRoot($build);
         $expected_cacheability = $default_menu_cacheability->merge($expectation['cacheability']);
         $this->assertEqual($expected_cacheability, BubbleableMetadata::createFromRenderArray($build));
         $menu_link_content->delete();
     }
     // Now test them all together in one menu: the rendered menu's cacheability
     // metadata should be the combination of the cacheability of all links, and
     // thus of all tested outbound path & route processors.
     $expected_cacheability = new BubbleableMetadata();
     foreach ($test_cases as $expectation) {
         $menu_link_content = MenuLinkContent::create(['link' => ['uri' => $expectation['uri']], 'menu_name' => 'tools']);
         $menu_link_content->save();
         $expected_cacheability = $expected_cacheability->merge($expectation['cacheability']);
     }
     $tree = $menu_tree->load('tools', new MenuTreeParameters());
     $build = $menu_tree->build($tree);
     $renderer->renderRoot($build);
     $expected_cacheability = $expected_cacheability->merge($default_menu_cacheability);
     $this->assertEqual($expected_cacheability, BubbleableMetadata::createFromRenderArray($build));
 }
 /**
  * @covers ::addCacheableDependency
  * @dataProvider providerTestMerge
  *
  * This only tests at a high level, because it reuses existing logic. Detailed
  * tests exist for the existing logic:
  *
  * @see \Drupal\Tests\Core\Cache\CacheTest::testMergeTags()
  * @see \Drupal\Tests\Core\Cache\CacheTest::testMergeMaxAges()
  * @see \Drupal\Tests\Core\Cache\CacheContextsTest
  */
 public function testAddCacheableDependency(BubbleableMetadata $a, $b, BubbleableMetadata $expected)
 {
     $cache_contexts_manager = $this->getMockBuilder('Drupal\\Core\\Cache\\Context\\CacheContextsManager')->disableOriginalConstructor()->getMock();
     $cache_contexts_manager->method('assertValidTokens')->willReturn(TRUE);
     $container = new ContainerBuilder();
     $container->set('cache_contexts_manager', $cache_contexts_manager);
     \Drupal::setContainer($container);
     $this->assertEquals($expected, $a->addCacheableDependency($b));
 }
 /**
  * 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;
 }
Ejemplo n.º 28
0
 /**
  * Creates a file, then tests the tokens generated from it.
  */
 function testFileTokenReplacement()
 {
     $node_storage = $this->container->get('entity.manager')->getStorage('node');
     $token_service = \Drupal::token();
     $language_interface = \Drupal::languageManager()->getCurrentLanguage();
     // Create file field.
     $type_name = 'article';
     $field_name = 'field_' . strtolower($this->randomMachineName());
     $this->createFileField($field_name, 'node', $type_name);
     $test_file = $this->getTestFile('text');
     // Coping a file to test uploads with non-latin filenames.
     $filename = drupal_dirname($test_file->getFileUri()) . '/текстовый файл.txt';
     $test_file = file_copy($test_file, $filename);
     // Create a new node with the uploaded file.
     $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
     // Load the node and the file.
     $node_storage->resetCache(array($nid));
     $node = $node_storage->load($nid);
     $file = File::load($node->{$field_name}->target_id);
     // Generate and test sanitized tokens.
     $tests = array();
     $tests['[file:fid]'] = $file->id();
     $tests['[file:name]'] = SafeMarkup::checkPlain($file->getFilename());
     $tests['[file:path]'] = SafeMarkup::checkPlain($file->getFileUri());
     $tests['[file:mime]'] = SafeMarkup::checkPlain($file->getMimeType());
     $tests['[file:size]'] = format_size($file->getSize());
     $tests['[file:url]'] = SafeMarkup::checkPlain(file_create_url($file->getFileUri()));
     $tests['[file:created]'] = format_date($file->getCreatedTime(), 'medium', '', NULL, $language_interface->getId());
     $tests['[file:created:short]'] = format_date($file->getCreatedTime(), 'short', '', NULL, $language_interface->getId());
     $tests['[file:changed]'] = format_date($file->getChangedTime(), 'medium', '', NULL, $language_interface->getId());
     $tests['[file:changed:short]'] = format_date($file->getChangedTime(), 'short', '', NULL, $language_interface->getId());
     $tests['[file:owner]'] = SafeMarkup::checkPlain(user_format_name($this->adminUser));
     $tests['[file:owner:uid]'] = $file->getOwnerId();
     $base_bubbleable_metadata = BubbleableMetadata::createFromObject($file);
     $metadata_tests = [];
     $metadata_tests['[file:fid]'] = $base_bubbleable_metadata;
     $metadata_tests['[file:name]'] = $base_bubbleable_metadata;
     $metadata_tests['[file:path]'] = $base_bubbleable_metadata;
     $metadata_tests['[file:mime]'] = $base_bubbleable_metadata;
     $metadata_tests['[file:size]'] = $base_bubbleable_metadata;
     $metadata_tests['[file:url]'] = $base_bubbleable_metadata;
     $bubbleable_metadata = clone $base_bubbleable_metadata;
     $metadata_tests['[file:created]'] = $bubbleable_metadata->addCacheTags(['rendered']);
     $metadata_tests['[file:created:short]'] = $bubbleable_metadata;
     $metadata_tests['[file:changed]'] = $bubbleable_metadata;
     $metadata_tests['[file:changed:short]'] = $bubbleable_metadata;
     $bubbleable_metadata = clone $base_bubbleable_metadata;
     $metadata_tests['[file:owner]'] = $bubbleable_metadata->addCacheTags(['user:2']);
     $metadata_tests['[file:owner:uid]'] = $bubbleable_metadata;
     // Test to make sure that we generated something for each token.
     $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
     foreach ($tests as $input => $expected) {
         $bubbleable_metadata = new BubbleableMetadata();
         $output = $token_service->replace($input, array('file' => $file), array('langcode' => $language_interface->getId()), $bubbleable_metadata);
         $this->assertEqual($output, $expected, format_string('Sanitized file token %token replaced.', array('%token' => $input)));
         $this->assertEqual($bubbleable_metadata, $metadata_tests[$input]);
     }
     // Generate and test unsanitized tokens.
     $tests['[file:name]'] = $file->getFilename();
     $tests['[file:path]'] = $file->getFileUri();
     $tests['[file:mime]'] = $file->getMimeType();
     $tests['[file:size]'] = format_size($file->getSize());
     foreach ($tests as $input => $expected) {
         $output = $token_service->replace($input, array('file' => $file), array('langcode' => $language_interface->getId(), 'sanitize' => FALSE));
         $this->assertEqual($output, $expected, format_string('Unsanitized file token %token replaced.', array('%token' => $input)));
     }
 }
 /**
  * Adds Ajax information about an element to communicate with JavaScript.
  *
  * If #ajax is set on an element, this additional JavaScript is added to the
  * page header to attach the Ajax behaviors. See ajax.js for more information.
  *
  * @param array $element
  *   An associative array containing the properties of the element.
  *   Properties used:
  *   - #ajax['event']
  *   - #ajax['prevent']
  *   - #ajax['url']
  *   - #ajax['callback']
  *   - #ajax['options']
  *   - #ajax['wrapper']
  *   - #ajax['parameters']
  *   - #ajax['effect']
  *   - #ajax['accepts']
  *
  * @return array
  *   The processed element with the necessary JavaScript attached to it.
  */
 public static function preRenderAjaxForm($element)
 {
     // Skip already processed elements.
     if (isset($element['#ajax_processed'])) {
         return $element;
     }
     // Initialize #ajax_processed, so we do not process this element again.
     $element['#ajax_processed'] = FALSE;
     // Nothing to do if there are no Ajax settings.
     if (empty($element['#ajax'])) {
         return $element;
     }
     // Add a reasonable default event handler if none was specified.
     if (isset($element['#ajax']) && !isset($element['#ajax']['event'])) {
         switch ($element['#type']) {
             case 'submit':
             case 'button':
             case 'image_button':
                 // Pressing the ENTER key within a textfield triggers the click event of
                 // the form's first submit button. Triggering Ajax in this situation
                 // leads to problems, like breaking autocomplete textfields, so we bind
                 // to mousedown instead of click.
                 // @see https://www.drupal.org/node/216059
                 $element['#ajax']['event'] = 'mousedown';
                 // Retain keyboard accessibility by setting 'keypress'. This causes
                 // ajax.js to trigger 'event' when SPACE or ENTER are pressed while the
                 // button has focus.
                 $element['#ajax']['keypress'] = TRUE;
                 // Binding to mousedown rather than click means that it is possible to
                 // trigger a click by pressing the mouse, holding the mouse button down
                 // until the Ajax request is complete and the button is re-enabled, and
                 // then releasing the mouse button. Set 'prevent' so that ajax.js binds
                 // an additional handler to prevent such a click from triggering a
                 // non-Ajax form submission. This also prevents a textfield's ENTER
                 // press triggering this button's non-Ajax form submission behavior.
                 if (!isset($element['#ajax']['prevent'])) {
                     $element['#ajax']['prevent'] = 'click';
                 }
                 break;
             case 'password':
             case 'textfield':
             case 'number':
             case 'tel':
             case 'textarea':
                 $element['#ajax']['event'] = 'blur';
                 break;
             case 'radio':
             case 'checkbox':
             case 'select':
                 $element['#ajax']['event'] = 'change';
                 break;
             case 'link':
                 $element['#ajax']['event'] = 'click';
                 break;
             default:
                 return $element;
         }
     }
     // Attach JavaScript settings to the element.
     if (isset($element['#ajax']['event'])) {
         $element['#attached']['library'][] = 'core/jquery.form';
         $element['#attached']['library'][] = 'core/drupal.ajax';
         $settings = $element['#ajax'];
         // Assign default settings. When 'url' is set to NULL, ajax.js submits the
         // Ajax request to the same URL as the form or link destination is for
         // someone with JavaScript disabled. This is generally preferred as a way to
         // ensure consistent server processing for js and no-js users, and Drupal's
         // content negotiation takes care of formatting the response appropriately.
         // However, 'url' and 'options' may be set when wanting server processing
         // to be substantially different for a JavaScript triggered submission.
         $settings += ['url' => NULL, 'options' => ['query' => []], 'dialogType' => 'ajax'];
         if (array_key_exists('callback', $settings) && !isset($settings['url'])) {
             $settings['url'] = Url::fromRoute('<current>');
             // Add all the current query parameters in order to ensure that we build
             // the same form on the AJAX POST requests. For example,
             // \Drupal\user\AccountForm takes query parameters into account in order
             // to hide the password field dynamically.
             $settings['options']['query'] += \Drupal::request()->query->all();
             $settings['options']['query'][FormBuilderInterface::AJAX_FORM_REQUEST] = TRUE;
         }
         // @todo Legacy support. Remove in Drupal 8.
         if (isset($settings['method']) && $settings['method'] == 'replace') {
             $settings['method'] = 'replaceWith';
         }
         // Convert \Drupal\Core\Url object to string.
         if (isset($settings['url']) && $settings['url'] instanceof Url) {
             $url = $settings['url']->setOptions($settings['options'])->toString(TRUE);
             BubbleableMetadata::createFromRenderArray($element)->merge($url)->applyTo($element);
             $settings['url'] = $url->getGeneratedUrl();
         } else {
             $settings['url'] = NULL;
         }
         unset($settings['options']);
         // Add special data to $settings['submit'] so that when this element
         // triggers an Ajax submission, Drupal's form processing can determine which
         // element triggered it.
         // @see _form_element_triggered_scripted_submission()
         if (isset($settings['trigger_as'])) {
             // An element can add a 'trigger_as' key within #ajax to make the element
             // submit as though another one (for example, a non-button can use this
             // to submit the form as though a button were clicked). When using this,
             // the 'name' key is always required to identify the element to trigger
             // as. The 'value' key is optional, and only needed when multiple elements
             // share the same name, which is commonly the case for buttons.
             $settings['submit']['_triggering_element_name'] = $settings['trigger_as']['name'];
             if (isset($settings['trigger_as']['value'])) {
                 $settings['submit']['_triggering_element_value'] = $settings['trigger_as']['value'];
             }
             unset($settings['trigger_as']);
         } elseif (isset($element['#name'])) {
             // Most of the time, elements can submit as themselves, in which case the
             // 'trigger_as' key isn't needed, and the element's name is used.
             $settings['submit']['_triggering_element_name'] = $element['#name'];
             // If the element is a (non-image) button, its name may not identify it
             // uniquely, in which case a match on value is also needed.
             // @see _form_button_was_clicked()
             if (!empty($element['#is_button']) && empty($element['#has_garbage_value'])) {
                 $settings['submit']['_triggering_element_value'] = $element['#value'];
             }
         }
         // Convert a simple #ajax['progress'] string into an array.
         if (isset($settings['progress']) && is_string($settings['progress'])) {
             $settings['progress'] = array('type' => $settings['progress']);
         }
         // Change progress path to a full URL.
         if (isset($settings['progress']['url']) && $settings['progress']['url'] instanceof Url) {
             $settings['progress']['url'] = $settings['progress']['url']->toString();
         }
         $element['#attached']['drupalSettings']['ajax'][$element['#id']] = $settings;
         $element['#attached']['drupalSettings']['ajaxTrustedUrl'][$settings['url']] = TRUE;
         // Indicate that Ajax processing was successful.
         $element['#ajax_processed'] = TRUE;
     }
     return $element;
 }
Ejemplo n.º 30
0
 /**
  * {@inheritdoc}
  */
 public function mergeBubbleableMetadata(array $a, array $b)
 {
     $meta_a = BubbleableMetadata::createFromRenderArray($a);
     $meta_b = BubbleableMetadata::createFromRenderArray($b);
     $meta_a->merge($meta_b)->applyTo($a);
     return $a;
 }