/** * Tests that #cache_properties are properly handled. * * @param array $expected_results * An associative array of expected results keyed by property name. * * @covers ::render * @covers ::doRender * @covers \Drupal\Core\Render\RenderCache::get * @covers \Drupal\Core\Render\RenderCache::set * @covers \Drupal\Core\Render\RenderCache::createCacheID * @covers \Drupal\Core\Render\RenderCache::getCacheableRenderArray * * @dataProvider providerTestRenderCacheProperties */ public function testRenderCacheProperties(array $expected_results) { $this->setUpRequest(); $this->setupMemoryCache(); $element = $original = [ '#cache' => [ 'keys' => ['render_cache_test'], ], // Collect expected property names. '#cache_properties' => array_keys(array_filter($expected_results)), 'child1' => ['#markup' => Markup::create('1')], 'child2' => ['#markup' => Markup::create('2')], // Mark the value as safe. '#custom_property' => Markup::create('custom_value'), '#custom_property_array' => ['custom value'], ]; $this->renderer->renderRoot($element); $cache = $this->cacheFactory->get('render'); $data = $cache->get('render_cache_test:en:stark')->data; // Check that parent markup is ignored when caching children's markup. $this->assertEquals($data['#markup'] === '', (bool) Element::children($data)); // Check that the element properties are cached as specified. foreach ($expected_results as $property => $expected) { $cached = !empty($data[$property]); $this->assertEquals($cached, (bool) $expected); // Check that only the #markup key is preserved for children. if ($cached) { $this->assertEquals($data[$property], $original[$property]); } } // #custom_property_array can not be a safe_cache_property. $safe_cache_properties = array_diff(Element::properties(array_filter($expected_results)), ['#custom_property_array']); foreach ($safe_cache_properties as $cache_property) { $this->assertTrue(SafeMarkup::isSafe($data[$cache_property]), "$cache_property is marked as a safe string"); } }
/** * Tests the properties() method. */ public function testProperties() { $element = array('#property1' => 'property1', '#property2' => 'property2', 'property3' => 'property3'); $properties = Element::properties($element); $this->assertContains('#property1', $properties); $this->assertContains('#property2', $properties); $this->assertNotContains('property3', $properties); }
/** * Expands an element into a base element with text format selector attached. * * The form element will be expanded into two separate form elements, one * holding the original element, and the other holding the text format * selector: * - value: Holds the original element, having its #type changed to the value * of #base_type or 'textarea' by default. * - format: Holds the text format details and the text format selection, * using the text format ID specified in #format or the user's default * format by default, if NULL. * * The resulting value for the element will be an array holding the value and * the format. For example, the value for the body element will be: * @code * $values = $form_state->getValue('body'); * $values['value'] = 'foo'; * $values['format'] = 'foo'; * @endcode * * @param array $element * The form element to process. See main class documentation for properties. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. * @param array $complete_form * The complete form structure. * * @return array * The form element. */ public static function processFormat(&$element, FormStateInterface $form_state, &$complete_form) { $user = static::currentUser(); // Ensure that children appear as subkeys of this element. $element['#tree'] = TRUE; $blacklist = array('#parents', '#id', '#name', '#process', '#pre_render', '#description', '#weight', '#prefix', '#suffix', '#attached', '#processed', '#theme_wrappers'); // Move this element into sub-element 'value'. unset($element['value']); foreach (Element::properties($element) as $key) { if (!in_array($key, $blacklist)) { $element['value'][$key] = $element[$key]; } } $element['value']['#type'] = $element['#base_type']; $element['value'] += static::elementInfo()->getInfo($element['#base_type']); // Make sure the #default_value key is set, so we can use it below. $element['value'] += array('#default_value' => ''); // Turn original element into a text format wrapper. $element['#attached']['library'][] = 'filter/drupal.filter'; // Setup child container for the text format widget. $element['format'] = array('#type' => 'container', '#attributes' => array('class' => array('filter-wrapper'))); // Get a list of formats that the current user has access to. $formats = filter_formats($user); // Allow the list of formats to be restricted. if (isset($element['#allowed_formats'])) { // We do not add the fallback format here to allow the use-case of forcing // certain text formats to be used for certain text areas. In case the // fallback format is supposed to be allowed as well, it must be added to // $element['#allowed_formats'] explicitly. $formats = array_intersect_key($formats, array_flip($element['#allowed_formats'])); } if (!isset($element['#format']) && !empty($formats)) { // If no text format was selected, use the allowed format with the highest // weight. This is equivalent to calling filter_default_format(). $element['#format'] = reset($formats)->id(); } // If #allowed_formats is set, the list of formats must not be modified in // any way. Otherwise, however, if all of the following conditions are true, // remove the fallback format from the list of formats: // 1. The 'always_show_fallback_choice' filter setting has not been activated. // 2. Multiple text formats are available. // 3. The fallback format is not the default format. // The 'always_show_fallback_choice' filter setting is a hidden setting that // has no UI. It defaults to FALSE. $config = static::configFactory()->get('filter.settings'); if (!isset($element['#allowed_formats']) && !$config->get('always_show_fallback_choice')) { $fallback_format = $config->get('fallback_format'); if ($element['#format'] !== $fallback_format && count($formats) > 1) { unset($formats[$fallback_format]); } } // Prepare text format guidelines. $element['format']['guidelines'] = array('#type' => 'container', '#attributes' => array('class' => array('filter-guidelines')), '#weight' => 20); $options = array(); foreach ($formats as $format) { $options[$format->id()] = $format->label(); $element['format']['guidelines'][$format->id()] = array('#theme' => 'filter_guidelines', '#format' => $format); } $element['format']['format'] = array('#type' => 'select', '#title' => t('Text format'), '#options' => $options, '#default_value' => $element['#format'], '#access' => count($formats) > 1, '#weight' => 10, '#attributes' => array('class' => array('filter-list')), '#parents' => array_merge($element['#parents'], array('format'))); $element['format']['help'] = ['#type' => 'container', 'about' => ['#type' => 'link', '#title' => t('About text formats'), '#url' => new Url('filter.tips_all'), '#attributes' => ['target' => '_blank']], '#attributes' => ['class' => ['filter-help']], '#weight' => 0]; $all_formats = filter_formats(); $format_exists = isset($all_formats[$element['#format']]); $format_allowed = !isset($element['#allowed_formats']) || in_array($element['#format'], $element['#allowed_formats']); $user_has_access = isset($formats[$element['#format']]); $user_is_admin = $user->hasPermission('administer filters'); // If the stored format does not exist or if it is not among the allowed // formats for this textarea, administrators have to assign a new format. if ((!$format_exists || !$format_allowed) && $user_is_admin) { $element['format']['format']['#required'] = TRUE; $element['format']['format']['#default_value'] = NULL; // Force access to the format selector (it may have been denied above if // the user only has access to a single format). $element['format']['format']['#access'] = TRUE; } elseif (!$user_has_access || !$format_exists) { // Overload default values into #value to make them unalterable. $element['value']['#value'] = $element['value']['#default_value']; $element['format']['format']['#value'] = $element['format']['format']['#default_value']; // Prepend #pre_render callback to replace field value with user notice // prior to rendering. $element['value'] += array('#pre_render' => array()); array_unshift($element['value']['#pre_render'], 'filter_form_access_denied'); // Cosmetic adjustments. if (isset($element['value']['#rows'])) { $element['value']['#rows'] = 3; } $element['value']['#disabled'] = TRUE; $element['value']['#resizable'] = 'none'; // Hide the text format selector and any other child element (such as text // field's summary). foreach (Element::children($element) as $key) { if ($key != 'value') { $element[$key]['#access'] = FALSE; } } } return $element; }
/** * Gets properties of a structured array element (keys beginning with '#'). * * @return array * An array of property keys for the element. */ public function properties() { return \Drupal\Core\Render\Element::properties($this->array); }
/** * {@inheritdoc} */ public function getCacheableRenderArray(array $elements) { $data = ['#markup' => $elements['#markup'], '#attached' => $elements['#attached'], '#cache' => ['contexts' => $elements['#cache']['contexts'], 'tags' => $elements['#cache']['tags'], 'max-age' => $elements['#cache']['max-age']]]; // Preserve cacheable items if specified. If we are preserving any cacheable // children of the element, we assume we are only interested in their // individual markup and not the parent's one, thus we empty it to minimize // the cache entry size. if (!empty($elements['#cache_properties']) && is_array($elements['#cache_properties'])) { $data['#cache_properties'] = $elements['#cache_properties']; // Ensure that any safe strings are a Markup object. foreach (Element::properties(array_flip($elements['#cache_properties'])) as $cache_property) { if (isset($elements[$cache_property]) && is_scalar($elements[$cache_property]) && SafeMarkup::isSafe($elements[$cache_property])) { $elements[$cache_property] = Markup::create($elements[$cache_property]); } } // Extract all the cacheable items from the element using cache // properties. $cacheable_items = array_intersect_key($elements, array_flip($elements['#cache_properties'])); $cacheable_children = Element::children($cacheable_items); if ($cacheable_children) { $data['#markup'] = ''; // Cache only cacheable children's markup. foreach ($cacheable_children as $key) { // We can assume that #markup is safe at this point. $cacheable_items[$key] = ['#markup' => Markup::create($cacheable_items[$key]['#markup'])]; } } $data += $cacheable_items; } $data['#markup'] = Markup::create($data['#markup']); return $data; }