/** * Renders all of the fields for a given style and store them on the object. * * @param array $result * The result array from $view->result */ protected function renderFields(array $result) { if (!$this->usesFields()) { return; } if (!isset($this->rendered_fields)) { $this->rendered_fields = []; $this->view->row_index = 0; $field_ids = array_keys($this->view->field); // Only tokens relating to field handlers preceding the one we invoke // ::getRenderTokens() on are returned, so here we need to pick the last // available field handler. $render_tokens_field_id = end($field_ids); // If all fields have a field::access FALSE there might be no fields, so // there is no reason to execute this code. if (!empty($field_ids)) { $renderer = $this->getRenderer(); /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache_plugin */ $cache_plugin = $this->view->display_handler->getPlugin('cache'); /** @var \Drupal\views\ResultRow $row */ foreach ($result as $index => $row) { $this->view->row_index = $index; // Here we implement render caching for result rows. Since we never // build a render array for single rows, given that style templates // need individual field markup to support proper theming, we build // a raw render array containing all field render arrays and cache it. // This allows us to cache the markup of the various children, that is // individual fields, which is then available for style template // preprocess functions, later in the rendering workflow. // @todo Fetch all the available cached row items in one single cache // get operation, once https://www.drupal.org/node/2453945 is fixed. $data = ['#pre_render' => [[$this, 'elementPreRenderRow']], '#row' => $row, '#cache' => ['keys' => $cache_plugin->getRowCacheKeys($row), 'tags' => $cache_plugin->getRowCacheTags($row)], '#cache_properties' => $field_ids]; $renderer->addCacheableDependency($data, $this->view->storage); $renderer->renderPlain($data); // Extract field output from the render array and post process it. $fields = $this->view->field; $rendered_fields =& $this->rendered_fields[$index]; $post_render_tokens = []; foreach ($field_ids as $id) { $rendered_fields[$id] = $data[$id]['#markup']; $tokens = $fields[$id]->postRender($row, $rendered_fields[$id]); if ($tokens) { $post_render_tokens += $tokens; } } // Populate row tokens. $this->rowTokens[$index] = $this->view->field[$render_tokens_field_id]->getRenderTokens([]); // Replace post-render tokens. if ($post_render_tokens) { $placeholders = array_keys($post_render_tokens); $values = array_values($post_render_tokens); foreach ($this->rendered_fields[$index] as &$rendered_field) { // Placeholders and rendered fields have been processed by the // render system and are therefore safe. $rendered_field = ViewsRenderPipelineSafeString::create(str_replace($placeholders, $values, $rendered_field)); } } } } unset($this->view->row_index); } }
/** * {@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'] = ViewsRenderPipelineSafeString::create($build['#markup']); } parent::applyDisplayCachablityMetadata($build); return $build; }
/** * {@inheritdoc} */ public function renderText($alter) { // We need to preserve the safeness of the value regardless of the // alterations made by this method. Any alterations or replacements made // within this method need to ensure that at the minimum the result is // XSS admin filtered. See self::renderAltered() as an example that does. $value_is_safe = SafeMarkup::isSafe($this->last_render); // Cast to a string so that empty checks and string functions work as // expected. $value = (string) $this->last_render; if (!empty($alter['alter_text']) && $alter['text'] !== '') { $tokens = $this->getRenderTokens($alter); $value = $this->renderAltered($alter, $tokens); } if (!empty($this->options['alter']['trim_whitespace'])) { $value = trim($value); } // Check if there should be no further rewrite for empty values. $no_rewrite_for_empty = $this->options['hide_alter_empty'] && $this->isValueEmpty($this->original_value, $this->options['empty_zero']); // Check whether the value is empty and return nothing, so the field isn't rendered. // First check whether the field should be hidden if the value(hide_alter_empty = TRUE) /the rewrite is empty (hide_alter_empty = FALSE). // For numeric values you can specify whether "0"/0 should be empty. if (($this->options['hide_empty'] && empty($value) || $alter['phase'] != static::RENDER_TEXT_PHASE_EMPTY && $no_rewrite_for_empty) && $this->isValueEmpty($value, $this->options['empty_zero'], FALSE)) { return ''; } // Only in empty phase. if ($alter['phase'] == static::RENDER_TEXT_PHASE_EMPTY && $no_rewrite_for_empty) { // If we got here then $alter contains the value of "No results text" // and so there is nothing left to do. if ($value_is_safe) { $value = ViewsRenderPipelineSafeString::create($value); } return $value; } if (!empty($alter['strip_tags'])) { $value = strip_tags($value, $alter['preserve_tags']); } $suffix = ''; if (!empty($alter['trim']) && !empty($alter['max_length'])) { $length = strlen($value); $value = $this->renderTrimText($alter, $value); if ($this->options['alter']['more_link'] && strlen($value) < $length) { $tokens = $this->getRenderTokens($alter); $more_link_text = $this->options['alter']['more_link_text'] ? $this->options['alter']['more_link_text'] : $this->t('more'); $more_link_text = strtr(Xss::filterAdmin($more_link_text), $tokens); $more_link_path = $this->options['alter']['more_link_path']; $more_link_path = strip_tags(Html::decodeEntities($this->viewsTokenReplace($more_link_path, $tokens))); // Make sure that paths which were run through _url() work as well. $base_path = base_path(); // Checks whether the path starts with the base_path. if (strpos($more_link_path, $base_path) === 0) { $more_link_path = Unicode::substr($more_link_path, Unicode::strlen($base_path)); } // @todo Views should expect and store a leading /. See // https://www.drupal.org/node/2423913. $more_link = \Drupal::l($more_link_text, CoreUrl::fromUserInput('/' . $more_link_path, array('attributes' => array('class' => array('views-more-link'))))); $suffix .= " " . $more_link; } } if (!empty($alter['nl2br'])) { $value = nl2br($value); } // Preserve whether or not the string is safe. Since $suffix comes from // \Drupal::l(), it is safe to append. if ($value_is_safe) { $value = ViewsRenderPipelineSafeString::create($value . $suffix); } $this->last_render_text = $value; if (!empty($alter['make_link']) && (!empty($alter['path']) || !empty($alter['url']))) { if (!isset($tokens)) { $tokens = $this->getRenderTokens($alter); } $value = $this->renderAsLink($alter, $value, $tokens); } // Preserve whether or not the string is safe. Since $suffix comes from // \Drupal::l(), it is safe to append. if ($value_is_safe) { return ViewsRenderPipelineSafeString::create($value . $suffix); } else { // If the string is not already marked safe, it is still OK to return it // because it will be sanitized by Twig. return $value . $suffix; } }