Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\Core\Render;
|
Chris@0
|
4
|
Chris@0
|
5 use Drupal\Component\Utility\NestedArray;
|
Chris@0
|
6 use Drupal\Core\Cache\CacheableMetadata;
|
Chris@0
|
7
|
Chris@0
|
8 /**
|
Chris@0
|
9 * Value object used for bubbleable rendering metadata.
|
Chris@0
|
10 *
|
Chris@0
|
11 * @see \Drupal\Core\Render\RendererInterface::render()
|
Chris@0
|
12 */
|
Chris@0
|
13 class BubbleableMetadata extends CacheableMetadata implements AttachmentsInterface {
|
Chris@0
|
14
|
Chris@0
|
15 use AttachmentsTrait;
|
Chris@0
|
16
|
Chris@0
|
17 /**
|
Chris@0
|
18 * Merges the values of another bubbleable metadata object with this one.
|
Chris@0
|
19 *
|
Chris@0
|
20 * @param \Drupal\Core\Cache\CacheableMetadata $other
|
Chris@0
|
21 * The other bubbleable metadata object.
|
Chris@0
|
22 *
|
Chris@0
|
23 * @return static
|
Chris@0
|
24 * A new bubbleable metadata object, with the merged data.
|
Chris@0
|
25 */
|
Chris@0
|
26 public function merge(CacheableMetadata $other) {
|
Chris@0
|
27 $result = parent::merge($other);
|
Chris@0
|
28
|
Chris@0
|
29 // This is called many times per request, so avoid merging unless absolutely
|
Chris@0
|
30 // necessary.
|
Chris@0
|
31 if ($other instanceof BubbleableMetadata) {
|
Chris@0
|
32 if (empty($this->attachments)) {
|
Chris@0
|
33 $result->attachments = $other->attachments;
|
Chris@0
|
34 }
|
Chris@0
|
35 elseif (empty($other->attachments)) {
|
Chris@0
|
36 $result->attachments = $this->attachments;
|
Chris@0
|
37 }
|
Chris@0
|
38 else {
|
Chris@0
|
39 $result->attachments = static::mergeAttachments($this->attachments, $other->attachments);
|
Chris@0
|
40 }
|
Chris@0
|
41 }
|
Chris@0
|
42
|
Chris@0
|
43 return $result;
|
Chris@0
|
44 }
|
Chris@0
|
45
|
Chris@0
|
46 /**
|
Chris@0
|
47 * Applies the values of this bubbleable metadata object to a render array.
|
Chris@0
|
48 *
|
Chris@0
|
49 * @param array &$build
|
Chris@0
|
50 * A render array.
|
Chris@0
|
51 */
|
Chris@0
|
52 public function applyTo(array &$build) {
|
Chris@0
|
53 parent::applyTo($build);
|
Chris@0
|
54 $build['#attached'] = $this->attachments;
|
Chris@0
|
55 }
|
Chris@0
|
56
|
Chris@0
|
57 /**
|
Chris@0
|
58 * Creates a bubbleable metadata object with values taken from a render array.
|
Chris@0
|
59 *
|
Chris@0
|
60 * @param array $build
|
Chris@0
|
61 * A render array.
|
Chris@0
|
62 *
|
Chris@0
|
63 * @return static
|
Chris@0
|
64 */
|
Chris@0
|
65 public static function createFromRenderArray(array $build) {
|
Chris@0
|
66 $meta = parent::createFromRenderArray($build);
|
Chris@0
|
67 $meta->attachments = (isset($build['#attached'])) ? $build['#attached'] : [];
|
Chris@0
|
68 return $meta;
|
Chris@0
|
69 }
|
Chris@0
|
70
|
Chris@0
|
71 /**
|
Chris@0
|
72 * Creates a bubbleable metadata object from a depended object.
|
Chris@0
|
73 *
|
Chris@0
|
74 * @param \Drupal\Core\Cache\CacheableDependencyInterface|mixed $object
|
Chris@0
|
75 * The object whose cacheability metadata to retrieve. If it implements
|
Chris@0
|
76 * CacheableDependencyInterface, its cacheability metadata will be used,
|
Chris@0
|
77 * otherwise, the passed in object must be assumed to be uncacheable, so
|
Chris@0
|
78 * max-age 0 is set.
|
Chris@0
|
79 *
|
Chris@0
|
80 * @return static
|
Chris@0
|
81 */
|
Chris@0
|
82 public static function createFromObject($object) {
|
Chris@0
|
83 $meta = parent::createFromObject($object);
|
Chris@0
|
84
|
Chris@0
|
85 if ($object instanceof AttachmentsInterface) {
|
Chris@0
|
86 $meta->attachments = $object->getAttachments();
|
Chris@0
|
87 }
|
Chris@0
|
88
|
Chris@0
|
89 return $meta;
|
Chris@0
|
90 }
|
Chris@0
|
91
|
Chris@0
|
92 /**
|
Chris@0
|
93 * {@inheritdoc}
|
Chris@0
|
94 */
|
Chris@0
|
95 public function addCacheableDependency($other_object) {
|
Chris@0
|
96 parent::addCacheableDependency($other_object);
|
Chris@0
|
97
|
Chris@0
|
98 if ($other_object instanceof AttachmentsInterface) {
|
Chris@0
|
99 $this->addAttachments($other_object->getAttachments());
|
Chris@0
|
100 }
|
Chris@0
|
101
|
Chris@0
|
102 return $this;
|
Chris@0
|
103 }
|
Chris@0
|
104
|
Chris@0
|
105 /**
|
Chris@0
|
106 * Merges two attachments arrays (which live under the '#attached' key).
|
Chris@0
|
107 *
|
Chris@0
|
108 * The values under the 'drupalSettings' key are merged in a special way, to
|
Chris@0
|
109 * match the behavior of:
|
Chris@0
|
110 *
|
Chris@0
|
111 * @code
|
Chris@0
|
112 * jQuery.extend(true, {}, $settings_items[0], $settings_items[1], ...)
|
Chris@0
|
113 * @endcode
|
Chris@0
|
114 *
|
Chris@0
|
115 * This means integer indices are preserved just like string indices are,
|
Chris@0
|
116 * rather than re-indexed as is common in PHP array merging.
|
Chris@0
|
117 *
|
Chris@0
|
118 * Example:
|
Chris@0
|
119 * @code
|
Chris@0
|
120 * function module1_page_attachments(&$page) {
|
Chris@0
|
121 * $page['a']['#attached']['drupalSettings']['foo'] = ['a', 'b', 'c'];
|
Chris@0
|
122 * }
|
Chris@0
|
123 * function module2_page_attachments(&$page) {
|
Chris@0
|
124 * $page['#attached']['drupalSettings']['foo'] = ['d'];
|
Chris@0
|
125 * }
|
Chris@0
|
126 * // When the page is rendered after the above code, and the browser runs the
|
Chris@0
|
127 * // resulting <SCRIPT> tags, the value of drupalSettings.foo is
|
Chris@0
|
128 * // ['d', 'b', 'c'], not ['a', 'b', 'c', 'd'].
|
Chris@0
|
129 * @endcode
|
Chris@0
|
130 *
|
Chris@0
|
131 * By following jQuery.extend() merge logic rather than common PHP array merge
|
Chris@0
|
132 * logic, the following are ensured:
|
Chris@0
|
133 * - Attaching JavaScript settings is idempotent: attaching the same settings
|
Chris@0
|
134 * twice does not change the output sent to the browser.
|
Chris@0
|
135 * - If pieces of the page are rendered in separate PHP requests and the
|
Chris@0
|
136 * returned settings are merged by JavaScript, the resulting settings are
|
Chris@0
|
137 * the same as if rendered in one PHP request and merged by PHP.
|
Chris@0
|
138 *
|
Chris@0
|
139 * @param array $a
|
Chris@0
|
140 * An attachments array.
|
Chris@0
|
141 * @param array $b
|
Chris@0
|
142 * Another attachments array.
|
Chris@0
|
143 *
|
Chris@0
|
144 * @return array
|
Chris@0
|
145 * The merged attachments array.
|
Chris@0
|
146 */
|
Chris@0
|
147 public static function mergeAttachments(array $a, array $b) {
|
Chris@0
|
148 // If both #attached arrays contain drupalSettings, then merge them
|
Chris@0
|
149 // correctly; adding the same settings multiple times needs to behave
|
Chris@0
|
150 // idempotently.
|
Chris@0
|
151 if (!empty($a['drupalSettings']) && !empty($b['drupalSettings'])) {
|
Chris@0
|
152 $drupalSettings = NestedArray::mergeDeepArray([$a['drupalSettings'], $b['drupalSettings']], TRUE);
|
Chris@0
|
153 // No need for re-merging them.
|
Chris@0
|
154 unset($a['drupalSettings']);
|
Chris@0
|
155 unset($b['drupalSettings']);
|
Chris@0
|
156 }
|
Chris@0
|
157 // Optimize merging of placeholders: no need for deep merging.
|
Chris@0
|
158 if (!empty($a['placeholders']) && !empty($b['placeholders'])) {
|
Chris@0
|
159 $placeholders = $a['placeholders'] + $b['placeholders'];
|
Chris@0
|
160 // No need for re-merging them.
|
Chris@0
|
161 unset($a['placeholders']);
|
Chris@0
|
162 unset($b['placeholders']);
|
Chris@0
|
163 }
|
Chris@0
|
164 // Apply the normal merge.
|
Chris@0
|
165 $a = array_merge_recursive($a, $b);
|
Chris@0
|
166 if (isset($drupalSettings)) {
|
Chris@0
|
167 // Save the custom merge for the drupalSettings.
|
Chris@0
|
168 $a['drupalSettings'] = $drupalSettings;
|
Chris@0
|
169 }
|
Chris@0
|
170 if (isset($placeholders)) {
|
Chris@0
|
171 // Save the custom merge for the placeholders.
|
Chris@0
|
172 $a['placeholders'] = $placeholders;
|
Chris@0
|
173 }
|
Chris@0
|
174 return $a;
|
Chris@0
|
175 }
|
Chris@0
|
176
|
Chris@0
|
177 }
|