예제 #1
0
 /**
  * Convert the DOM object-id of an inline container to an array.
  * The object-id could look like 'data-parentPageId-tx_mmftest_company-1-employees'.
  * This initializes $this->inlineStructure - used by AJAX entry points
  * There are two keys:
  * - 'stable': Containing full qualified identifiers (table, uid and field)
  * - 'unstable': Containing partly filled data (e.g. only table and possibly field)
  *
  * @param string $domObjectId The DOM object-id
  * @param bool $loadConfig Load the TCA configuration for that level (default: TRUE)
  * @return void
  */
 public function initializeByParsingDomObjectIdString($domObjectId, $loadConfig = TRUE)
 {
     $unstable = array();
     $vector = array('table', 'uid', 'field');
     // Substitute FlexForm addition and make parsing a bit easier
     $domObjectId = str_replace('---', ':', $domObjectId);
     // The starting pattern of an object identifier (e.g. "data-<firstPidValue>-<anything>)
     $pattern = '/^data' . '-' . '(.+?)' . '-' . '(.+)$/';
     if (preg_match($pattern, $domObjectId, $match)) {
         $inlineFirstPid = $match[1];
         $parts = explode('-', $match[2]);
         $partsCnt = count($parts);
         for ($i = 0; $i < $partsCnt; $i++) {
             if ($i > 0 && $i % 3 == 0) {
                 // Load the TCA configuration of the table field and store it in the stack
                 if ($loadConfig) {
                     $unstable['config'] = $GLOBALS['TCA'][$unstable['table']]['columns'][$unstable['field']]['config'];
                     // Fetch TSconfig:
                     $TSconfig = FormEngineUtility::getTSconfigForTableRow($unstable['table'], array('uid' => $unstable['uid'], 'pid' => $inlineFirstPid), $unstable['field']);
                     // Override TCA field config by TSconfig:
                     if (!$TSconfig['disabled']) {
                         $unstable['config'] = FormEngineUtility::overrideFieldConf($unstable['config'], $TSconfig);
                     }
                     $unstable['localizationMode'] = BackendUtility::getInlineLocalizationMode($unstable['table'], $unstable['config']);
                 }
                 // Extract FlexForm from field part (if any)
                 if (strpos($unstable['field'], ':') !== FALSE) {
                     $fieldParts = GeneralUtility::trimExplode(':', $unstable['field']);
                     $unstable['field'] = array_shift($fieldParts);
                     // FlexForm parts start with data:
                     if (count($fieldParts) > 0 && $fieldParts[0] === 'data') {
                         $unstable['flexform'] = $fieldParts;
                     }
                 }
                 $this->inlineStructure['stable'][] = $unstable;
                 $unstable = array();
             }
             $unstable[$vector[$i % 3]] = $parts[$i];
         }
         if (count($unstable)) {
             $this->inlineStructure['unstable'] = $unstable;
         }
     }
 }
