‪TYPO3CMS  ‪main
FormSlugAjaxController.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Psr\Http\Message\ResponseInterface;
21 use Psr\Http\Message\ServerRequestInterface;
23 use TYPO3\CMS\Backend\Utility\BackendUtility;
31 
37 #[AsController]
39 {
40  public function ‪__construct(
41  private ‪Context $context,
42  private readonly ‪HashService $hashService
43  ) {}
44 
71  public function ‪suggestAction(ServerRequestInterface $request): ResponseInterface
72  {
73  $this->‪checkRequest($request);
74 
75  $queryParameters = $request->getParsedBody() ?? [];
76  $values = $queryParameters['values'];
77  $mode = $queryParameters['mode'];
78  $tableName = (string)($queryParameters['tableName'] ?? '');
79  $pid = (int)$queryParameters['pageId'];
80  $parentPageId = (int)$queryParameters['parentPageId'];
81  $recordId = (int)$queryParameters['recordId'];
82  $languageId = (int)$queryParameters['language'];
83  $fieldName = $queryParameters['fieldName'];
84 
85  $fieldConfig = ‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'] ?? [];
86  $row = (array)BackendUtility::getRecord($tableName, $recordId);
87  $recordType = BackendUtility::getTCAtypeValue($tableName, $row);
88  $columnsOverridesConfigOfField = ‪$GLOBALS['TCA'][$tableName]['types'][$recordType]['columnsOverrides'][$fieldName]['config'] ?? null;
89  if ($columnsOverridesConfigOfField) {
90  ArrayUtility::mergeRecursiveWithOverrule($fieldConfig, $columnsOverridesConfigOfField);
91  }
92  if (empty($fieldConfig)) {
93  throw new \RuntimeException(
94  'No valid field configuration for table ' . $tableName . ' field name ' . $fieldName . ' found.',
95  1535379534
96  );
97  }
98 
99  $evalInfo = !empty($fieldConfig['eval']) ? ‪GeneralUtility::trimExplode(',', $fieldConfig['eval'], true) : [];
100  $hasToBeUniqueInDb = in_array('unique', $evalInfo, true);
101  $hasToBeUniqueInSite = in_array('uniqueInSite', $evalInfo, true);
102  $hasToBeUniqueInPid = in_array('uniqueInPid', $evalInfo, true);
103 
104  $hasConflict = false;
105 
106  $recordData = $values;
107  if (!isset($recordData['uid'])) {
108  $recordData['uid'] = $recordId;
109  }
110  $recordData['pid'] = $pid;
111  if (!empty(‪$GLOBALS['TCA'][$tableName]['ctrl']['languageField'])) {
112  $recordData[‪$GLOBALS['TCA'][$tableName]['ctrl']['languageField']] = $languageId;
113  }
114  if ($tableName === 'pages' && empty($recordData['is_siteroot'])) {
115  $recordData['is_siteroot'] = $row['is_siteroot'] ?? false;
116  }
117 
118  $workspaceId = $this->context->getPropertyFromAspect('workspace', 'id');
119  $slug = GeneralUtility::makeInstance(SlugHelper::class, $tableName, $fieldName, $fieldConfig, $workspaceId);
120  if ($mode === 'auto') {
121  // New page - Feed incoming values to generator
122  $proposal = $slug->generate($recordData, $pid);
123  } elseif ($mode === 'recreate') {
124  $proposal = $slug->generate($recordData, $parentPageId);
125  } elseif ($mode === 'manual') {
126  // Existing record - Fetch full record and only validate against the new "slug" field.
127  $proposal = $slug->sanitize($values['manual']);
128  } else {
129  throw new \RuntimeException('mode must be either "auto", "recreate" or "manual"', 1535835666);
130  }
131 
132  $state = ‪RecordStateFactory::forName($tableName)
133  ->fromArray($recordData, $pid, $recordId);
134  if ($hasToBeUniqueInDb && !$slug->isUniqueInTable($proposal, $state)) {
135  $hasConflict = true;
136  $proposal = $slug->buildSlugForUniqueInTable($proposal, $state);
137  }
138  if ($hasToBeUniqueInSite && !$slug->isUniqueInSite($proposal, $state)) {
139  $hasConflict = true;
140  $proposal = $slug->buildSlugForUniqueInSite($proposal, $state);
141  }
142  if ($hasToBeUniqueInPid && !$slug->isUniqueInPid($proposal, $state)) {
143  $hasConflict = true;
144  $proposal = $slug->buildSlugForUniqueInPid($proposal, $state);
145  }
146 
147  return new ‪JsonResponse([
148  'hasConflicts' => $hasConflict,
149  'manual' => $values['manual'] ?? '',
150  'proposal' => $proposal,
151  ]);
152  }
153 
157  protected function ‪checkRequest(ServerRequestInterface $request): bool
158  {
159  $queryParameters = $request->getParsedBody() ?? [];
160  $expectedHash = $this->hashService->hmac(
161  implode(
162  '',
163  [
164  $queryParameters['tableName'],
165  $queryParameters['pageId'],
166  $queryParameters['recordId'],
167  $queryParameters['language'],
168  $queryParameters['fieldName'],
169  $queryParameters['command'],
170  $queryParameters['parentPageId'],
171  ]
172  ),
173  __CLASS__
174  );
175  if (!hash_equals($expectedHash, $queryParameters['signature'])) {
176  throw new \InvalidArgumentException(
177  'HMAC could not be verified',
178  1535137045
179  );
180  }
181  return true;
182  }
183 }
‪TYPO3\CMS\Backend\Controller\FormSlugAjaxController\checkRequest
‪checkRequest(ServerRequestInterface $request)
Definition: FormSlugAjaxController.php:157
‪TYPO3\CMS\Backend\Controller\FormSlugAjaxController\__construct
‪__construct(private Context $context, private readonly HashService $hashService)
Definition: FormSlugAjaxController.php:40
‪TYPO3\CMS\Core\Context\Context
Definition: Context.php:54
‪TYPO3\CMS\Core\DataHandling\Model\RecordStateFactory
Definition: RecordStateFactory.php:26
‪TYPO3\CMS\Core\DataHandling\SlugHelper
Definition: SlugHelper.php:43
‪TYPO3\CMS\Backend\Controller\FormSlugAjaxController\suggestAction
‪suggestAction(ServerRequestInterface $request)
Definition: FormSlugAjaxController.php:71
‪TYPO3\CMS\Core\Utility\ArrayUtility
Definition: ArrayUtility.php:26
‪TYPO3\CMS\Core\Http\JsonResponse
Definition: JsonResponse.php:28
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Backend\Attribute\AsController
Definition: AsController.php:25
‪TYPO3\CMS\Backend\Controller\AbstractFormEngineAjaxController
Definition: AbstractFormEngineAjaxController.php:36
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Crypto\HashService
Definition: HashService.php:27
‪TYPO3\CMS\Backend\Controller
Definition: AboutController.php:18
‪TYPO3\CMS\Backend\Controller\FormSlugAjaxController
Definition: FormSlugAjaxController.php:39
‪TYPO3\CMS\Core\DataHandling\Model\RecordStateFactory\forName
‪static forName(string $name)
Definition: RecordStateFactory.php:29
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode(string $delim, string $string, bool $removeEmptyValues=false, int $limit=0)
Definition: GeneralUtility.php:822