/** * Additional #pre_render callback for 'text_format' elements. */ function preRenderTextFormat(array $element) { // Allow modules to programmatically enforce no client-side editor by // setting the #editor property to FALSE. if (isset($element['#editor']) && !$element['#editor']) { return $element; } // filter_process_format() copies properties to the expanded 'value' child // element, including the #pre_render property. Skip this text format // widget, if it contains no 'format'. if (!isset($element['format'])) { return $element; } $format_ids = array_keys($element['format']['format']['#options']); // Early-return if no text editor is associated with any of the text formats. $editors = Editor::loadMultiple($format_ids); if (count($editors) === 0) { return $element; } // Use a hidden element for a single text format. $field_id = $element['value']['#id']; if (!$element['format']['format']['#access']) { // Use the first (and only) available text format. $format_id = $format_ids[0]; $element['format']['editor'] = array('#type' => 'hidden', '#name' => $element['format']['format']['#name'], '#value' => $format_id, '#attributes' => array('class' => array('editor'), 'data-editor-for' => $field_id)); } else { $element['format']['format']['#attributes']['class'][] = 'editor'; $element['format']['format']['#attributes']['data-editor-for'] = $field_id; } // Hide the text format's filters' guidelines of those text formats that have // a text editor associated: they're rather useless when using a text editor. foreach ($editors as $format_id => $editor) { $element['format']['guidelines'][$format_id]['#access'] = FALSE; } // Attach Text Editor module's (this module) library. $element['#attached']['library'][] = 'editor/drupal.editor'; // Attach attachments for all available editors. $element['#attached'] = drupal_merge_attached($element['#attached'], $this->pluginManager->getAttachments($format_ids)); // Apply XSS filters when editing content if necessary. Some types of text // editors cannot guarantee that the end user won't become a victim of XSS. if (!empty($element['value']['#value'])) { $original = $element['value']['#value']; $format = FilterFormat::load($element['format']['format']['#value']); // Ensure XSS-safety for the current text format/editor. $filtered = editor_filter_xss($original, $format); if ($filtered !== FALSE) { $element['value']['#value'] = $filtered; } // Only when the user has access to multiple text formats, we must add data- // attributes for the original value and change tracking, because they are // only necessary when the end user can switch between text formats/editors. if ($element['format']['format']['#access']) { $element['value']['#attributes']['data-editor-value-is-changed'] = 'false'; $element['value']['#attributes']['data-editor-value-original'] = $original; } } return $element; }
/** * #post_render_cache callback; replaces placeholder with comment form. * * @param array $element * The renderable array that contains the to be replaced placeholder. * @param array $context * An array with the following keys: * - entity_type: an entity type * - entity_id: an entity ID * - field_name: a comment field name * * @return array * A renderable array containing the comment form. */ public function renderForm(array $element, array $context) { $field_name = $context['field_name']; $entity = $this->entityManager->getStorage($context['entity_type'])->load($context['entity_id']); $field_storage = FieldStorageConfig::loadByName($entity->getEntityTypeId(), $field_name); $values = array('entity_type' => $entity->getEntityTypeId(), 'entity_id' => $entity->id(), 'field_name' => $field_name, 'comment_type' => $field_storage->getSetting('bundle'), 'pid' => NULL); $comment = $this->entityManager->getStorage('comment')->create($values); $form = $this->entityFormBuilder->getForm($comment); $markup = drupal_render($form); $callback = 'comment.post_render_cache:renderForm'; $placeholder = drupal_render_cache_generate_placeholder($callback, $context); $element['#markup'] = str_replace($placeholder, $markup, $element['#markup']); $element['#attached'] = drupal_merge_attached($element['#attached'], $form['#attached']); return $element; }
/** * Adds cache tags associated with the processed text. * * @param array $cache_tags * The cache tags to be added. * * @return $this */ public function addCacheTags(array $cache_tags) { $this->cacheTags = drupal_merge_attached($this->cacheTags, $cache_tags); return $this; }
/** * Tests justs JavaScript and JavaScript setting asset merging. */ function testJsSettingMerging() { $a['#attached'] = array('js' => array('foo.js' => array(), 'bar.js' => array()), 'drupalSettings' => ['foo' => ['d']]); $b['#attached'] = array('js' => array('baz.js' => array()), 'drupalSettings' => ['bar' => ['a', 'b', 'c']]); $expected['#attached'] = array('js' => array('foo.js' => array(), 'bar.js' => array(), 'baz.js' => array()), 'drupalSettings' => ['foo' => ['d'], 'bar' => ['a', 'b', 'c']]); $this->assertIdentical($expected['#attached'], drupal_merge_attached($a['#attached'], $b['#attached']), 'Attachments merged correctly.'); // Merging in the opposite direction yields the opposite JS setting asset // order. $expected['#attached'] = array('js' => array('baz.js' => array(), 'foo.js' => array(), 'bar.js' => array()), 'drupalSettings' => ['bar' => ['a', 'b', 'c'], 'foo' => ['d']]); $this->assertIdentical($expected['#attached'], drupal_merge_attached($b['#attached'], $a['#attached']), 'Attachments merged correctly; opposite merging yields opposite order.'); // Merging with duplicates (simple case). $b['#attached']['drupalSettings']['foo'] = ['a', 'b', 'c']; $expected['#attached'] = array('js' => array('foo.js' => array(), 'bar.js' => array(), 'baz.js' => array()), 'drupalSettings' => ['foo' => ['a', 'b', 'c'], 'bar' => ['a', 'b', 'c']]); $this->assertIdentical($expected['#attached'], drupal_merge_attached($a['#attached'], $b['#attached'])); // Merging with duplicates (simple case) in the opposite direction yields // the opposite JS setting asset order, but also opposite overriding order. $expected['#attached'] = array('js' => array('baz.js' => array(), 'foo.js' => array(), 'bar.js' => array()), 'drupalSettings' => ['bar' => ['a', 'b', 'c'], 'foo' => ['d', 'b', 'c']]); $this->assertIdentical($expected['#attached'], drupal_merge_attached($b['#attached'], $a['#attached'])); // Merging with duplicates: complex case. // Only the second of these two entries should appear in drupalSettings. $build = array(); $build['a']['#attached']['drupalSettings']['commonTest'] = 'firstValue'; $build['b']['#attached']['drupalSettings']['commonTest'] = 'secondValue'; // Only the second of these entries should appear in drupalSettings. $build['a']['#attached']['drupalSettings']['commonTestJsArrayLiteral'] = ['firstValue']; $build['b']['#attached']['drupalSettings']['commonTestJsArrayLiteral'] = ['secondValue']; // Only the second of these two entries should appear in drupalSettings. $build['a']['#attached']['drupalSettings']['commonTestJsObjectLiteral'] = ['key' => 'firstValue']; $build['b']['#attached']['drupalSettings']['commonTestJsObjectLiteral'] = ['key' => 'secondValue']; // Real world test case: multiple elements in a render array are adding the // same (or nearly the same) JavaScript settings. When merged, they should // contain all settings and not duplicate some settings. $settings_one = array('moduleName' => array('ui' => array('button A', 'button B'), 'magical flag' => 3.14159265359)); $build['a']['#attached']['drupalSettings']['commonTestRealWorldIdentical'] = $settings_one; $build['b']['#attached']['drupalSettings']['commonTestRealWorldIdentical'] = $settings_one; $settings_two_a = array('moduleName' => array('ui' => array('button A', 'button B', 'button C'), 'magical flag' => 3.14159265359, 'thingiesOnPage' => array('id1' => array()))); $build['a']['#attached']['drupalSettings']['commonTestRealWorldAlmostIdentical'] = $settings_two_a; $settings_two_b = array('moduleName' => array('ui' => array('button D', 'button E'), 'magical flag' => 3.14, 'thingiesOnPage' => array('id2' => array()))); $build['b']['#attached']['drupalSettings']['commonTestRealWorldAlmostIdentical'] = $settings_two_b; $merged = drupal_merge_attached($build['a']['#attached'], $build['b']['#attached']); // Test whether #attached can be used to override a previous setting. $this->assertIdentical('secondValue', $merged['drupalSettings']['commonTest']); // Test whether #attached can be used to add and override a JavaScript // array literal (an indexed PHP array) values. $this->assertIdentical('secondValue', $merged['drupalSettings']['commonTestJsArrayLiteral'][0]); // Test whether #attached can be used to add and override a JavaScript // object literal (an associate PHP array) values. $this->assertIdentical('secondValue', $merged['drupalSettings']['commonTestJsObjectLiteral']['key']); // Test whether the two real world cases are handled correctly: the first // adds the exact same settings twice and hence tests idempotency, the // second adds *almost* the same settings twice: the second time, some // values are altered, and some key-value pairs are added. $settings_two['moduleName']['thingiesOnPage']['id1'] = array(); $this->assertIdentical($settings_one, $merged['drupalSettings']['commonTestRealWorldIdentical']); $expected_settings_two = $settings_two_a; $expected_settings_two['moduleName']['ui'][0] = 'button D'; $expected_settings_two['moduleName']['ui'][1] = 'button E'; $expected_settings_two['moduleName']['ui'][2] = 'button C'; $expected_settings_two['moduleName']['magical flag'] = 3.14; $expected_settings_two['moduleName']['thingiesOnPage']['id2'] = []; $this->assertIdentical($expected_settings_two, $merged['drupalSettings']['commonTestRealWorldAlmostIdentical']); }
/** * Tests justs JavaScript and JavaScript setting asset merging. */ function testJsSettingMerging() { $a['#attached'] = array('js' => array('foo.js' => array(), array('type' => 'setting', 'data' => array('foo' => array('d'))), 'bar.js' => array())); $b['#attached'] = array('js' => array(83 => array('type' => 'setting', 'data' => array('bar' => array('a', 'b', 'c'))), 'baz.js' => array())); $expected['#attached'] = array('js' => array('foo.js' => array(), 0 => array('type' => 'setting', 'data' => array('foo' => array('d'))), 'bar.js' => array(), 1 => array('type' => 'setting', 'data' => array('bar' => array('a', 'b', 'c'))), 'baz.js' => array())); $this->assertIdentical($expected['#attached'], drupal_merge_attached($a['#attached'], $b['#attached']), 'Attachments merged correctly.'); // Merging in the opposite direction yields the opposite JS setting asset // order. $expected['#attached'] = array('js' => array(0 => array('type' => 'setting', 'data' => array('bar' => array('a', 'b', 'c'))), 'baz.js' => array(), 'foo.js' => array(), 1 => array('type' => 'setting', 'data' => array('foo' => array('d'))), 'bar.js' => array())); $this->assertIdentical($expected['#attached'], drupal_merge_attached($b['#attached'], $a['#attached']), 'Attachments merged correctly; opposite merging yields opposite order.'); // Merging with duplicates: JavaScript setting duplicates are simply // retained, it's up to the rest of the system (drupal_merge_js_settings()) // to handle duplicates. $b['#attached']['js'][] = array('type' => 'setting', 'data' => array('foo' => array('a', 'b', 'c'))); $expected['#attached'] = array('js' => array('foo.js' => array(), 0 => array('type' => 'setting', 'data' => array('foo' => array('d'))), 'bar.js' => array(), 1 => array('type' => 'setting', 'data' => array('bar' => array('a', 'b', 'c'))), 'baz.js' => array(), 2 => array('type' => 'setting', 'data' => array('foo' => array('a', 'b', 'c'))))); $this->assertIdentical($expected['#attached'], drupal_merge_attached($a['#attached'], $b['#attached']), 'Attachments merged correctly; JavaScript asset duplicates removed, JavaScript setting asset duplicates retained.'); }