예제 #2
0
 /**
  * Entry method
  *
  * @return array As defined in initializeResultArray() of AbstractNode
  */
 public function render()
 {
     $backendUser = $this->getBackendUserAuthentication();
     $languageService = $this->getLanguageService();
     $resultArray = $this->initializeResultArray();
     $table = $this->data['tableName'];
     $row = $this->data['databaseRow'];
     $fieldName = $this->data['fieldName'];
     // @todo: it should be safe at this point, this array exists ...
     if (!is_array($this->data['processedTca']['columns'][$fieldName])) {
         return $resultArray;
     }
     $parameterArray = array();
     $parameterArray['fieldConf'] = $this->data['processedTca']['columns'][$fieldName];
     $isOverlay = false;
     // This field decides whether the current record is an overlay (as opposed to being a standalone record)
     // Based on this decision we need to trigger field exclusion or special rendering (like readOnly)
     if (isset($this->data['processedTca']['ctrl']['transOrigPointerField']) && is_array($this->data['processedTca']['columns'][$this->data['processedTca']['ctrl']['transOrigPointerField']]) && is_array($row[$this->data['processedTca']['ctrl']['transOrigPointerField']]) && $row[$this->data['processedTca']['ctrl']['transOrigPointerField']][0] > 0) {
         $isOverlay = true;
     }
     // A couple of early returns in case the field should not be rendered
     // Check if this field is configured and editable according to exclude fields and other configuration
     if ($parameterArray['fieldConf']['exclude'] && !$backendUser->check('non_exclude_fields', $table . ':' . $fieldName) || $parameterArray['fieldConf']['config']['type'] === 'passthrough' || !$backendUser->isRTE() && $parameterArray['fieldConf']['config']['showIfRTE'] || $isOverlay && !$parameterArray['fieldConf']['l10n_display'] && $parameterArray['fieldConf']['l10n_mode'] === 'exclude' || $isOverlay && $this->data['localizationMode'] && $this->data['localizationMode'] !== $parameterArray['fieldConf']['l10n_cat'] || $this->inlineFieldShouldBeSkipped()) {
         return $resultArray;
     }
     $parameterArray['fieldTSConfig'] = [];
     if (isset($this->data['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.']) && is_array($this->data['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.'])) {
         $parameterArray['fieldTSConfig'] = $this->data['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.'];
     }
     if ($parameterArray['fieldTSConfig']['disabled']) {
         return $resultArray;
     }
     // Override fieldConf by fieldTSconfig:
     $parameterArray['fieldConf']['config'] = FormEngineUtility::overrideFieldConf($parameterArray['fieldConf']['config'], $parameterArray['fieldTSConfig']);
     $parameterArray['itemFormElName'] = 'data[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']';
     $parameterArray['itemFormElID'] = 'data_' . $table . '_' . $row['uid'] . '_' . $fieldName;
     $newElementBaseName = $this->data['elementBaseName'] . '[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']';
     // The value to show in the form field.
     $parameterArray['itemFormElValue'] = $row[$fieldName];
     // Set field to read-only if configured for translated records to show default language content as readonly
     if ($parameterArray['fieldConf']['l10n_display'] && GeneralUtility::inList($parameterArray['fieldConf']['l10n_display'], 'defaultAsReadonly') && $isOverlay) {
         $parameterArray['fieldConf']['config']['readOnly'] = true;
         $parameterArray['itemFormElValue'] = $this->data['defaultLanguageRow'][$fieldName];
     }
     if (strpos($this->data['processedTca']['ctrl']['type'], ':') === false) {
         $typeField = $this->data['processedTca']['ctrl']['type'];
     } else {
         $typeField = substr($this->data['processedTca']['ctrl']['type'], 0, strpos($this->data['processedTca']['ctrl']['type'], ':'));
     }
     // Create a JavaScript code line which will ask the user to save/update the form due to changing the element.
     // This is used for eg. "type" fields and others configured with "requestUpdate"
     if (!empty($this->data['processedTca']['ctrl']['type']) && $fieldName === $typeField || !empty($this->data['processedTca']['ctrl']['requestUpdate']) && GeneralUtility::inList(str_replace(' ', '', $this->data['processedTca']['ctrl']['requestUpdate']), $fieldName)) {
         if ($backendUser->jsConfirmation(JsConfirmation::TYPE_CHANGE)) {
             $alertMsgOnChange = 'top.TYPO3.Modal.confirm(TBE_EDITOR.labels.refreshRequired.title, TBE_EDITOR.labels.refreshRequired.content).on("button.clicked", function(e) { if (e.target.name == "ok" && TBE_EDITOR.checkSubmit(-1)) { TBE_EDITOR.submitForm() } top.TYPO3.Modal.dismiss(); });';
         } else {
             $alertMsgOnChange = 'if (TBE_EDITOR.checkSubmit(-1)){ TBE_EDITOR.submitForm() };';
         }
     } else {
         $alertMsgOnChange = '';
     }
     // JavaScript code for event handlers:
     $parameterArray['fieldChangeFunc'] = array();
     $parameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged'] = 'TBE_EDITOR.fieldChanged(' . GeneralUtility::quoteJSvalue($table) . ',' . GeneralUtility::quoteJSvalue($row['uid']) . ',' . GeneralUtility::quoteJSvalue($fieldName) . ',' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ');';
     $parameterArray['fieldChangeFunc']['alert'] = $alertMsgOnChange;
     // If this is the child of an inline type and it is the field creating the label
     if ($this->isInlineChildAndLabelField($table, $fieldName)) {
         /** @var InlineStackProcessor $inlineStackProcessor */
         $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
         $inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
         $inlineDomObjectId = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
         $inlineObjectId = implode('-', array($inlineDomObjectId, $table, $row['uid']));
         $parameterArray['fieldChangeFunc']['inline'] = 'inline.handleChangedField(' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ',' . GeneralUtility::quoteJSvalue($inlineObjectId) . ');';
     }
     // Based on the type of the item, call a render function on a child element
     $options = $this->data;
     $options['parameterArray'] = $parameterArray;
     $options['elementBaseName'] = $newElementBaseName;
     if (!empty($parameterArray['fieldConf']['config']['renderType'])) {
         $options['renderType'] = $parameterArray['fieldConf']['config']['renderType'];
     } else {
         // Fallback to type if no renderType is given
         $options['renderType'] = $parameterArray['fieldConf']['config']['type'];
     }
     $resultArray = $this->nodeFactory->create($options)->render();
     // If output is empty stop further processing.
     // This means there was internal processing only and we don't need to add additional information
     if (empty($resultArray['html'])) {
         return $resultArray;
     }
     $html = $resultArray['html'];
     // @todo: the language handling, the null and the placeholder stuff should be embedded in the single
     // @todo: element classes. Basically, this method should return here and have the element classes
     // @todo: decide on language stuff and other wraps already.
     // Add language + diff
     $renderLanguageDiff = true;
     if ($parameterArray['fieldConf']['l10n_display'] && (GeneralUtility::inList($parameterArray['fieldConf']['l10n_display'], 'hideDiff') || GeneralUtility::inList($parameterArray['fieldConf']['l10n_display'], 'defaultAsReadonly'))) {
         $renderLanguageDiff = false;
     }
     if ($renderLanguageDiff) {
         $html = $this->renderDefaultLanguageContent($table, $fieldName, $row, $html);
         $html = $this->renderDefaultLanguageDiff($table, $fieldName, $row, $html);
     }
     $fieldItemClasses = array('t3js-formengine-field-item');
     // NULL value and placeholder handling
     $nullControlNameAttribute = ' name="' . htmlspecialchars('control[active][' . $table . '][' . $row['uid'] . '][' . $fieldName . ']') . '"';
     if (!empty($parameterArray['fieldConf']['config']['eval']) && GeneralUtility::inList($parameterArray['fieldConf']['config']['eval'], 'null') && (empty($parameterArray['fieldConf']['config']['mode']) || $parameterArray['fieldConf']['config']['mode'] !== 'useOrOverridePlaceholder')) {
         // This field has eval=null set, but has no useOverridePlaceholder defined.
         // Goal is to have a field that can distinct between NULL and empty string in the database.
         // A checkbox and an additional hidden field will be created, both with the same name
         // and prefixed with "control[active]". If the checkbox is set (value 1), the value from the casual
         // input field will be written to the database. If the checkbox is not set, the hidden field
         // transfers value=0 to DataHandler, the value of the input field will then be reset to NULL by the
         // DataHandler at an early point in processing, so NULL will be written to DB as field value.
         // If the value of the field *is* NULL at the moment, an additional class is set
         // @todo: This does not work well at the moment, but is kept for now. see input_14 of ext:styleguide as example
         $checked = ' checked="checked"';
         if ($this->data['databaseRow'][$fieldName] === null) {
             $fieldItemClasses[] = 'disabled';
             $checked = '';
         }
         $formElementName = 'data[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']';
         $onChange = htmlspecialchars('typo3form.fieldSetNull(' . GeneralUtility::quoteJSvalue($formElementName) . ', !this.checked)');
         $nullValueWrap = array();
         $nullValueWrap[] = '<div class="' . implode(' ', $fieldItemClasses) . '">';
         $nullValueWrap[] = '<div class="t3-form-field-disable"></div>';
         $nullValueWrap[] = '<div class="checkbox">';
         $nullValueWrap[] = '<label>';
         $nullValueWrap[] = '<input type="hidden"' . $nullControlNameAttribute . ' value="0" />';
         $nullValueWrap[] = '<input type="checkbox"' . $nullControlNameAttribute . ' value="1" onchange="' . $onChange . '"' . $checked . ' /> &nbsp;';
         $nullValueWrap[] = '</label>';
         $nullValueWrap[] = $html;
         $nullValueWrap[] = '</div>';
         $nullValueWrap[] = '</div>';
         $html = implode(LF, $nullValueWrap);
     } elseif (isset($parameterArray['fieldConf']['config']['mode']) && $parameterArray['fieldConf']['config']['mode'] === 'useOrOverridePlaceholder') {
         // This field has useOverridePlaceholder set.
         // Here, a value from a deeper DB structure can be "fetched up" as value, and can also be overridden by a
         // local value. This is used in FAL, where eg. the "title" field can have the default value from sys_file_metadata,
         // the title field of sys_file_reference is then set to NULL. Or the "override" checkbox is set, and a string
         // or an empty string is then written to the field of sys_file_reference.
         // The situation is similar to the NULL handling above, but additionally a "default" value should be shown.
         // To achieve this, again a hidden control[hidden] field is added together with a checkbox with the same name
         // to transfer the information whether the default value should be used or not: Checkbox checked transfers 1 as
         // value in control[active], meaning the overridden value should be used.
         // Additionally to the casual input field, a second field is added containing the "placeholder" value. This
         // field has no name attribute and is not transferred at all. Those two are then hidden / shown depending
         // on the state of the above checkbox in via JS.
         $placeholder = empty($parameterArray['fieldConf']['config']['placeholder']) ? '' : $parameterArray['fieldConf']['config']['placeholder'];
         $onChange = 'typo3form.fieldTogglePlaceholder(' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ', !this.checked)';
         $checked = $parameterArray['itemFormElValue'] === null ? '' : ' checked="checked"';
         $resultArray['additionalJavaScriptPost'][] = 'typo3form.fieldTogglePlaceholder(' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ', ' . ($checked ? 'false' : 'true') . ');';
         // Renders an input or textarea field depending on type of "parent"
         $options = array();
         $options['databaseRow'] = array();
         $options['table'] = '';
         $options['parameterArray'] = $parameterArray;
         $options['parameterArray']['itemFormElValue'] = GeneralUtility::fixed_lgd_cs($placeholder, 30);
         $options['renderType'] = 'none';
         $noneElementResult = $this->nodeFactory->create($options)->render();
         $noneElementHtml = $noneElementResult['html'];
         $placeholderWrap = array();
         $placeholderWrap[] = '<div class="' . implode(' ', $fieldItemClasses) . '">';
         $placeholderWrap[] = '<div class="t3-form-field-disable"></div>';
         $placeholderWrap[] = '<div class="checkbox">';
         $placeholderWrap[] = '<label>';
         $placeholderWrap[] = '<input type="hidden"' . $nullControlNameAttribute . ' value="0" />';
         $placeholderWrap[] = '<input type="checkbox"' . $nullControlNameAttribute . ' value="1" id="tce-forms-textfield-use-override-' . $fieldName . '-' . $row['uid'] . '" onchange="' . htmlspecialchars($onChange) . '"' . $checked . ' />';
         $placeholderWrap[] = sprintf($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.placeholder.override'), BackendUtility::getRecordTitlePrep($placeholder, 20));
         $placeholderWrap[] = '</label>';
         $placeholderWrap[] = '</div>';
         $placeholderWrap[] = '<div class="t3js-formengine-placeholder-placeholder">';
         $placeholderWrap[] = $noneElementHtml;
         $placeholderWrap[] = '</div>';
         $placeholderWrap[] = '<div class="t3js-formengine-placeholder-formfield">';
         $placeholderWrap[] = $html;
         $placeholderWrap[] = '</div>';
         $placeholderWrap[] = '</div>';
         $html = implode(LF, $placeholderWrap);
     } elseif ($parameterArray['fieldConf']['config']['type'] !== 'user' || empty($parameterArray['fieldConf']['config']['noTableWrapping'])) {
         // Add a casual wrap if the field is not of type user with no wrap requested.
         $standardWrap = array();
         $standardWrap[] = '<div class="' . implode(' ', $fieldItemClasses) . '">';
         $standardWrap[] = '<div class="t3-form-field-disable"></div>';
         $standardWrap[] = $html;
         $standardWrap[] = '</div>';
         $html = implode(LF, $standardWrap);
     }
     $resultArray['html'] = $html;
     return $resultArray;
 }
