initializeResultArray(); // There must be only keys that actually exist in result data. $keysNotInResult = array_diff(array_keys($initialData), array_keys($result)); if (!empty($keysNotInResult)) { throw new \InvalidArgumentException( 'Array keys ' . implode(',', $keysNotInResult) . ' do not exist in result array and can not be set', 1440601540 ); } foreach ($initialData as $dataKey => $dataValue) { if ($dataKey === 'command') { // Sanitize $command if ($dataValue !== 'edit' && $dataValue !== 'new') { throw new \InvalidArgumentException('Command must be either "edit" or "new"', 1437653136); } } if ($dataKey === 'tableName') { // Sanitize $tableName if (empty($dataValue)) { throw new \InvalidArgumentException('No $tableName given', 1437654409); } } if ($dataKey === 'vanillaUid') { if ( !MathUtility::canBeInterpretedAsInteger($dataValue) && !(is_string($dataValue) && str_starts_with($dataValue, 'NEW')) ) { throw new \InvalidArgumentException('$vanillaUid is not an integer or "NEW..." string ID', 1437654247); } if (isset($initialData['command']) && $initialData['command'] === 'edit' && $dataValue < 0) { throw new \InvalidArgumentException('Negative $vanillaUid is not supported with $command="edit', 1437654332); } } $result[$dataKey] = $dataValue; } if (!$result['request'] instanceof ServerRequestInterface) { throw new \RuntimeException( 'The current ServerRequestInterface must be provided in key "request"', 1686867720 ); } // Call the data group provider but take care it does not add or remove result keys // This is basically a safety measure against data providers colliding with our array "contract" $resultKeysBeforeFormDataGroup = array_keys($result); $result = $formDataGroup->compile($result); if (!is_array($result)) { throw new \UnexpectedValueException( 'Data group provider must return array', 1446664764 ); } if (!empty($result['renderData'])) { throw new \RuntimeException( 'Array \'renderData\' not empty. Data providers must not add data here', 1485201279 ); } $resultKeysAfterFormDataGroup = array_keys($result); if ($resultKeysAfterFormDataGroup !== $resultKeysBeforeFormDataGroup) { throw new \UnexpectedValueException( 'Data group provider must not change result key list', 1438079402 ); } return $result; } private function initializeResultArray(): array { return [ // Current ServerRequestInterface. Controllers using FormDataCompiler must provide the current // request object, various data providers and nodes rely on this. 'request' => null, // Either "edit" or "new" 'command' => '', // Table name of the handled row 'tableName' => '', // Not changed uid of the record, meaning of value depends on context (new / edit) // * If $command is "edit" // ** $vanillaUid is a positive integer > 0 pointing to the record in the table // * If $command is "new": // ** If $vanillaUid > 0, it is the uid of a page the record should be added at // ** If $vanillaUid < 0, it is the uid of a record in the same table after which the new record should be added // ** If $vanillaUid = 0, a new record is added on page 0 // ** If $vanillaUid = "NEW..." Id of a parent page record if an inline child is added to a not yet persisted page 'vanillaUid' => 0, // Url to return to 'returnUrl' => null, // Title of the handled record. 'recordTitle' => '', // Parent page record is either the full row of the parent page the record is located at or should // be added to, or it is NULL, if a record is added or edited below the root page node. 'parentPageRow' => null, // If a translated page is handled, the page row of the default language (the page against all page checks // are made) is set here 'defaultLanguagePageRow' => null, // Holds the "neighbor" row if incoming vanillaUid is negative and record creation is relative to a row of the same table. 'neighborRow' => null, // For "new" this is the fully initialized row with defaults // The database row. For "edit" workspaceOL() was applied already. // @todo: rename to valueStructure or handledData or similar 'databaseRow' => [], // The "effective" page uid we're working on. This is the uid of a page if a page is edited, or the uid // of the parent page if a page or other record is added, or 0 if a record is added or edited below root node. 'effectivePid' => 0, // Rootline of page the record that is handled is located at as created by BackendUtility::BEgetRootline() 'rootline' => [], // The resolved SiteInterface object of the page or page given record is located on. 'site' => null, // For "edit", this is the permission bitmask of the page that is edited, or of the page a record is located at // For "new", this is the permission bitmask of the page the record is added to // @todo: Remove if not needed on a lower level 'userPermissionOnPage' => 0, // Full user TSconfig 'userTsConfig' => [], // Full page TSconfig of the page that is edited or of the parent page if a record is added. // After type of handled record has been determined, record type specific settings from // [TCEFORM.][tableName.][field.][types.][type.] are merged into [TCEFORM.][tableName.][field.]. // Array keys still contain the concatenation dots. 'pageTsConfig' => [], // List of available system languages. Array key is the system language uid, value array // contains details of the record, with iso code resolved. Key is the sys_language_uid uid. 'systemLanguageRows' => [], // If the page that is handled has "page_language_overlay" records (page has localizations in // different languages), then this array holds those rows. 'pageLanguageOverlayRows' => [], // If the handled row is a localized row, this entry hold the default language row array 'defaultLanguageRow' => null, // If the handled row is a localized row and $TCA[]['ctrl']['translationSource'] is configured, // This entry holds the row of the language source record. 'sourceLanguageRow' => null, // If the handled row is a localized row and a transOrigDiffSourceField is defined, this // is the json_decode() version of it. The diff source field is basically a shadow version // of the default language record at the time when the language overlay record was created. // This is used later to compare the default record with this content to show a "diff" if // the default language record changed meanwhile. 'defaultLanguageDiffRow' => null, // With userTS options.additionalPreviewLanguages set, field values of additional languages // can be shown. This array holds those additional language records, Array key is sys_language_uid. 'additionalLanguageRows' => [], // The tca record type value of the record. Forced to string, there can be "named" type values. 'recordTypeValue' => '', // TCA of table with processed fields. After processing, this array contains merged and resolved // array data, items were resolved, only used types are set, renderTypes are set. 'processedTca' => [], // SchemaCollection of all TCA tables. 'tcaSchemata' => null, // Keeps the full TCA. Needed to reinitialize processedTca for inline form handling. // Why not use $GLOBALS['TCA']? Because we want to get rid of it here and sometimes the TCA needs to be faked. 'fullTca' => null, // List of columns to be processed by data provider. Array value is the column name. 'columnsToProcess' => [], // If set to TRUE, no wizards are calculated and rendered later 'disabledWizards' => false, // Flex form field data handling is done in a separated FormDataCompiler instance. The full databaseRow // of the record this flex form is embedded in is transferred in case features like single fields // itemsProcFunc need to have this data at hand to do their job. 'flexParentDatabaseRow' => [], // If not empty, it tells the TcaFlexProcess data provider to not calculate existing flex fields and // existing flex container sections, but to instead prepare field values and the data structure TCA // for a new container section. This is used by FormFlexAjaxController, the array contains details // which container of which flex field should be created. 'flexSectionContainerPreparation' => [], // If true, TcaSelectTreeItems data provider will compile tree items. This is false by default since // on opening a record items are not calculated but are fetch in an ajax request, see FormSelectTreeAjaxController. 'selectTreeCompileItems' => false, // BackendUser->uc['inlineView'] - This array holds status of expand / collapsed inline items // This array is "flat", an inline structure with parent uid 1 having firstChild uid 2 having secondChild uid 3 // firstChild and secondChild are not nested. If a uid is set it means "record is expanded", example: // 'parent' => [ // 1 => [ // 'firstChild' => [ 2 ], // record 2 of firstChild table is open in inline context to parent 1 // 'secondChild' => [ 3 ], // record 3 of secondChild table is open in inline context to parent 1 // ], // ] 'inlineExpandCollapseStateArray' => [], // The "entry" pid for inline records. Nested inline records can potentially hang around on different // pid's, but the entry pid is needed for AJAX calls, so that they would know where the action takes // place on the page structure. This is usually an int, but can be a "NEW..." string if an inline relation // is added to a currently being created page. 'inlineFirstPid' => null, // The "config" section of an inline parent, prepared and sanitized by TcaInlineConfiguration provider 'inlineParentConfig' => [], // Flag that is enabled if a records is child of an inline parent 'isInlineChild' => false, // Flag if an inline child is expanded so that additional fields need to be rendered 'isInlineChildExpanded' => false, // Flag if the inline is in an ajax context that wants to expand the element 'isInlineAjaxOpeningContext' => false, // Uid of the direct parent of the inline element. Handled as string since it may be a "NEW123" string 'inlineParentUid' => '', // Table name of the direct parent of the inline element 'inlineParentTableName' => '', // Field name of the direct parent of the inline element 'inlineParentFieldName' => '', // Uid of the top most parent element. Handled as string since it may be a "NEW123" string 'inlineTopMostParentUid' => '', // Table name of the top most parent element 'inlineTopMostParentTableName' => '', // Field name of the top most parent element 'inlineTopMostParentFieldName' => '', // If is on symmetric side of an inline child parent reference. // symmetric side can be achieved in case of an mm relation to the same table. If record A has a relation // to record B, the symmetric side is set in case that record B gets edited. // Record A (table1) <=> mm <=> Record B (table1) 'isOnSymmetricSide' => false, // Uid of a "child-child" if a new record of an intermediate table is compiled to an existing child. This // happens if foreign_selector in parent inline config is set. It will be used by default database row // data providers to set this as value for the foreign_selector field on the intermediate table. One use // case is FAL, where for instance a tt_content parent adds relation to an existing sys_file record and // should set the uid of the existing sys_file record as uid_local - the foreign_selector of this inline // configuration - of the new intermediate sys_file_reference record. Data provider that are called later // will then use this relation to resolve for instance input placeholder relation values. 'inlineChildChildUid' => null, // Inline scenario: A localized parent record is handled, so inline parents can have localized children. // This value is set to TRUE if this array represents a default language // child record that was not yet localized. 'isInlineDefaultLanguageRecordInLocalizedParentContext' => false, // If set, inline children will be resolved. This is set to FALSE in inline ajax context where new children // are created and existing children don't matter much. 'inlineResolveExistingChildren' => true, // @todo - for input placeholder inline to suppress an infinite loop, this *may* become obsolete if // @todo compilation of certain fields is possible 'inlineCompileExistingChildren' => true, // @todo: keys below must be handled / further defined 'elementBaseName' => '', 'tabAndInlineStack' => [], 'inlineData' => [], 'inlineStructure' => [], // This array of fields will be set as hidden-fields instead of rendered normally! // This is used by EditDocumentController to force some field values if set as "overrideVals" in _GP 'overrideValues' => [], // Default values for fields. This is for example used in DatabaseRowInitializeNew data provider to set // fields to specific values if new records are created. Values are often carried around as "defVals" GET // parameters and hand over by controllers to FormEngine using this array. Array structure is // ['aTableName']['aFieldName'] = 'aValue'. 'defaultValues' => [], // This array must NOT be set / manipulated by data providers but is instead used by the render part // of FormEngine to add runtime data. Containers and elements add data here which is given to // sub-containers, elements, controls and wizards. 'renderData' => [], // A place for non-core, additional, custom data providers to add data. If a data provider needs to add // additional data to the data array that doesn't fit elsewhere, it can place it here to use it in the // render part again. Data in here should be namespaced in a way that it does not collide with other // data providers adding further data here. Using the extension key as array key could be a good idea. 'customData' => [], ]; } }