/**
  * Get the related records of the embedding item, this could be 1:n, m:n.
  * Returns an associative array with the keys records and count. 'count' contains only real existing records on the current parent record.
  *
  * @param string $table The table name of the record
  * @param string $field The field name which this element is supposed to edit
  * @param array $row The record data array where the value(s) for the field can be found
  * @param array $PA An array with additional configuration options.
  * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience)
  * @param int $inlineFirstPid Inline first pid
  * @return array The records related to the parent item as associative array.
  */
 public function getRelatedRecords($table, $field, $row, $PA, $config, $inlineFirstPid)
 {
     $language = 0;
     $elements = $PA['itemFormElValue'];
     $foreignTable = $config['foreign_table'];
     $localizationMode = BackendUtility::getInlineLocalizationMode($table, $config);
     if ($localizationMode !== FALSE) {
         $language = (int) $row[$GLOBALS['TCA'][$table]['ctrl']['languageField']];
         $transOrigPointer = (int) $row[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']];
         $transOrigTable = BackendUtility::getOriginalTranslationTable($table);
         if ($language > 0 && $transOrigPointer) {
             // Localization in mode 'keep', isn't a real localization, but keeps the children of the original parent record:
             if ($localizationMode === 'keep') {
                 $transOrigRec = $this->getRecord($transOrigTable, $transOrigPointer);
                 $elements = $transOrigRec[$field];
             } elseif ($localizationMode === 'select') {
                 $transOrigRec = $this->getRecord($transOrigTable, $transOrigPointer);
                 $fieldValue = $transOrigRec[$field];
                 // Checks if it is a flexform field
                 if ($GLOBALS['TCA'][$table]['columns'][$field]['config']['type'] === 'flex') {
                     $flexFormParts = FormEngineUtility::extractFlexFormParts($PA['itemFormElName']);
                     $flexData = GeneralUtility::xml2array($fieldValue);
                     /** @var  $flexFormTools  FlexFormTools */
                     $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
                     $flexFormFieldValue = $flexFormTools->getArrayValueByPath($flexFormParts, $flexData);
                     if ($flexFormFieldValue !== NULL) {
                         $fieldValue = $flexFormFieldValue;
                     }
                 }
                 $recordsOriginal = $this->getRelatedRecordsArray($foreignTable, $fieldValue);
             }
         }
     }
     $records = $this->getRelatedRecordsArray($foreignTable, $elements);
     $relatedRecords = array('records' => $records, 'count' => count($records));
     // Merge original language with current localization and show differences:
     if (!empty($recordsOriginal)) {
         $options = array('showPossible' => isset($config['appearance']['showPossibleLocalizationRecords']) && $config['appearance']['showPossibleLocalizationRecords'], 'showRemoved' => isset($config['appearance']['showRemovedLocalizationRecords']) && $config['appearance']['showRemovedLocalizationRecords']);
         // Either show records that possibly can localized or removed
         if ($options['showPossible'] || $options['showRemoved']) {
             $relatedRecords['records'] = $this->getLocalizationDifferences($foreignTable, $options, $recordsOriginal, $records);
             // Otherwise simulate localizeChildrenAtParentLocalization behaviour when creating a new record
             // (which has language and translation pointer values set)
         } elseif (!empty($config['behaviour']['localizeChildrenAtParentLocalization']) && !MathUtility::canBeInterpretedAsInteger($row['uid'])) {
             if (!empty($GLOBALS['TCA'][$foreignTable]['ctrl']['transOrigPointerField'])) {
                 $foreignLanguageField = $GLOBALS['TCA'][$foreignTable]['ctrl']['languageField'];
             }
             if (!empty($GLOBALS['TCA'][$foreignTable]['ctrl']['transOrigPointerField'])) {
                 $foreignTranslationPointerField = $GLOBALS['TCA'][$foreignTable]['ctrl']['transOrigPointerField'];
             }
             // Duplicate child records of default language in form
             foreach ($recordsOriginal as $record) {
                 if (!empty($foreignLanguageField)) {
                     $record[$foreignLanguageField] = $language;
                 }
                 if (!empty($foreignTranslationPointerField)) {
                     $record[$foreignTranslationPointerField] = $record['uid'];
                 }
                 $newId = uniqid('NEW', TRUE);
                 $record['uid'] = $newId;
                 $record['pid'] = ${$inlineFirstPid};
                 $relatedRecords['records'][$newId] = $record;
             }
         }
     }
     return $relatedRecords;
 }
예제 #2
0
 /**
  * Entry method
  *
  * @return array As defined in initializeResultArray() of AbstractNode
  */
 public function render()
 {
     $languageService = $this->getLanguageService();
     $this->inlineData = $this->globalOptions['inlineData'];
     /** @var InlineStackProcessor $inlineStackProcessor */
     $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
     $this->inlineStackProcessor = $inlineStackProcessor;
     $inlineStackProcessor->initializeByGivenStructure($this->globalOptions['inlineStructure']);
     $table = $this->globalOptions['table'];
     $row = $this->globalOptions['databaseRow'];
     $field = $this->globalOptions['fieldName'];
     $parameterArray = $this->globalOptions['parameterArray'];
     $resultArray = $this->initializeResultArray();
     $html = '';
     // An inline field must have a foreign_table, if not, stop all further inline actions for this field
     if (!$parameterArray['fieldConf']['config']['foreign_table'] || !is_array($GLOBALS['TCA'][$parameterArray['fieldConf']['config']['foreign_table']])) {
         return $resultArray;
     }
     $config = FormEngineUtility::mergeInlineConfiguration($parameterArray['fieldConf']['config']);
     $foreign_table = $config['foreign_table'];
     $language = 0;
     if (BackendUtility::isTableLocalizable($table)) {
         $language = (int) $row[$GLOBALS['TCA'][$table]['ctrl']['languageField']];
     }
     $minItems = MathUtility::forceIntegerInRange($config['minitems'], 0);
     $maxItems = MathUtility::forceIntegerInRange($config['maxitems'], 0);
     if (!$maxItems) {
         $maxItems = 100000;
     }
     // Add the current inline job to the structure stack
     $newStructureItem = array('table' => $table, 'uid' => $row['uid'], 'field' => $field, 'config' => $config, 'localizationMode' => BackendUtility::getInlineLocalizationMode($table, $config));
     // Extract FlexForm parts (if any) from element name, e.g. array('vDEF', 'lDEF', 'FlexField', 'vDEF')
     if (!empty($parameterArray['itemFormElName'])) {
         $flexFormParts = FormEngineUtility::extractFlexFormParts($parameterArray['itemFormElName']);
         if ($flexFormParts !== NULL) {
             $newStructureItem['flexform'] = $flexFormParts;
         }
     }
     $inlineStackProcessor->pushStableStructureItem($newStructureItem);
     // e.g. data[<table>][<uid>][<field>]
     $nameForm = $inlineStackProcessor->getCurrentStructureFormPrefix();
     // e.g. data-<pid>-<table1>-<uid1>-<field1>-<table2>-<uid2>-<field2>
     $nameObject = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid']);
     // Get the records related to this inline record
     $inlineRelatedRecordResolver = GeneralUtility::makeInstance(InlineRelatedRecordResolver::class);
     $relatedRecords = $inlineRelatedRecordResolver->getRelatedRecords($table, $field, $row, $parameterArray, $config, $this->globalOptions['inlineFirstPid']);
     // Set the first and last record to the config array
     $relatedRecordsUids = array_keys($relatedRecords['records']);
     $config['inline']['first'] = reset($relatedRecordsUids);
     $config['inline']['last'] = end($relatedRecordsUids);
     $top = $inlineStackProcessor->getStructureLevel(0);
     $this->inlineData['config'][$nameObject] = array('table' => $foreign_table, 'md5' => md5($nameObject));
     $this->inlineData['config'][$nameObject . '-' . $foreign_table] = array('min' => $minItems, 'max' => $maxItems, 'sortable' => $config['appearance']['useSortable'], 'top' => array('table' => $top['table'], 'uid' => $top['uid']), 'context' => array('config' => $config, 'hmac' => GeneralUtility::hmac(serialize($config))));
     $this->inlineData['nested'][$nameObject] = $this->globalOptions['tabAndInlineStack'];
     // If relations are required to be unique, get the uids that have already been used on the foreign side of the relation
     if ($config['foreign_unique']) {
         // If uniqueness *and* selector are set, they should point to the same field - so, get the configuration of one:
         $selConfig = FormEngineUtility::getInlinePossibleRecordsSelectorConfig($config, $config['foreign_unique']);
         // Get the used unique ids:
         $uniqueIds = $this->getUniqueIds($relatedRecords['records'], $config, $selConfig['type'] == 'groupdb');
         $possibleRecords = $this->getPossibleRecords($table, $field, $row, $config, 'foreign_unique');
         $uniqueMax = $config['appearance']['useCombination'] || $possibleRecords === FALSE ? -1 : count($possibleRecords);
         $this->inlineData['unique'][$nameObject . '-' . $foreign_table] = array('max' => $uniqueMax, 'used' => $uniqueIds, 'type' => $selConfig['type'], 'table' => $config['foreign_table'], 'elTable' => $selConfig['table'], 'field' => $config['foreign_unique'], 'selector' => $selConfig['selector'], 'possible' => $this->getPossibleRecordsFlat($possibleRecords));
     }
     $resultArray['inlineData'] = $this->inlineData;
     // Render the localization links
     $localizationLinks = '';
     if ($language > 0 && $row[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] > 0 && MathUtility::canBeInterpretedAsInteger($row['uid'])) {
         // Add the "Localize all records" link before all child records:
         if (isset($config['appearance']['showAllLocalizationLink']) && $config['appearance']['showAllLocalizationLink']) {
             $localizationLinks .= ' ' . $this->getLevelInteractionLink('localize', $nameObject . '-' . $foreign_table, $config);
         }
         // Add the "Synchronize with default language" link before all child records:
         if (isset($config['appearance']['showSynchronizationLink']) && $config['appearance']['showSynchronizationLink']) {
             $localizationLinks .= ' ' . $this->getLevelInteractionLink('synchronize', $nameObject . '-' . $foreign_table, $config);
         }
     }
     // Define how to show the "Create new record" link - if there are more than maxitems, hide it
     if ($relatedRecords['count'] >= $maxItems || $uniqueMax > 0 && $relatedRecords['count'] >= $uniqueMax) {
         $config['inline']['inlineNewButtonStyle'] = 'display: none;';
         $config['inline']['inlineNewRelationButtonStyle'] = 'display: none;';
     }
     // Render the level links (create new record):
     $levelLinks = $this->getLevelInteractionLink('newRecord', $nameObject . '-' . $foreign_table, $config);
     // Wrap all inline fields of a record with a <div> (like a container)
     $html .= '<div class="form-group" id="' . $nameObject . '">';
     // Add the level links before all child records:
     if ($config['appearance']['levelLinksPosition'] === 'both' || $config['appearance']['levelLinksPosition'] === 'top') {
         $html .= '<div class="form-group t3js-formengine-validation-marker">' . $levelLinks . $localizationLinks . '</div>';
     }
     // If it's required to select from possible child records (reusable children), add a selector box
     if ($config['foreign_selector'] && $config['appearance']['showPossibleRecordsSelector'] !== FALSE) {
         // If not already set by the foreign_unique, set the possibleRecords here and the uniqueIds to an empty array
         if (!$config['foreign_unique']) {
             $possibleRecords = $this->getPossibleRecords($table, $field, $row, $config);
             $uniqueIds = array();
         }
         $selectorBox = $this->renderPossibleRecordsSelector($possibleRecords, $config, $uniqueIds);
         $html .= $selectorBox . $localizationLinks;
     }
     $title = $languageService->sL($parameterArray['fieldConf']['label']);
     $html .= '<div class="panel-group panel-hover" data-title="' . htmlspecialchars($title) . '" id="' . $nameObject . '_records">';
     $relationList = array();
     if (!empty($relatedRecords['records'])) {
         foreach ($relatedRecords['records'] as $rec) {
             $options = $this->globalOptions;
             $options['inlineRelatedRecordToRender'] = $rec;
             $options['inlineRelatedRecordConfig'] = $config;
             $options['inlineData'] = $this->inlineData;
             $options['inlineStructure'] = $inlineStackProcessor->getStructure();
             $options['renderType'] = 'inlineRecordContainer';
             /** @var NodeFactory $nodeFactory */
             $nodeFactory = $this->globalOptions['nodeFactory'];
             $childArray = $nodeFactory->create($options)->render();
             $html .= $childArray['html'];
             $childArray['html'] = '';
             $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $childArray);
             if (!isset($rec['__virtual']) || !$rec['__virtual']) {
                 $relationList[] = $rec['uid'];
             }
         }
     }
     $html .= '</div>';
     // Add the level links after all child records:
     if ($config['appearance']['levelLinksPosition'] === 'both' || $config['appearance']['levelLinksPosition'] === 'bottom') {
         $html .= $levelLinks . $localizationLinks;
     }
     if (is_array($config['customControls'])) {
         $html .= '<div id="' . $nameObject . '_customControls">';
         foreach ($config['customControls'] as $customControlConfig) {
             $parameters = array('table' => $table, 'field' => $field, 'row' => $row, 'nameObject' => $nameObject, 'nameForm' => $nameForm, 'config' => $config);
             $html .= GeneralUtility::callUserFunction($customControlConfig, $parameters, $this);
         }
         $html .= '</div>';
     }
     // Add Drag&Drop functions for sorting to FormEngine::$additionalJS_post
     if (count($relationList) > 1 && $config['appearance']['useSortable']) {
         $resultArray['additionalJavaScriptPost'][] = 'inline.createDragAndDropSorting("' . $nameObject . '_records' . '");';
     }
     // Publish the uids of the child records in the given order to the browser
     $html .= '<input type="hidden" name="' . $nameForm . '" value="' . implode(',', $relationList) . '" ' . $this->getValidationDataAsDataAttribute(array('type' => 'inline', 'minitems' => $minItems, 'maxitems' => $maxItems)) . ' class="inlineRecord" />';
     // Close the wrap for all inline fields (container)
     $html .= '</div>';
     $resultArray['html'] = $html;
     return $resultArray;
 }