/** * Renders the HTML header for a foreign record, such as the title, toggle-function, drag'n'drop, etc. * Later on the command-icons are inserted here. * * @param string $parentUid The uid of the parent (embedding) record (uid or NEW...) * @param string $foreign_table The foreign_table we create a header for * @param array $data Current data * @param array $config content of $PA['fieldConf']['config'] * @param bool $isVirtualRecord * @return string The HTML code of the header */ protected function renderForeignRecordHeader($parentUid, $foreign_table, $data, $config, $isVirtualRecord = false) { $rec = $data['databaseRow']; // Init: $domObjectId = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']); $objectId = $domObjectId . '-' . $foreign_table . '-' . $rec['uid']; // We need the returnUrl of the main script when loading the fields via AJAX-call (to correct wizard code, so include it as 3rd parameter) // Pre-Processing: $isOnSymmetricSide = RelationHandler::isOnSymmetricSide($parentUid, $config, $rec); $hasForeignLabel = (bool) (!$isOnSymmetricSide && $config['foreign_label']); $hasSymmetricLabel = (bool) $isOnSymmetricSide && $config['symmetric_label']; // Get the record title/label for a record: // Try using a self-defined user function only for formatted labels if (isset($GLOBALS['TCA'][$foreign_table]['ctrl']['formattedLabel_userFunc'])) { $params = array('table' => $foreign_table, 'row' => $rec, 'title' => '', 'isOnSymmetricSide' => $isOnSymmetricSide, 'options' => isset($GLOBALS['TCA'][$foreign_table]['ctrl']['formattedLabel_userFunc_options']) ? $GLOBALS['TCA'][$foreign_table]['ctrl']['formattedLabel_userFunc_options'] : array(), 'parent' => array('uid' => $parentUid, 'config' => $config)); // callUserFunction requires a third parameter, but we don't want to give $this as reference! $null = null; GeneralUtility::callUserFunction($GLOBALS['TCA'][$foreign_table]['ctrl']['formattedLabel_userFunc'], $params, $null); $recTitle = $params['title']; // Try using a normal self-defined user function } elseif (isset($GLOBALS['TCA'][$foreign_table]['ctrl']['label_userFunc'])) { $params = array('table' => $foreign_table, 'row' => $rec, 'title' => '', 'isOnSymmetricSide' => $isOnSymmetricSide, 'parent' => array('uid' => $parentUid, 'config' => $config)); // callUserFunction requires a third parameter, but we don't want to give $this as reference! $null = null; GeneralUtility::callUserFunction($GLOBALS['TCA'][$foreign_table]['ctrl']['label_userFunc'], $params, $null); $recTitle = $params['title']; } elseif ($hasForeignLabel || $hasSymmetricLabel) { $titleCol = $hasForeignLabel ? $config['foreign_label'] : $config['symmetric_label']; $foreignConfig = FormEngineUtility::getInlinePossibleRecordsSelectorConfig($config, $titleCol); // Render title for everything else than group/db: if ($foreignConfig['type'] !== 'groupdb') { $recTitle = BackendUtility::getProcessedValueExtra($foreign_table, $titleCol, $rec[$titleCol], 0, 0, false); } else { // $recTitle could be something like: "tx_table_123|...", $valueParts = GeneralUtility::trimExplode('|', $rec[$titleCol]); $itemParts = GeneralUtility::revExplode('_', $valueParts[0], 2); $recTemp = BackendUtility::getRecordWSOL($itemParts[0], $itemParts[1]); $recTitle = BackendUtility::getRecordTitle($itemParts[0], $recTemp, false); } $recTitle = BackendUtility::getRecordTitlePrep($recTitle); if (trim($recTitle) === '') { $recTitle = BackendUtility::getNoRecordTitle(true); } } else { $recTitle = BackendUtility::getRecordTitle($foreign_table, FormEngineUtility::databaseRowCompatibility($rec), true); } $altText = BackendUtility::getRecordIconAltText($rec, $foreign_table); $iconImg = '<span title="' . $altText . '" id="' . htmlspecialchars($objectId) . '_icon' . '">' . $this->iconFactory->getIconForRecord($foreign_table, $rec, Icon::SIZE_SMALL)->render() . '</span>'; $label = '<span id="' . $objectId . '_label">' . $recTitle . '</span>'; $ctrl = $this->renderForeignRecordHeaderControl($parentUid, $foreign_table, $data, $config, $isVirtualRecord); $thumbnail = false; // Renders a thumbnail for the header if (!empty($config['appearance']['headerThumbnail']['field'])) { $fieldValue = $rec[$config['appearance']['headerThumbnail']['field']]; $firstElement = array_shift(GeneralUtility::trimExplode('|', array_shift(GeneralUtility::trimExplode(',', $fieldValue)))); $fileUid = array_pop(BackendUtility::splitTable_Uid($firstElement)); if (!empty($fileUid)) { $fileObject = ResourceFactory::getInstance()->getFileObject($fileUid); if ($fileObject && $fileObject->isMissing()) { $flashMessage = \TYPO3\CMS\Core\Resource\Utility\BackendUtility::getFlashMessageForMissingFile($fileObject); $thumbnail = $flashMessage->render(); } elseif ($fileObject) { $imageSetup = $config['appearance']['headerThumbnail']; unset($imageSetup['field']); if (!empty($rec['crop'])) { $imageSetup['crop'] = $rec['crop']; } $imageSetup = array_merge(array('width' => '45', 'height' => '45c'), $imageSetup); $processedImage = $fileObject->process(ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $imageSetup); // Only use a thumbnail if the processing process was successful by checking if image width is set if ($processedImage->getProperty('width')) { $imageUrl = $processedImage->getPublicUrl(true); $thumbnail = '<img src="' . $imageUrl . '" ' . 'width="' . $processedImage->getProperty('width') . '" ' . 'height="' . $processedImage->getProperty('height') . '" ' . 'alt="' . htmlspecialchars($altText) . '" ' . 'title="' . htmlspecialchars($altText) . '">'; } } } } if (!empty($config['appearance']['headerThumbnail']['field']) && $thumbnail) { $mediaContainer = '<div class="form-irre-header-cell form-irre-header-thumbnail" id="' . $objectId . '_thumbnailcontainer">' . $thumbnail . '</div>'; } else { $mediaContainer = '<div class="form-irre-header-cell form-irre-header-icon" id="' . $objectId . '_iconcontainer">' . $iconImg . '</div>'; } $header = $mediaContainer . ' <div class="form-irre-header-cell form-irre-header-body">' . $label . '</div> <div class="form-irre-header-cell form-irre-header-control t3js-formengine-irre-control">' . $ctrl . '</div>'; return $header; }
/** * Get a selector as used for the select type, to select from all available * records and to create a relation to the embedding record (e.g. like MM). * * @param array $selItems Array of all possible records * @param array $conf TCA configuration of the parent(!) field * @param array $uniqueIds The uids that have already been used and should be unique * @return string A HTML <select> box with all possible records */ protected function renderPossibleRecordsSelector($selItems, $conf, $uniqueIds = array()) { $foreign_selector = $conf['foreign_selector']; $selConfig = FormEngineUtility::getInlinePossibleRecordsSelectorConfig($conf, $foreign_selector); $item = ''; if ($selConfig['type'] === 'select') { $item = $this->renderPossibleRecordsSelectorTypeSelect($selItems, $conf, $selConfig['PA'], $uniqueIds); } elseif ($selConfig['type'] === 'groupdb') { $item = $this->renderPossibleRecordsSelectorTypeGroupDB($conf, $selConfig['PA']); } return $item; }
/** * Handle AJAX calls to show a new inline-record of the given table. * * @param string $domObjectId The calling object in hierarchy, that requested a new record. * @param string|int $foreignUid If set, the new record should be inserted after that one. * @return array An array to be used for JSON */ protected function renderInlineNewChildRecord($domObjectId, $foreignUid) { // The current table - for this table we should add/import records $current = $this->inlineStackProcessor->getUnstableStructure(); // The parent table - this table embeds the current table $parent = $this->inlineStackProcessor->getStructureLevel(-1); $config = $parent['config']; if (empty($config['foreign_table']) || !is_array($GLOBALS['TCA'][$config['foreign_table']])) { return $this->getErrorMessageForAJAX('Wrong configuration in table ' . $parent['table']); } $inlineRelatedRecordResolver = GeneralUtility::makeInstance(InlineRelatedRecordResolver::class); $config = FormEngineUtility::mergeInlineConfiguration($config); $collapseAll = isset($config['appearance']['collapseAll']) && $config['appearance']['collapseAll']; $expandSingle = isset($config['appearance']['expandSingle']) && $config['appearance']['expandSingle']; $inlineFirstPid = FormEngineUtility::getInlineFirstPidFromDomObjectId($domObjectId); // Dynamically create a new record using \TYPO3\CMS\Backend\Form\DataPreprocessor if (!$foreignUid || !MathUtility::canBeInterpretedAsInteger($foreignUid) || $config['foreign_selector']) { $record = $inlineRelatedRecordResolver->getNewRecord($inlineFirstPid, $current['table']); // Set default values for new created records if (isset($config['foreign_record_defaults']) && is_array($config['foreign_record_defaults'])) { $foreignTableConfig = $GLOBALS['TCA'][$current['table']]; // The following system relevant fields can't be set by foreign_record_defaults $notSettableFields = array('uid', 'pid', 't3ver_oid', 't3ver_id', 't3ver_label', 't3ver_wsid', 't3ver_state', 't3ver_stage', 't3ver_count', 't3ver_tstamp', 't3ver_move_id'); $configurationKeysForNotSettableFields = array('crdate', 'cruser_id', 'delete', 'origUid', 'transOrigDiffSourceField', 'transOrigPointerField', 'tstamp'); foreach ($configurationKeysForNotSettableFields as $configurationKey) { if (isset($foreignTableConfig['ctrl'][$configurationKey])) { $notSettableFields[] = $foreignTableConfig['ctrl'][$configurationKey]; } } foreach ($config['foreign_record_defaults'] as $fieldName => $defaultValue) { if (isset($foreignTableConfig['columns'][$fieldName]) && !in_array($fieldName, $notSettableFields)) { $record[$fieldName] = $defaultValue; } } } // Set language of new child record to the language of the parent record: if ($parent['localizationMode'] === 'select') { $parentRecord = $inlineRelatedRecordResolver->getRecord($parent['table'], $parent['uid']); $parentLanguageField = $GLOBALS['TCA'][$parent['table']]['ctrl']['languageField']; $childLanguageField = $GLOBALS['TCA'][$current['table']]['ctrl']['languageField']; if ($parentRecord[$parentLanguageField] > 0) { $record[$childLanguageField] = $parentRecord[$parentLanguageField]; } } } else { // @todo: Check this: Else also hits if $foreignUid = 0? $record = $inlineRelatedRecordResolver->getRecord($current['table'], $foreignUid); } // Now there is a foreign_selector, so there is a new record on the intermediate table, but // this intermediate table holds a field, which is responsible for the foreign_selector, so // we have to set this field to the uid we get - or if none, to a new uid if ($config['foreign_selector'] && $foreignUid) { $selConfig = FormEngineUtility::getInlinePossibleRecordsSelectorConfig($config, $config['foreign_selector']); // For a selector of type group/db, prepend the tablename (<tablename>_<uid>): $record[$config['foreign_selector']] = $selConfig['type'] != 'groupdb' ? '' : $selConfig['table'] . '_'; $record[$config['foreign_selector']] .= $foreignUid; if ($selConfig['table'] === 'sys_file') { $fileRecord = $inlineRelatedRecordResolver->getRecord($selConfig['table'], $foreignUid); if ($fileRecord !== FALSE && !$this->checkInlineFileTypeAccessForField($selConfig, $fileRecord)) { return $this->getErrorMessageForAJAX('File extension ' . $fileRecord['extension'] . ' is not allowed here!'); } } } // The HTML-object-id's prefix of the dynamically created record $objectName = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid); $objectPrefix = $objectName . '-' . $current['table']; $objectId = $objectPrefix . '-' . $record['uid']; $options = $this->getConfigurationOptionsForChildElements(); $options['databaseRow'] = array('uid' => $parent['uid']); $options['inlineFirstPid'] = $inlineFirstPid; $options['inlineRelatedRecordToRender'] = $record; $options['inlineRelatedRecordConfig'] = $config; $options['inlineStructure'] = $this->inlineStackProcessor->getStructure(); $options['isAjaxContext'] = TRUE; $options['renderType'] = 'inlineRecordContainer'; $childArray = $this->nodeFactory->create($options)->render(); if ($childArray === FALSE) { return $this->getErrorMessageForAJAX('Access denied'); } $this->mergeResult($childArray); $jsonArray = array('data' => $childArray['html'], 'scriptCall' => array()); if (!$current['uid']) { $jsonArray['scriptCall'][] = 'inline.domAddNewRecord(\'bottom\',' . GeneralUtility::quoteJSvalue($objectName . '_records') . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',json.data);'; $jsonArray['scriptCall'][] = 'inline.memorizeAddRecord(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($record['uid']) . ',null,' . GeneralUtility::quoteJSvalue($foreignUid) . ');'; } else { $jsonArray['scriptCall'][] = 'inline.domAddNewRecord(\'after\',' . GeneralUtility::quoteJSvalue($domObjectId . '_div') . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',json.data);'; $jsonArray['scriptCall'][] = 'inline.memorizeAddRecord(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($record['uid']) . ',' . GeneralUtility::quoteJSvalue($current['uid']) . ',' . GeneralUtility::quoteJSvalue($foreignUid) . ');'; } $jsonArray = $this->getInlineAjaxCommonScriptCalls($jsonArray, $config, $inlineFirstPid); // Collapse all other records if requested: if (!$collapseAll && $expandSingle) { $jsonArray['scriptCall'][] = 'inline.collapseAllRecords(' . GeneralUtility::quoteJSvalue($objectId) . ', ' . GeneralUtility::quoteJSvalue($objectPrefix) . ', ' . GeneralUtility::quoteJSvalue($record['uid']) . ');'; } // Tell the browser to scroll to the newly created record $jsonArray['scriptCall'][] = 'Element.scrollTo(' . GeneralUtility::quoteJSvalue($objectId . '_div') . ');'; // Fade out and fade in the new record in the browser view to catch the user's eye $jsonArray['scriptCall'][] = 'inline.fadeOutFadeIn(' . GeneralUtility::quoteJSvalue($objectId . '_div') . ');'; return $jsonArray; }