‪TYPO3CMS  ‪main
InlineControlContainer.php
Go to the documentation of this file.
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 
17 
19 use TYPO3\CMS\Backend\Utility\BackendUtility;
21 use TYPO3\CMS\Core\Imaging\IconSize;
26 
39 {
45  protected ‪$inlineData = [];
46 
50  protected ‪$javaScriptModules = [];
51 
57  protected ‪$defaultFieldInformation = [
58  'tcaDescription' => [
59  'renderType' => 'tcaDescription',
60  ],
61  ];
62 
66  protected ‪$defaultFieldWizard = [
67  'localizationStateSelector' => [
68  'renderType' => 'localizationStateSelector',
69  ],
70  ];
71 
72  public function ‪__construct(
73  private readonly ‪IconFactory $iconFactory,
74  private readonly ‪InlineStackProcessor $inlineStackProcessor,
75  ) {}
76 
82  public function ‪render(): array
83  {
84  $languageService = $this->‪getLanguageService();
85 
86  $this->inlineData = $this->data['inlineData'];
87 
88  $this->inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
89 
90  $table = $this->data['tableName'];
91  $row = $this->data['databaseRow'];
92  $field = $this->data['fieldName'];
93  $parameterArray = $this->data['parameterArray'];
94 
95  $resultArray = $this->‪initializeResultArray();
96 
97  $config = $parameterArray['fieldConf']['config'];
98  $foreign_table = $config['foreign_table'];
99  $isReadOnly = isset($config['readOnly']) && $config['readOnly'];
100  $language = 0;
101  if (BackendUtility::isTableLocalizable($table)) {
102  $languageFieldName = ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'] ?? '';
103  $language = isset($row[$languageFieldName][0]) ? (int)$row[$languageFieldName][0] : (int)($row[$languageFieldName] ?? 0);
104  }
105 
106  // Add the current inline job to the structure stack
107  $newStructureItem = [
108  'table' => $table,
109  'uid' => $row['uid'],
110  'field' => $field,
111  'config' => $config,
112  ];
113  // Extract FlexForm parts (if any) from element name, e.g. array('vDEF', 'lDEF', 'FlexField', 'vDEF')
114  if (!empty($parameterArray['itemFormElName'])) {
115  $flexFormParts = $this->‪extractFlexFormParts($parameterArray['itemFormElName']);
116  if ($flexFormParts !== null) {
117  $newStructureItem['flexform'] = $flexFormParts;
118  }
119  }
120  $this->inlineStackProcessor->pushStableStructureItem($newStructureItem);
121 
122  // Transport the flexform DS identifier fields to the FormInlineAjaxController
123  if (!empty($newStructureItem['flexform'])
124  && isset($this->data['processedTca']['columns'][$field]['config']['dataStructureIdentifier'])
125  ) {
126  $config['dataStructureIdentifier'] = $this->data['processedTca']['columns'][$field]['config']['dataStructureIdentifier'];
127  }
128 
129  // Hand over original returnUrl to FormInlineAjaxController. Needed if opening for instance a
130  // nested element in a new view to then go back to the original returnUrl and not the url of
131  // the inline ajax controller
132  $config['originalReturnUrl'] = $this->data['returnUrl'];
133 
134  // e.g. data[<table>][<uid>][<field>]
135  $nameForm = $this->inlineStackProcessor->getCurrentStructureFormPrefix();
136  // e.g. data-<pid>-<table1>-<uid1>-<field1>-<table2>-<uid2>-<field2>
137  $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
138 
139  $config['inline']['first'] = false;
140  $firstChild = reset($this->data['parameterArray']['fieldConf']['children']);
141  if (isset($firstChild['databaseRow']['uid'])) {
142  $config['inline']['first'] = $firstChild['databaseRow']['uid'];
143  }
144  $config['inline']['last'] = false;
145  $lastChild = end($this->data['parameterArray']['fieldConf']['children']);
146  if (isset($lastChild['databaseRow']['uid'])) {
147  $config['inline']['last'] = $lastChild['databaseRow']['uid'];
148  }
149 
150  $top = $this->inlineStackProcessor->getStructureLevel(0);
151 
152  $this->inlineData['config'][$nameObject] = [
153  'table' => $foreign_table,
154  ];
155  $configJson = (string)json_encode($config);
156  $this->inlineData['config'][$nameObject . '-' . $foreign_table] = [
157  'min' => $config['minitems'],
158  'max' => $config['maxitems'],
159  'sortable' => $config['appearance']['useSortable'] ?? false,
160  'top' => [
161  'table' => $top['table'],
162  'uid' => $top['uid'],
163  ],
164  'context' => [
165  'config' => $configJson,
166  'hmac' => ‪GeneralUtility::hmac($configJson, 'InlineContext'),
167  ],
168  ];
169  $this->inlineData['nested'][$nameObject] = $this->data['tabAndInlineStack'];
170 
171  $uniqueMax = 0;
172  $uniqueIds = [];
173 
174  if ($config['foreign_unique'] ?? false) {
175  // Add inlineData['unique'] with JS unique configuration
176  // @todo: Improve validation and throw an exception if type is neither select nor group here
177  $type = ($config['selectorOrUniqueConfiguration']['config']['type'] ?? '') === 'select' ? 'select' : 'groupdb';
178  foreach ($parameterArray['fieldConf']['children'] as $child) {
179  // Determine used unique ids, skip not localized records
180  if (!$child['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
181  $value = $child['databaseRow'][$config['foreign_unique']];
182  // We're assuming there is only one connected value here for both select and group
183  if ($type === 'select') {
184  // A select field is an array of uids. See TcaSelectItems data provider for details.
185  // Pick first entry, ends up as eg. $value = 42.
186  $value = $value['0'] ?? [];
187  } else {
188  // A group field is an array of arrays containing uid + table + title + row.
189  // See TcaGroup data provider for details.
190  // Pick the first one (always on 0), and use uid + table only. Exclude title + row
191  // since the entire inlineData['unique'] array ends up in JavaScript in the end
192  // and we don't need and want the title and the entire row data in the frontend.
193  // Ends up as $value = [ 'uid' => '42', 'table' => 'tx_my_table' ]
194  $value = [
195  'uid' => $value[0]['uid'],
196  'table' => $value[0]['table'],
197  ];
198  }
199  // Note structure of $value is different in select vs. group: It's a uid for select, but an
200  // array with uid + table for group.
201  if (isset($child['databaseRow']['uid'])) {
202  $uniqueIds[$child['databaseRow']['uid']] = $value;
203  }
204  }
205  }
206  $possibleRecords = $config['selectorOrUniquePossibleRecords'] ?? [];
207  $possibleRecordsUidToTitle = [];
208  foreach ($possibleRecords as $possibleRecord) {
209  $possibleRecordsUidToTitle[$possibleRecord['value']] = $possibleRecord['label'];
210  }
211  $uniqueMax = ($config['appearance']['useCombination'] ?? false) || empty($possibleRecords) ? -1 : count($possibleRecords);
212  $this->inlineData['unique'][$nameObject . '-' . $foreign_table] = [
213  'max' => $uniqueMax,
214  'used' => $uniqueIds,
215  'type' => $type,
216  'table' => $foreign_table,
217  'elTable' => $config['selectorOrUniqueConfiguration']['foreignTable'] ?? '',
218  'field' => $config['foreign_unique'] ?? '',
219  'selector' => ($config['selectorOrUniqueConfiguration']['isSelector'] ?? false) ? $type : false,
220  'possible' => $possibleRecordsUidToTitle,
221  ];
222  }
223 
224  $resultArray['inlineData'] = ‪$this->inlineData;
225 
226  // @todo: It might be a good idea to have something like "isLocalizedRecord" or similar set by a data provider
227  $uidOfDefaultRecord = $row[‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] ?? null] ?? 0;
228  $isLocalizedParent = $language > 0
229  && ($uidOfDefaultRecord[0] ?? $uidOfDefaultRecord) > 0
231  $numberOfFullLocalizedChildren = 0;
232  $numberOfNotYetLocalizedChildren = 0;
233  foreach ($this->data['parameterArray']['fieldConf']['children'] as $child) {
234  if (!$child['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
235  $numberOfFullLocalizedChildren++;
236  }
237  if ($isLocalizedParent && $child['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
238  $numberOfNotYetLocalizedChildren++;
239  }
240  }
241 
242  // Render the localization buttons if needed
243  $localizationButtons = '';
244  if ($numberOfNotYetLocalizedChildren) {
245  // Add the "Localize all records" button before all child records:
246  if (!empty($config['appearance']['showAllLocalizationLink'])) {
247  $localizationButtons = ' ' . $this->‪getLevelInteractionButton('localize', $config);
248  }
249  // Add the "Synchronize with default language" button before all child records:
250  if (!empty($config['appearance']['showSynchronizationLink'])) {
251  $localizationButtons .= ' ' . $this->‪getLevelInteractionButton('synchronize', $config);
252  }
253  }
254 
255  // Define how to show the "Create new record" button - if there are more than maxitems, hide it
256  if ($isReadOnly || $numberOfFullLocalizedChildren >= ($config['maxitems'] ?? 0) || ($uniqueMax > 0 && $numberOfFullLocalizedChildren >= $uniqueMax)) {
257  $config['inline']['inlineNewButtonStyle'] = 'display: none;';
258  }
259 
260  // Render the "new record" level button:
261  $newRecordButton = '';
262  // For b/w compatibility, "showNewRecordLink" - in contrast to the other show* options - defaults to TRUE
263  if (!isset($config['appearance']['showNewRecordLink']) || $config['appearance']['showNewRecordLink']) {
264  $newRecordButton = $this->‪getLevelInteractionButton('newRecord', $config);
265  }
266 
267  $formGroupAttributes = [
268  'class' => 'form-group',
269  'id' => $nameObject,
270  'data-uid' => (string)$row['uid'],
271  'data-local-table' => (string)$top['table'],
272  'data-local-field' => (string)$top['field'],
273  'data-foreign-table' => (string)$foreign_table,
274  'data-object-group' => $nameObject . '-' . $foreign_table,
275  'data-form-field' => $nameForm,
276  'data-appearance' => (string)json_encode($config['appearance'] ?? ''),
277  ];
278 
279  // Wrap all inline fields of a record with a <div> (like a container)
280  $html = '<div ' . GeneralUtility::implodeAttributes($formGroupAttributes, true) . '>';
281 
282  $fieldInformationResult = $this->‪renderFieldInformation();
283  $html .= $fieldInformationResult['html'];
284  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
285 
286  // Add the level buttons before all child records:
287  if (in_array($config['appearance']['levelLinksPosition'] ?? null, ['both', 'top'], true)) {
288  $html .= '<div class="form-group t3js-formengine-validation-marker t3js-inline-controls-top-outer-container">' . $newRecordButton . $localizationButtons . '</div>';
289  }
290 
291  // If it's required to select from possible child records (reusable children), add a selector box
292  if (!$isReadOnly && ($config['foreign_selector'] ?? false) && ($config['appearance']['showPossibleRecordsSelector'] ?? true) !== false) {
293  if (($config['selectorOrUniqueConfiguration']['config']['type'] ?? false) === 'select') {
294  $selectorBox = $this->‪renderPossibleRecordsSelectorTypeSelect($config, $uniqueIds);
295  } else {
296  $selectorBox = $this->‪renderPossibleRecordsSelectorTypeGroupDB($config);
297  }
298  $html .= $selectorBox . $localizationButtons;
299  }
300 
301  $title = $languageService->sL(trim($parameterArray['fieldConf']['label'] ?? ''));
302  $html .= '<div class="panel-group panel-hover" data-title="' . htmlspecialchars($title) . '" id="' . $nameObject . '_records">';
303 
304  $sortableRecordUids = [];
305  foreach ($this->data['parameterArray']['fieldConf']['children'] as $options) {
306  $options['inlineParentUid'] = $row['uid'];
307  $options['inlineFirstPid'] = $this->data['inlineFirstPid'];
308  // @todo: this can be removed if this container no longer sets additional info to $config
309  $options['inlineParentConfig'] = $config;
310  $options['inlineData'] = ‪$this->inlineData;
311  $options['inlineStructure'] = $this->inlineStackProcessor->getStructure();
312  $options['inlineExpandCollapseStateArray'] = $this->data['inlineExpandCollapseStateArray'];
313  $options['renderType'] = 'inlineRecordContainer';
314  $childResult = $this->nodeFactory->create($options)->render();
315  $html .= $childResult['html'];
316  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $childResult, false);
317  if (!$options['isInlineDefaultLanguageRecordInLocalizedParentContext'] && isset($options['databaseRow']['uid'])) {
318  // Don't add record to list of "valid" uids if it is only the default
319  // language record of a not yet localized child
320  $sortableRecordUids[] = $options['databaseRow']['uid'];
321  }
322  }
323 
324  $html .= '</div>';
325 
326  $fieldWizardResult = $this->‪renderFieldWizard();
327  $fieldWizardHtml = $fieldWizardResult['html'];
328  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
329  $html .= $fieldWizardHtml;
330 
331  // Add the level buttons after all child records:
332  if (!$isReadOnly && in_array($config['appearance']['levelLinksPosition'] ?? false, ['both', 'bottom'], true)) {
333  $html .= $newRecordButton . $localizationButtons;
334  }
335  if (is_array($config['customControls'] ?? false)) {
336  $html .= '<div id="' . $nameObject . '_customControls">';
337  foreach ($config['customControls'] as $customControlConfig) {
338  if (!isset($customControlConfig['userFunc'])) {
339  throw new \RuntimeException('Support for customControl without a userFunc key in TCA type inline is not supported.', 1548052629);
340  }
341  $parameters = [
342  'table' => $table,
343  'field' => $field,
344  'row' => $row,
345  'nameObject' => $nameObject,
346  'nameForm' => $nameForm,
347  'config' => $config,
348  'customControlConfig' => $customControlConfig,
349  // Warning: By reference should be used with care here and exists mostly to allow additional $resultArray['javaScriptModules']
350  'resultArray' => &$resultArray,
351  ];
352  $html .= GeneralUtility::callUserFunction($customControlConfig['userFunc'], $parameters, $this);
353  }
354  $html .= '</div>';
355  }
356  $resultArray['javaScriptModules'] = array_merge($resultArray['javaScriptModules'], $this->javaScriptModules);
357  $resultArray['javaScriptModules'][] = ‪JavaScriptModuleInstruction::create(
358  '@typo3/backend/form-engine/container/inline-control-container.js'
359  )->instance($nameObject);
360 
361  // Publish the uids of the child records in the given order to the browser
362  $html .= '<input type="hidden" name="' . $nameForm . '" value="' . implode(',', $sortableRecordUids) . '" '
363  . ' data-formengine-validation-rules="'
364  . htmlspecialchars($this->‪getValidationDataAsJsonString([
365  'type' => 'inline',
366  'minitems' => $config['minitems'] ?? null,
367  'maxitems' => $config['maxitems'] ?? null,
368  ]))
369  . '"'
370  . ' class="inlineRecord" />';
371  // Close the wrap for all inline fields (container)
372  $html .= '</div>';
373 
374  $resultArray['html'] = $this->‪wrapWithFieldsetAndLegend($html);
375  return $resultArray;
376  }
377 
386  protected function ‪getLevelInteractionButton(string $type, array $conf = []): string
387  {
388  $languageService = $this->‪getLanguageService();
389  $attributes = [];
390  switch ($type) {
391  case 'newRecord':
392  $title = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.createnew'));
393  $icon = 'actions-plus';
394  $className = 'typo3-newRecordLink t3js-inline-controls';
395  $attributes['class'] = 'btn btn-default t3js-create-new-button';
396  if (!empty($conf['inline']['inlineNewButtonStyle'])) {
397  $attributes['style'] = $conf['inline']['inlineNewButtonStyle'];
398  }
399  if (!empty($conf['appearance']['newRecordLinkAddTitle'])) {
400  $title = htmlspecialchars(sprintf(
401  $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.createnew.link'),
402  $languageService->sL(‪$GLOBALS['TCA'][$conf['foreign_table']]['ctrl']['title'])
403  ));
404  } elseif (isset($conf['appearance']['newRecordLinkTitle']) && $conf['appearance']['newRecordLinkTitle'] !== '') {
405  $title = htmlspecialchars($languageService->sL($conf['appearance']['newRecordLinkTitle']));
406  }
407  break;
408  case 'localize':
409  $title = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:localizeAllRecords'));
410  $icon = 'actions-document-localize';
411  $className = 'typo3-localizationLink';
412  $attributes['class'] = 'btn btn-default t3js-synchronizelocalize-button';
413  $attributes['data-type'] = 'localize';
414  break;
415  case 'synchronize':
416  $title = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:synchronizeWithOriginalLanguage'));
417  $icon = 'actions-document-synchronize';
418  $className = 'typo3-synchronizationLink';
419  $attributes['class'] = 'btn btn-default inlineNewButton t3js-synchronizelocalize-button';
420  $attributes['data-type'] = 'synchronize';
421  break;
422  default:
423  $title = '';
424  $icon = '';
425  $className = '';
426  }
427  // Create the button:
428  $icon = $icon ? $this->iconFactory->getIcon($icon, IconSize::SMALL)->render() : '';
429  $button = $this->‪wrapWithButton($icon . ' ' . $title, $attributes);
430  return '<div' . ($className ? ' class="' . $className . '"' : '') . 'title="' . $title . '">' . $button . '</div>';
431  }
432 
440  protected function ‪wrapWithButton(string $text, array $attributes = []): string
441  {
442  return '<button type="button" ' . GeneralUtility::implodeAttributes($attributes, true, true) . '>' . $text . '</button>';
443  }
444 
452  protected function ‪renderPossibleRecordsSelectorTypeGroupDB(array $inlineConfiguration): string
453  {
454  $languageService = $this->‪getLanguageService();
455  $groupFieldConfiguration = $inlineConfiguration['selectorOrUniqueConfiguration']['config'];
456  $objectPrefix = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']) . '-' . $inlineConfiguration['foreign_table'];
457  $elementBrowserEnabled = true;
458  if (is_array($groupFieldConfiguration['appearance'] ?? null)
459  && isset($inlineConfiguration['appearance']['elementBrowserEnabled'])
460  ) {
461  $elementBrowserEnabled = (bool)$inlineConfiguration['appearance']['elementBrowserEnabled'];
462  }
463  // Remove any white-spaces from the allowed extension lists
464  $allowed = ‪GeneralUtility::trimExplode(',', (string)($groupFieldConfiguration['allowed'] ?? ''), true);
465  $buttonStyle = '';
466  if (isset($inlineConfiguration['inline']['inlineNewRelationButtonStyle'])) {
467  $buttonStyle = ' style="' . $inlineConfiguration['inline']['inlineNewRelationButtonStyle'] . '"';
468  }
469  $item = '';
470  if ($elementBrowserEnabled) {
471  if (!empty($inlineConfiguration['appearance']['createNewRelationLinkTitle'])) {
472  $createNewRelationText = htmlspecialchars($languageService->sL($inlineConfiguration['appearance']['createNewRelationLinkTitle']));
473  } else {
474  $createNewRelationText = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.createNewRelation'));
475  }
476  $item .= '
477  <button type="button" class="btn btn-default t3js-element-browser" data-mode="db" data-params="' . htmlspecialchars('|||' . implode(',', $allowed) . '|' . $objectPrefix) . '"
478  ' . $buttonStyle . ' title="' . $createNewRelationText . '">
479  ' . $this->iconFactory->getIcon('actions-insert-record', IconSize::SMALL)->render() . '
480  ' . $createNewRelationText . '
481  </button>';
482  }
483  $item = '<div class="form-control-wrap t3js-inline-controls">' . $item . '</div>';
484  if (!empty($allowed)) {
485  $item .= '
486  <div class="form-text">
487  ' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.allowedRelations')) . '
488  <br>
489  ' . implode(' ', array_map(static fn(string $item): string => '<span class="badge badge-success">' . strtoupper($item) . '</span>', $allowed)) . '
490  </div>';
491  }
492  return '<div class="form-group t3js-formengine-validation-marker t3js-inline-controls-top-outer-container">' . $item . '</div>';
493  }
494 
503  protected function ‪renderPossibleRecordsSelectorTypeSelect(array $config, array $uniqueIds)
504  {
505  $config += [
506  'autoSizeMax' => 0,
507  'foreign_table' => '',
508  ];
509  $possibleRecords = $config['selectorOrUniquePossibleRecords'];
510  $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
511  // Create option tags:
512  $opt = [];
513  foreach ($possibleRecords as $possibleRecord) {
514  if (!in_array($possibleRecord['value'], $uniqueIds)) {
515  $opt[] = '<option value="' . htmlspecialchars($possibleRecord['value']) . '">' . htmlspecialchars($possibleRecord['label']) . '</option>';
516  }
517  }
518  // Put together the selector box:
519  $size = (int)($config['size'] ?? 0);
520  $autoSizeMax = (int)($config['autoSizeMax'] ?? 0);
521  if ($autoSizeMax > 0) {
522  $size = ‪MathUtility::forceIntegerInRange($size, 1);
523  $size = ‪MathUtility::forceIntegerInRange(count($possibleRecords) + 1, $size, $autoSizeMax);
524  }
525 
526  $item = '
527  <select id="' . $nameObject . '-' . $config['foreign_table'] . '_selector" class="form-select t3js-create-new-selector"' . ($size ? ' size="' . $size . '"' : '') . '>
528  ' . implode('', $opt) . '
529  </select>';
530 
531  if ($size <= 1) {
532  // Add a "Create new relation" button for adding new relations
533  // This is necessary, if the size of the selector is "1" or if
534  // there is only one record item in the select-box, that is selected by default
535  // The selector-box creates a new relation on using an onChange event (see some line above)
536  if (!empty($config['appearance']['createNewRelationLinkTitle'])) {
537  $createNewRelationText = htmlspecialchars($this->‪getLanguageService()->sL($config['appearance']['createNewRelationLinkTitle']));
538  } else {
539  $createNewRelationText = htmlspecialchars($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.createNewRelation'));
540  }
541  $item .= '
542  <button type="button" class="btn btn-default t3js-create-new-button" title="' . $createNewRelationText . '">
543  ' . $this->iconFactory->getIcon('actions-plus', IconSize::SMALL)->render() . $createNewRelationText . '
544  </button>';
545  } else {
546  $item .= '
547  <span class="btn"></span>';
548  }
549 
550  // Wrap the selector and add a spacer to the bottom
551  $item = '<div class="input-group form-group t3js-formengine-validation-marker t3js-inline-controls-top-outer-container">' . $item . '</div>';
552  return $item;
553  }
554 
563  protected function ‪extractFlexFormParts($formElementName)
564  {
565  $flexFormParts = null;
566  $matches = [];
567  if (preg_match('#^data(?:\[[^]]+\]){3}(\[data\](?:\[[^]]+\]){4,})$#', $formElementName, $matches)) {
568  $flexFormParts = ‪GeneralUtility::trimExplode(
569  '][',
570  trim($matches[1], '[]')
571  );
572  }
573  return $flexFormParts;
574  }
575 
576  protected function ‪getLanguageService(): ‪LanguageService
577  {
578  return ‪$GLOBALS['LANG'];
579  }
580 }
‪TYPO3\CMS\Backend\Form\Container\InlineControlContainer\__construct
‪__construct(private readonly IconFactory $iconFactory, private readonly InlineStackProcessor $inlineStackProcessor,)
Definition: InlineControlContainer.php:68
‪TYPO3\CMS\Backend\Form\Container\InlineControlContainer\getLevelInteractionButton
‪string getLevelInteractionButton(string $type, array $conf=[])
Definition: InlineControlContainer.php:382
‪TYPO3\CMS\Backend\Form\Container\InlineControlContainer\render
‪array render()
Definition: InlineControlContainer.php:78
‪TYPO3\CMS\Backend\Form\Container\InlineControlContainer\extractFlexFormParts
‪array null extractFlexFormParts($formElementName)
Definition: InlineControlContainer.php:559
‪TYPO3\CMS\Backend\Form\AbstractNode\mergeChildReturnIntoExistingResult
‪array mergeChildReturnIntoExistingResult(array $existing, array $childReturn, bool $mergeHtml=true)
Definition: AbstractNode.php:104
‪TYPO3\CMS\Backend\Form\Container\InlineControlContainer\getLanguageService
‪getLanguageService()
Definition: InlineControlContainer.php:572
‪TYPO3\CMS\Backend\Form\Container\InlineControlContainer\wrapWithButton
‪string wrapWithButton(string $text, array $attributes=[])
Definition: InlineControlContainer.php:436
‪TYPO3\CMS\Backend\Form\Container\InlineControlContainer
Definition: InlineControlContainer.php:39
‪TYPO3\CMS\Core\Page\JavaScriptModuleInstruction\create
‪static create(string $name, string $exportName=null)
Definition: JavaScriptModuleInstruction.php:47
‪TYPO3\CMS\Backend\Form\Container
Definition: AbstractContainer.php:16
‪TYPO3\CMS\Backend\Form\Container\InlineControlContainer\renderPossibleRecordsSelectorTypeSelect
‪string renderPossibleRecordsSelectorTypeSelect(array $config, array $uniqueIds)
Definition: InlineControlContainer.php:499
‪TYPO3\CMS\Backend\Form\Container\AbstractContainer\wrapWithFieldsetAndLegend
‪wrapWithFieldsetAndLegend(string $fieldContent)
Definition: AbstractContainer.php:140
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:34
‪TYPO3\CMS\Core\Page\JavaScriptModuleInstruction
Definition: JavaScriptModuleInstruction.php:23
‪TYPO3\CMS\Backend\Form\Container\InlineControlContainer\$defaultFieldInformation
‪array $defaultFieldInformation
Definition: InlineControlContainer.php:54
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger(mixed $var)
Definition: MathUtility.php:69
‪TYPO3\CMS\Backend\Form\Container\InlineControlContainer\$javaScriptModules
‪array< int, JavaScriptModuleInstruction > $javaScriptModules
Definition: InlineControlContainer.php:48
‪TYPO3\CMS\Backend\Form\Container\AbstractContainer
Definition: AbstractContainer.php:29
‪TYPO3\CMS\Core\Utility\GeneralUtility\hmac
‪static string hmac($input, $additionalSecret='')
Definition: GeneralUtility.php:474
‪TYPO3\CMS\Backend\Form\Container\AbstractContainer\renderFieldInformation
‪array renderFieldInformation()
Definition: AbstractContainer.php:52
‪TYPO3\CMS\Backend\Form\Container\InlineControlContainer\renderPossibleRecordsSelectorTypeGroupDB
‪string renderPossibleRecordsSelectorTypeGroupDB(array $inlineConfiguration)
Definition: InlineControlContainer.php:448
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Backend\Form\Container\InlineControlContainer\$inlineData
‪array $inlineData
Definition: InlineControlContainer.php:44
‪TYPO3\CMS\Backend\Form\AbstractNode\getValidationDataAsJsonString
‪getValidationDataAsJsonString(array $config)
Definition: AbstractNode.php:133
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:24
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:46
‪TYPO3\CMS\Backend\Form\Container\InlineControlContainer\$defaultFieldWizard
‪array $defaultFieldWizard
Definition: InlineControlContainer.php:62
‪TYPO3\CMS\Backend\Form\InlineStackProcessor
Definition: InlineStackProcessor.php:32
‪TYPO3\CMS\Core\Utility\MathUtility\forceIntegerInRange
‪static int forceIntegerInRange(mixed $theInt, int $min, int $max=2000000000, int $defaultValue=0)
Definition: MathUtility.php:34
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Backend\Form\Container\AbstractContainer\renderFieldWizard
‪array renderFieldWizard()
Definition: AbstractContainer.php:86
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode(string $delim, string $string, bool $removeEmptyValues=false, int $limit=0)
Definition: GeneralUtility.php:817
‪TYPO3\CMS\Backend\Form\AbstractNode\initializeResultArray
‪initializeResultArray()
Definition: AbstractNode.php:77