1<?php 2namespace TYPO3\CMS\Core\DataHandling\Localization; 3 4/* 5 * This file is part of the TYPO3 CMS project. 6 * 7 * It is free software; you can redistribute it and/or modify it under 8 * the terms of the GNU General Public License, either version 2 9 * of the License, or any later version. 10 * 11 * For the full copyright and license information, please read the 12 * LICENSE.txt file that was distributed with this source code. 13 * 14 * The TYPO3 project - inspiring people to share! 15 */ 16 17use TYPO3\CMS\Core\Utility\GeneralUtility; 18 19/** 20 * Value object for l10n_state field value. 21 */ 22class State 23{ 24 const STATE_CUSTOM = 'custom'; 25 const STATE_PARENT = 'parent'; 26 const STATE_SOURCE = 'source'; 27 28 /** 29 * @param string $tableName 30 * @return State|null 31 */ 32 public static function create(string $tableName) 33 { 34 if (!static::isApplicable($tableName)) { 35 return null; 36 } 37 38 return GeneralUtility::makeInstance( 39 static::class, 40 $tableName 41 ); 42 } 43 44 /** 45 * @param string $tableName 46 * @param string|null $json 47 * @return State|null 48 */ 49 public static function fromJSON(string $tableName, string $json = null) 50 { 51 if (!static::isApplicable($tableName)) { 52 return null; 53 } 54 55 $states = json_decode($json ?? '', true); 56 return GeneralUtility::makeInstance( 57 static::class, 58 $tableName, 59 $states ?? [] 60 ); 61 } 62 63 /** 64 * @param string $tableName 65 * @return bool 66 */ 67 public static function isApplicable(string $tableName) 68 { 69 return 70 static::hasColumns($tableName) 71 && static::hasLanguageFieldName($tableName) 72 && static::hasTranslationParentFieldName($tableName) 73 && count(static::getFieldNames($tableName)) > 0 74 ; 75 } 76 77 /** 78 * @param string $tableName 79 * @return array 80 */ 81 public static function getFieldNames(string $tableName) 82 { 83 return array_keys( 84 array_filter( 85 $GLOBALS['TCA'][$tableName]['columns'] ?? [], 86 function (array $fieldConfiguration) { 87 return !empty( 88 $fieldConfiguration['config'] 89 ['behaviour']['allowLanguageSynchronization'] 90 ); 91 } 92 ) 93 ); 94 } 95 96 /** 97 * @param string $tableName 98 * @return bool 99 */ 100 protected static function hasColumns(string $tableName) 101 { 102 return 103 !empty($GLOBALS['TCA'][$tableName]['columns']) 104 && is_array($GLOBALS['TCA'][$tableName]['columns']) 105 ; 106 } 107 108 /** 109 * @param string $tableName 110 * @return bool 111 */ 112 protected static function hasLanguageFieldName(string $tableName) 113 { 114 return !empty($GLOBALS['TCA'][$tableName]['ctrl']['languageField']); 115 } 116 117 /** 118 * @param string $tableName 119 * @return bool 120 */ 121 protected static function hasTranslationParentFieldName(string $tableName) 122 { 123 return !empty($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']); 124 } 125 126 /** 127 * @var string 128 */ 129 protected $tableName; 130 131 /** 132 * @var array 133 */ 134 protected $states; 135 136 /** 137 * @var array 138 */ 139 protected $originalStates; 140 141 /** 142 * @var array 143 */ 144 protected $validStates = [ 145 self::STATE_CUSTOM, 146 self::STATE_SOURCE, 147 self::STATE_PARENT, 148 ]; 149 150 /** 151 * @param string $tableName 152 * @param array $states 153 */ 154 public function __construct(string $tableName, array $states = []) 155 { 156 $this->tableName = $tableName; 157 $this->states = $states; 158 $this->originalStates = $states; 159 160 $this->states = $this->enrich( 161 $this->sanitize($states) 162 ); 163 } 164 165 /** 166 * @param array $states 167 */ 168 public function update(array $states) 169 { 170 $this->states = array_merge( 171 $this->states, 172 $this->sanitize($states) 173 ); 174 } 175 176 /** 177 * Updates field names having a particular state to a target state. 178 * 179 * @param string $currentState 180 * @param string $targetState 181 */ 182 public function updateStates(string $currentState, string $targetState) 183 { 184 $states = []; 185 foreach ($this->filterFieldNames($currentState) as $fieldName) { 186 $states[$fieldName] = $targetState; 187 } 188 if (!empty($states)) { 189 $this->update($states); 190 } 191 } 192 193 /** 194 * @return string|null 195 */ 196 public function export() 197 { 198 if (empty($this->states)) { 199 return null; 200 } 201 return json_encode($this->states); 202 } 203 204 /** 205 * @return array 206 */ 207 public function toArray(): array 208 { 209 return $this->states ?? []; 210 } 211 212 /** 213 * @return string[] 214 */ 215 public function getModifiedFieldNames() 216 { 217 return array_keys( 218 array_diff_assoc( 219 $this->states, 220 $this->originalStates 221 ) 222 ); 223 } 224 225 /** 226 * @return bool 227 */ 228 public function isModified() 229 { 230 return !empty($this->getModifiedFieldNames()); 231 } 232 233 /** 234 * @param string $fieldName 235 * @return bool 236 */ 237 public function isUndefined(string $fieldName) 238 { 239 return !isset($this->states[$fieldName]); 240 } 241 242 /** 243 * @param string $fieldName 244 * @return bool 245 */ 246 public function isCustomState(string $fieldName) 247 { 248 return ($this->states[$fieldName] ?? null) === static::STATE_CUSTOM; 249 } 250 251 /** 252 * @param string $fieldName 253 * @return bool 254 */ 255 public function isParentState(string $fieldName) 256 { 257 return ($this->states[$fieldName] ?? null) === static::STATE_PARENT; 258 } 259 260 /** 261 * @param string $fieldName 262 * @return bool 263 */ 264 public function isSourceState(string $fieldName) 265 { 266 return ($this->states[$fieldName] ?? null) === static::STATE_SOURCE; 267 } 268 269 /** 270 * @param string $fieldName 271 * @return string|null 272 */ 273 public function getState(string $fieldName) 274 { 275 return $this->states[$fieldName] ?? null; 276 } 277 278 /** 279 * Filters field names having a desired state. 280 * 281 * @param string $desiredState 282 * @param bool $modified 283 * @return string[] 284 */ 285 public function filterFieldNames(string $desiredState, bool $modified = false) 286 { 287 if (!$modified) { 288 $fieldNames = array_keys($this->states); 289 } else { 290 $fieldNames = $this->getModifiedFieldNames(); 291 } 292 return array_filter( 293 $fieldNames, 294 function ($fieldName) use ($desiredState) { 295 return $this->states[$fieldName] === $desiredState; 296 } 297 ); 298 } 299 300 /** 301 * Filter out field names that don't exist in TCA. 302 * 303 * @param array $states 304 * @return array 305 */ 306 protected function sanitize(array $states) 307 { 308 $fieldNames = static::getFieldNames($this->tableName); 309 return array_intersect_key( 310 $states, 311 array_combine($fieldNames, $fieldNames) 312 ); 313 } 314 315 /** 316 * Add missing states for field names. 317 * 318 * @param array $states 319 * @return array 320 */ 321 protected function enrich(array $states) 322 { 323 foreach (static::getFieldNames($this->tableName) as $fieldName) { 324 $isValid = in_array( 325 $states[$fieldName] ?? null, 326 $this->validStates, 327 true 328 ); 329 if ($isValid) { 330 continue; 331 } 332 $states[$fieldName] = static::STATE_PARENT; 333 } 334 return $states; 335 } 336} 337