1<?php 2 3/* 4 * This file is part of the TYPO3 CMS project. 5 * 6 * It is free software; you can redistribute it and/or modify it under 7 * the terms of the GNU General Public License, either version 2 8 * of the License, or any later version. 9 * 10 * For the full copyright and license information, please read the 11 * LICENSE.txt file that was distributed with this source code. 12 * 13 * The TYPO3 project - inspiring people to share! 14 */ 15 16namespace TYPO3\CMS\Extbase\Property; 17 18/** 19 * Concrete configuration object for the PropertyMapper. 20 */ 21class PropertyMappingConfiguration implements PropertyMappingConfigurationInterface 22{ 23 /** 24 * Placeholder in property paths for multi-valued types 25 */ 26 const PROPERTY_PATH_PLACEHOLDER = '*'; 27 28 /** 29 * multi-dimensional array which stores type-converter specific configuration: 30 * 1. Dimension: Fully qualified class name of the type converter 31 * 2. Dimension: Configuration Key 32 * Value: Configuration Value 33 * 34 * @var array 35 */ 36 protected $configuration; 37 38 /** 39 * Stores the configuration for specific child properties. 40 * 41 * @var \TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface[] 42 */ 43 protected $subConfigurationForProperty = []; 44 45 /** 46 * Keys which should be renamed 47 * 48 * @var array 49 */ 50 protected $mapping = []; 51 52 /** 53 * @var \TYPO3\CMS\Extbase\Property\TypeConverterInterface 54 */ 55 protected $typeConverter; 56 57 /** 58 * List of allowed property names to be converted 59 * 60 * @var array 61 */ 62 protected $propertiesToBeMapped = []; 63 64 /** 65 * List of property names to be skipped during property mapping 66 * 67 * @var array 68 */ 69 protected $propertiesToSkip = []; 70 71 /** 72 * List of disallowed property names which will be ignored while property mapping 73 * 74 * @var array 75 */ 76 protected $propertiesNotToBeMapped = []; 77 78 /** 79 * If TRUE, unknown properties will be skipped during property mapping 80 * 81 * @var bool 82 */ 83 protected $skipUnknownProperties = false; 84 85 /** 86 * If TRUE, unknown properties will be mapped. 87 * 88 * @var bool 89 */ 90 protected $mapUnknownProperties = false; 91 92 /** 93 * The behavior is as follows: 94 * 95 * - if a property has been explicitly forbidden using allowAllPropertiesExcept(...), it is directly rejected 96 * - if a property has been allowed using allowProperties(...), it is directly allowed. 97 * - if allowAllProperties* has been called, we allow unknown properties 98 * - else, return FALSE. 99 * 100 * @param string $propertyName 101 * @return bool TRUE if the given propertyName should be mapped, FALSE otherwise. 102 */ 103 public function shouldMap($propertyName) 104 { 105 if (isset($this->propertiesNotToBeMapped[$propertyName])) { 106 return false; 107 } 108 109 if (isset($this->propertiesToBeMapped[$propertyName])) { 110 return true; 111 } 112 113 if (isset($this->subConfigurationForProperty[self::PROPERTY_PATH_PLACEHOLDER])) { 114 return true; 115 } 116 117 return $this->mapUnknownProperties; 118 } 119 120 /** 121 * Check if the given $propertyName should be skipped during mapping. 122 * 123 * @param string $propertyName 124 * @return bool 125 */ 126 public function shouldSkip($propertyName) 127 { 128 return isset($this->propertiesToSkip[$propertyName]); 129 } 130 131 /** 132 * Allow all properties in property mapping, even unknown ones. 133 * 134 * @return \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration this 135 */ 136 public function allowAllProperties() 137 { 138 $this->mapUnknownProperties = true; 139 return $this; 140 } 141 142 /** 143 * Allow a list of specific properties. All arguments of 144 * allowProperties are used here (varargs). 145 * 146 * Example: allowProperties('title', 'content', 'author') 147 * 148 * @return \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration 149 */ 150 public function allowProperties() 151 { 152 foreach (func_get_args() as $propertyName) { 153 $this->propertiesToBeMapped[$propertyName] = $propertyName; 154 } 155 return $this; 156 } 157 158 /** 159 * Skip a list of specific properties. All arguments of 160 * skipProperties are used here (varargs). 161 * 162 * Example: skipProperties('unused', 'dummy') 163 * 164 * @return \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration this 165 */ 166 public function skipProperties() 167 { 168 foreach (func_get_args() as $propertyName) { 169 $this->propertiesToSkip[$propertyName] = $propertyName; 170 } 171 return $this; 172 } 173 174 /** 175 * Allow all properties during property mapping, but reject a few 176 * selected ones (blacklist). 177 * 178 * Example: allowAllPropertiesExcept('password', 'userGroup') 179 * 180 * @return \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration this 181 */ 182 public function allowAllPropertiesExcept() 183 { 184 $this->mapUnknownProperties = true; 185 186 foreach (func_get_args() as $propertyName) { 187 $this->propertiesNotToBeMapped[$propertyName] = $propertyName; 188 } 189 return $this; 190 } 191 192 /** 193 * When this is enabled, properties that are disallowed will be skipped 194 * instead of triggering an error during mapping. 195 * 196 * @return \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration this 197 */ 198 public function skipUnknownProperties() 199 { 200 $this->skipUnknownProperties = true; 201 return $this; 202 } 203 204 /** 205 * Whether unknown (unconfigured) properties should be skipped during 206 * mapping, instead if causing an error. 207 * 208 * @return bool 209 */ 210 public function shouldSkipUnknownProperties() 211 { 212 return $this->skipUnknownProperties; 213 } 214 215 /** 216 * Returns the sub-configuration for the passed $propertyName. Must ALWAYS return a valid configuration object! 217 * 218 * @param string $propertyName 219 * @return \TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface the property mapping configuration for the given $propertyName. 220 */ 221 public function getConfigurationFor($propertyName) 222 { 223 if (isset($this->subConfigurationForProperty[$propertyName])) { 224 return $this->subConfigurationForProperty[$propertyName]; 225 } 226 if (isset($this->subConfigurationForProperty[self::PROPERTY_PATH_PLACEHOLDER])) { 227 return $this->subConfigurationForProperty[self::PROPERTY_PATH_PLACEHOLDER]; 228 } 229 230 return new self(); 231 } 232 233 /** 234 * Maps the given $sourcePropertyName to a target property name. 235 * 236 * @param string $sourcePropertyName 237 * @return string property name of target 238 */ 239 public function getTargetPropertyName($sourcePropertyName) 240 { 241 if (isset($this->mapping[$sourcePropertyName])) { 242 return $this->mapping[$sourcePropertyName]; 243 } 244 return $sourcePropertyName; 245 } 246 247 /** 248 * @param string $typeConverterClassName 249 * @param string $key 250 * @return mixed configuration value for the specific $typeConverterClassName. Can be used by Type Converters to fetch converter-specific configuration. 251 */ 252 public function getConfigurationValue($typeConverterClassName, $key) 253 { 254 if (!isset($this->configuration[$typeConverterClassName][$key])) { 255 return null; 256 } 257 258 return $this->configuration[$typeConverterClassName][$key]; 259 } 260 261 /** 262 * Define renaming from Source to Target property. 263 * 264 * @param string $sourcePropertyName 265 * @param string $targetPropertyName 266 * @return \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration this 267 */ 268 public function setMapping($sourcePropertyName, $targetPropertyName) 269 { 270 $this->mapping[$sourcePropertyName] = $targetPropertyName; 271 return $this; 272 } 273 274 /** 275 * Set all options for the given $typeConverter. 276 * 277 * @param string $typeConverter class name of type converter 278 * @param array $options 279 * @return \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration this 280 */ 281 public function setTypeConverterOptions($typeConverter, array $options) 282 { 283 foreach ($this->getTypeConvertersWithParentClasses($typeConverter) as $typeConverter) { 284 $this->configuration[$typeConverter] = $options; 285 } 286 return $this; 287 } 288 289 /** 290 * Set a single option (denoted by $optionKey) for the given $typeConverter. 291 * 292 * @param string $typeConverter class name of type converter 293 * @param string $optionKey 294 * @param mixed $optionValue 295 * @return \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration this 296 */ 297 public function setTypeConverterOption($typeConverter, $optionKey, $optionValue) 298 { 299 foreach ($this->getTypeConvertersWithParentClasses($typeConverter) as $typeConverter) { 300 $this->configuration[$typeConverter][$optionKey] = $optionValue; 301 } 302 return $this; 303 } 304 305 /** 306 * Get type converter classes including parents for the given type converter 307 * 308 * When setting an option on a subclassed type converter, this option must also be set on 309 * all its parent type converters. 310 * 311 * @param string $typeConverter The type converter class 312 * @return array Class names of type converters 313 */ 314 protected function getTypeConvertersWithParentClasses($typeConverter) 315 { 316 $typeConverterClasses = class_parents($typeConverter); 317 $typeConverterClasses = $typeConverterClasses ?: []; 318 $typeConverterClasses[] = $typeConverter; 319 return $typeConverterClasses; 320 } 321 322 /** 323 * Returns the configuration for the specific property path, ready to be modified. Should be used 324 * inside a fluent interface like: 325 * $configuration->forProperty('foo.bar')->setTypeConverterOption(....) 326 * 327 * @param string $propertyPath 328 * @return \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration (or a subclass thereof) 329 */ 330 public function forProperty($propertyPath) 331 { 332 $splittedPropertyPath = explode('.', $propertyPath); 333 return $this->traverseProperties($splittedPropertyPath); 334 } 335 336 /** 337 * Traverse the property configuration. Only used by forProperty(). 338 * 339 * @param array $splittedPropertyPath 340 * @return \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration (or a subclass thereof) 341 */ 342 public function traverseProperties(array $splittedPropertyPath) 343 { 344 if (empty($splittedPropertyPath)) { 345 return $this; 346 } 347 348 $currentProperty = array_shift($splittedPropertyPath); 349 if (!isset($this->subConfigurationForProperty[$currentProperty])) { 350 $type = static::class; 351 if (isset($this->subConfigurationForProperty[self::PROPERTY_PATH_PLACEHOLDER])) { 352 $this->subConfigurationForProperty[$currentProperty] = clone $this->subConfigurationForProperty[self::PROPERTY_PATH_PLACEHOLDER]; 353 } else { 354 $this->subConfigurationForProperty[$currentProperty] = new $type(); 355 } 356 } 357 return $this->subConfigurationForProperty[$currentProperty]->traverseProperties($splittedPropertyPath); 358 } 359 360 /** 361 * Return the type converter set for this configuration. 362 * 363 * @return \TYPO3\CMS\Extbase\Property\TypeConverterInterface|null 364 */ 365 public function getTypeConverter() 366 { 367 return $this->typeConverter; 368 } 369 370 /** 371 * Set a type converter which should be used for this specific conversion. 372 * 373 * @param \TYPO3\CMS\Extbase\Property\TypeConverterInterface $typeConverter 374 * @return \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration this 375 */ 376 public function setTypeConverter(TypeConverterInterface $typeConverter) 377 { 378 $this->typeConverter = $typeConverter; 379 return $this; 380 } 381} 382