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