예제 #3
0
 /**
  * If the select field is build by a foreign_table the related UIDs
  * will be returned.
  *
  * Otherwise the label of the currently selected value will be written
  * to the alternativeFieldValue class property.
  *
  * @param array $fieldConfig The "config" section of the TCA for the current select field.
  * @param string $fieldName The name of the select field.
  * @param string $value The current value in the local record, usually a comma separated list of selected values.
  * @return array Array of related UIDs.
  */
 protected function getRelatedSelectFieldUids(array $fieldConfig, $fieldName, $value)
 {
     $relatedUids = array();
     $isTraversable = FALSE;
     if (isset($fieldConfig['foreign_table'])) {
         $isTraversable = TRUE;
         // if a foreign_table is used we pre-filter the records for performance
         $fieldConfig['foreign_table_where'] .= ' AND ' . $fieldConfig['foreign_table'] . '.uid IN (' . $value . ')';
     }
     $PA = array();
     $PA['fieldConf']['config'] = $fieldConfig;
     $PA['fieldTSConfig'] = FormEngineUtility::getTSconfigForTableRow($this->currentTable, $this->currentRow, $fieldName);
     $PA['fieldConf']['config'] = FormEngineUtility::overrideFieldConf($PA['fieldConf']['config'], $PA['fieldTSConfig']);
     $selectItemArray = FormEngineUtility::getSelectItems($this->currentTable, $fieldName, $this->currentRow, $PA);
     if ($isTraversable && !empty($selectItemArray)) {
         $this->currentTable = $fieldConfig['foreign_table'];
         $relatedUids = $this->getSelectedValuesFromSelectItemArray($selectItemArray, $value);
     } else {
         $selectedLabels = $this->getSelectedValuesFromSelectItemArray($selectItemArray, $value, 1, TRUE);
         if (count($selectedLabels) === 1) {
             $this->alternativeFieldValue = $selectedLabels[0];
             $this->forceAlternativeFieldValueUse = TRUE;
         }
     }
     return $relatedUids;
 }