9a09b5c4cb9e8957c4e537fff1b0e1d36e13913c
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / MetaTag / AbstractMetaTagManager.php
1 <?php
2 declare(strict_types = 1);
3
4 namespace TYPO3\CMS\Core\MetaTag;
5
6 /*
7 * This file is part of the TYPO3 CMS project.
8 *
9 * It is free software; you can redistribute it and/or modify it under
10 * the terms of the GNU General Public License, either version 2
11 * of the License, or any later version.
12 *
13 * For the full copyright and license information, please read the
14 * LICENSE.txt file that was distributed with this source code.
15 *
16 * The TYPO3 project - inspiring people to share!
17 */
18
19 use TYPO3\CMS\Core\SingletonInterface;
20
21 abstract class AbstractMetaTagManager implements MetaTagManagerInterface, SingletonInterface
22 {
23 /**
24 * The default attribute that defines the name of the property
25 *
26 * This creates tags like <meta name="" /> by default
27 *
28 * @var string
29 */
30 protected $defaultNameAttribute = 'name';
31
32 /**
33 * The default attribute that defines the content
34 *
35 * This creates tags like <meta content="" /> by default
36 *
37 * @var string
38 */
39 protected $defaultContentAttribute = 'content';
40
41 /**
42 * Set if by default it is possible to have multiple occurrences of properties of this manager
43 *
44 * @var bool
45 */
46 protected $defaultAllowMultipleOccurrences = false;
47
48 /**
49 * The separator to define subproperties like og:image:width
50 *
51 * @var string
52 */
53 protected $subPropertySeparator = ':';
54
55 /**
56 * Array of properties that can be handled by this manager
57 *
58 * Example:
59 *
60 * $handledProperties = [
61 * 'og:title' => [],
62 * 'og:image' => [
63 * 'allowMultipleOccurrences' => true,
64 * 'allowedSubProperties' => [
65 * 'url',
66 * 'secure_url',
67 * 'type',
68 * 'width',
69 * 'height',
70 * 'alt'
71 * ]
72 * ],
73 * 'og:locale' => [
74 * 'allowedSubProperties' => [
75 * 'alternate' => [
76 * 'allowMultipleOccurrences' => true
77 * ]
78 * ]
79 * ]
80 *];
81 *
82 * @var array
83 */
84 protected $handledProperties = [];
85
86 /**
87 * Array of properties that are set by the manager
88 *
89 * @var array
90 */
91 protected $properties = [];
92
93 /**
94 * Add a property
95 *
96 * @param string $property Name of the property
97 * @param string $content Content of the property
98 * @param array $subProperties Optional subproperties
99 * @param bool $replace Replace the currently set value
100 * @param string $type Optional type of property (name, property, http-equiv)
101 *
102 * @throws \UnexpectedValueException
103 */
104 public function addProperty(string $property, string $content, array $subProperties = [], bool $replace = false, string $type = '')
105 {
106 $property = strtolower($property);
107
108 if (isset($this->handledProperties[$property])) {
109 $subPropertiesArray = [];
110 foreach ($subProperties as $subPropertyKey => $subPropertyValue) {
111 if (isset($this->handledProperties[$property]['allowedSubProperties'][$subPropertyKey])) {
112 $subPropertiesArray[$subPropertyKey] = is_array($subPropertyValue) ? $subPropertyValue : [$subPropertyValue];
113 }
114 }
115 if (!isset($this->properties[$property]) || empty($this->properties[$property])) {
116 $this->properties[$property][] = ['content' => $content, 'subProperties' => $subPropertiesArray];
117 } else {
118 if ($replace === true) {
119 $this->removeProperty($property, $type);
120 $this->properties[$property][] = ['content' => $content, 'subProperties' => $subPropertiesArray];
121 return;
122 }
123
124 if (isset($this->handledProperties[$property]['allowMultipleOccurrences']) &&
125 (bool)$this->handledProperties[$property]['allowMultipleOccurrences']
126 ) {
127 $this->properties[$property][] = ['content' => $content, 'subProperties' => $subPropertiesArray];
128 }
129 }
130 } else {
131 // Check if there is an allowed subproperty that can handle the given property
132 foreach ($this->handledProperties as $handledProperty => $handledPropertyConfig) {
133 if (!isset($handledPropertyConfig['allowedSubProperties'])) {
134 continue;
135 }
136 foreach ((array)$handledPropertyConfig['allowedSubProperties'] as $allowedSubProperty => $allowedSubPropertyConfig) {
137 $propertyKey = is_array($allowedSubPropertyConfig) ? $allowedSubProperty : $allowedSubPropertyConfig;
138
139 if ($property !== $handledProperty . $this->subPropertySeparator . $propertyKey ||
140 !isset($this->properties[$handledProperty])
141 ) {
142 continue;
143 }
144
145 $propertyArrayKeys = array_keys($this->properties[$handledProperty]);
146 $lastIndex = end($propertyArrayKeys);
147
148 if (!isset($this->properties[$handledProperty][$lastIndex]['subProperties'][$propertyKey])) {
149 $this->properties[$handledProperty][$lastIndex]['subProperties'][$propertyKey][] = $content;
150 } else {
151 if ($replace === true) {
152 unset($this->properties[$handledProperty][$lastIndex]['subProperties'][$propertyKey]);
153 $this->properties[$handledProperty][$lastIndex]['subProperties'][$propertyKey][] = $content;
154 return;
155 }
156
157 if (is_array($allowedSubPropertyConfig) &&
158 isset($allowedSubPropertyConfig['allowMultipleOccurrences']) &&
159 (bool)$allowedSubPropertyConfig['allowMultipleOccurrences']
160 ) {
161 $this->properties[$handledProperty][$lastIndex]['subProperties'][$propertyKey][] = $content;
162 }
163 }
164
165 return;
166 }
167 }
168
169 throw new \UnexpectedValueException(
170 sprintf('This MetaTagManager can\'t handle property "%s"', $property),
171 1524209729
172 );
173 }
174 }
175
176 /**
177 * Returns an array with all properties that can be handled by this manager
178 *
179 * @return array
180 */
181 public function getAllHandledProperties(): array
182 {
183 return $this->handledProperties;
184 }
185
186 /**
187 * Get a specific property that is set before
188 *
189 * @param string $property Name of the property
190 * @param string $type Optional type of property (name, property, http-equiv)
191 * @return array
192 */
193 public function getProperty(string $property, string $type = ''): array
194 {
195 $property = strtolower($property);
196
197 if (isset($this->properties[$property])) {
198 return $this->properties[$property];
199 }
200
201 return [];
202 }
203
204 /**
205 * Render a meta tag for a specific property
206 *
207 * @param string $property Name of the property
208 * @return string
209 */
210 public function renderProperty(string $property): string
211 {
212 $property = strtolower($property);
213 $metaTags = [];
214
215 $nameAttribute = $this->defaultNameAttribute;
216 if (isset($this->handledProperties[$property]['nameAttribute'])
217 && !empty((string)$this->handledProperties[$property]['nameAttribute'])) {
218 $nameAttribute = (string)$this->handledProperties[$property]['nameAttribute'];
219 }
220
221 $contentAttribute = $this->defaultContentAttribute;
222 if (isset($this->handledProperties[$property]['contentAttribute'])
223 && !empty((string)$this->handledProperties[$property]['contentAttribute'])) {
224 $contentAttribute = (string)$this->handledProperties[$property]['contentAttribute'];
225 }
226
227 if ($nameAttribute && $contentAttribute) {
228 foreach ($this->getProperty($property) as $propertyItem) {
229 $metaTags[] = '<meta ' .
230 htmlspecialchars($nameAttribute) . '="' . htmlspecialchars($property) . '" ' .
231 htmlspecialchars($contentAttribute) . '="' . htmlspecialchars($propertyItem['content']) . '" />';
232
233 if (!count($propertyItem['subProperties'])) {
234 continue;
235 }
236 foreach ($propertyItem['subProperties'] as $subProperty => $subPropertyItems) {
237 foreach ($subPropertyItems as $subPropertyItem) {
238 $metaTags[] = '<meta ' .
239 htmlspecialchars($nameAttribute) . '="' . htmlspecialchars($property . $this->subPropertySeparator . $subProperty) . '" ' .
240 htmlspecialchars($contentAttribute) . '="' . htmlspecialchars((string)$subPropertyItem) . '" />';
241 }
242 }
243 }
244 }
245
246 return implode(PHP_EOL, $metaTags);
247 }
248
249 /**
250 * Render all registered properties of this manager
251 *
252 * @return string
253 */
254 public function renderAllProperties(): string
255 {
256 $metatags = [];
257 foreach (array_keys($this->properties) as $property) {
258 $metatags[] = $this->renderProperty($property);
259 }
260
261 return implode(PHP_EOL, $metatags);
262 }
263
264 /**
265 * Remove one property from the MetaTagManager
266 * If there are multiple occurrences of a property, they all will be removed
267 *
268 * @param string $property
269 * @param string $type
270 */
271 public function removeProperty(string $property, string $type = '')
272 {
273 $property = strtolower($property);
274
275 unset($this->properties[$property]);
276 }
277
278 /**
279 * Unset all properties of this MetaTagManager
280 */
281 public function removeAllProperties()
282 {
283 $this->properties = [];
284 }
285
286 /**
287 * Check if this manager can handle the given property
288 *
289 * @param string $property Name of property to check (eg. og:title)
290 * @return bool
291 */
292 public function canHandleProperty(string $property): bool
293 {
294 if (isset($this->handledProperties[$property])) {
295 return true;
296 }
297
298 foreach ($this->handledProperties as $handledProperty => $handledPropertyConfig) {
299 foreach ((array)$handledPropertyConfig['allowedSubProperties'] as $allowedSubProperty => $allowedSubPropertyConfig) {
300 $propertyKey = is_array($allowedSubPropertyConfig) ? $allowedSubProperty : $allowedSubPropertyConfig;
301 if ($property === $handledProperty . $this->subPropertySeparator . $propertyKey) {
302 return true;
303 }
304 }
305 }
306
307 return false;
308 }
309 }