/** * Handler for unknown types. * * @return array As defined in initializeResultArray() of AbstractNode */ public function render() { $resultArray = $this->initializeResultArray(); $languageService = $this->getLanguageService(); $row = $this->data['databaseRow']; $parameterArray = $this->data['parameterArray']; // If ratios are set do not add default options if (isset($parameterArray['fieldConf']['config']['ratios'])) { unset($this->defaultConfig['ratios']); } $config = ArrayUtility::arrayMergeRecursiveOverrule($this->defaultConfig, $parameterArray['fieldConf']['config']); // By default we allow all image extensions that can be handled by the GFX functionality if ($config['allowedExtensions'] === null) { $config['allowedExtensions'] = $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']; } if ($config['readOnly']) { $options = array(); $options['parameterArray'] = array('fieldConf' => array('config' => $config), 'itemFormElValue' => $parameterArray['itemFormElValue']); $options['renderType'] = 'none'; return $this->nodeFactory->create($options)->render(); } $file = $this->getFile($row, $config['file_field']); if (!$file) { return $resultArray; } $content = ''; $preview = ''; if (GeneralUtility::inList(mb_strtolower($config['allowedExtensions']), mb_strtolower($file->getExtension()))) { // Get preview $preview = $this->getPreview($file, $parameterArray['itemFormElValue']); // Check if ratio labels hold translation strings foreach ((array) $config['ratios'] as $ratio => $label) { $config['ratios'][$ratio] = $languageService->sL($label, true); } $formFieldId = StringUtility::getUniqueId('formengine-image-manipulation-'); $wizardData = array('zoom' => $config['enableZoom'] ? '1' : '0', 'ratios' => json_encode($config['ratios']), 'file' => $file->getUid()); $wizardData['token'] = GeneralUtility::hmac(implode('|', $wizardData), 'ImageManipulationWizard'); $buttonAttributes = array('data-url' => BackendUtility::getAjaxUrl('wizard_image_manipulation', $wizardData), 'data-severity' => 'notice', 'data-image-name' => $file->getNameWithoutExtension(), 'data-image-uid' => $file->getUid(), 'data-file-field' => $config['file_field'], 'data-field' => $formFieldId); $button = '<button class="btn btn-default t3js-image-manipulation-trigger"'; foreach ($buttonAttributes as $key => $value) { $button .= ' ' . $key . '="' . htmlspecialchars($value) . '"'; } $button .= '><span class="t3-icon fa fa-crop"></span>'; $button .= $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.open-editor', true); $button .= '</button>'; $inputField = '<input type="hidden" ' . 'id="' . $formFieldId . '" ' . 'name="' . $parameterArray['itemFormElName'] . '" ' . 'value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" />'; $content .= $inputField . $button; $content .= $this->getImageManipulationInfoTable($parameterArray['itemFormElValue']); $resultArray['requireJsModules'][] = array('TYPO3/CMS/Backend/ImageManipulation' => 'function(ImageManipulation){ImageManipulation.initializeTrigger()}'); } $content .= '<p class="text-muted"><em>' . $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.supported-types-message', true) . '<br />'; $content .= mb_strtoupper(implode(', ', GeneralUtility::trimExplode(',', $config['allowedExtensions']))); $content .= '</em></p>'; $item = '<div class="media">'; $item .= $preview; $item .= '<div class="media-body">' . $content . '</div>'; $item .= '</div>'; $resultArray['html'] = $item; return $resultArray; }
public function renderHtml($name, $value, $options) { $width = $options['width']; // TODO: das Feld beachten! $maxlength = $options['maxlength']; $attributes = array(); // for data-formengine-input-params $paramsList = array('field' => $name, 'evalList' => 'int', 'is_in' => ''); $attributes['id'] = StringUtility::getUniqueId('formengine-input-'); $attributes['value'] = ''; $attributes['data-formengine-validation-rules'] = json_encode(array('type' => 'int')); $attributes['data-formengine-input-params'] = json_encode($paramsList); $attributes['data-formengine-input-name'] = htmlspecialchars($name); $attributeString = ''; foreach ($attributes as $attributeName => $attributeValue) { $attributeString .= ' ' . $attributeName . '="' . htmlspecialchars($attributeValue) . '"'; } //$width = (int)$this->formMaxWidth($size); $width = $GLOBALS['TBE_TEMPLATE']->formWidth($width); $html = ' <input type="text"' . $attributeString . $width . ' />'; // This is the ACTUAL form field - values from the EDITABLE field must be transferred to this field which is the one that is written to the database. $html .= '<input type="hidden" name="' . $name . '" value="' . htmlspecialchars($value) . '" />'; // Den Wrap lassen wir weg, weil es zu einem Zeilenumbruch kommt // $html = '<div class="form-control-wrap"' . $width . '>' . $html . '</div>'; return $html; }
/** * Rendering the cObject, QTOBJECT * * @param array $conf Array of TypoScript properties * @return string Output */ public function render($conf = array()) { $params = $prefix = ''; if ($GLOBALS['TSFE']->baseUrl) { $prefix = $GLOBALS['TSFE']->baseUrl; } if ($GLOBALS['TSFE']->absRefPrefix) { $prefix = $GLOBALS['TSFE']->absRefPrefix; } $type = isset($conf['type.']) ? $this->cObj->stdWrap($conf['type'], $conf['type.']) : $conf['type']; // If file is audio and an explicit path has not been set, // take path from audio fallback property if ($type == 'audio' && empty($conf['file'])) { $conf['file'] = $conf['audioFallback']; } $filename = isset($conf['file.']) ? $this->cObj->stdWrap($conf['file'], $conf['file.']) : $conf['file']; $typeConf = $conf[$type . '.']; // Add QTobject js-file $this->getPageRenderer()->addJsFile($this->getPathToLibrary('flashmedia/qtobject/qtobject.js')); $replaceElementIdString = StringUtility::getUniqueId('mmqt'); $GLOBALS['TSFE']->register['MMQTID'] = $replaceElementIdString; $qtObject = 'QTObject' . $replaceElementIdString; // Merge with default parameters $conf['params.'] = array_merge((array) $typeConf['default.']['params.'], (array) $conf['params.']); if (is_array($conf['params.']) && is_array($typeConf['mapping.']['params.'])) { ArrayUtility::remapArrayKeys($conf['params.'], $typeConf['mapping.']['params.']); foreach ($conf['params.'] as $key => $value) { $params .= $qtObject . '.addParam("' . $key . '", "' . $value . '");' . LF; } } $params = ($params ? substr($params, 0, -2) : '') . LF . $qtObject . '.write("' . $replaceElementIdString . '");'; $alternativeContent = isset($conf['alternativeContent.']) ? $this->cObj->stdWrap($conf['alternativeContent'], $conf['alternativeContent.']) : $conf['alternativeContent']; $layout = str_replace(array('###ID###', '###QTOBJECT###'), array($replaceElementIdString, '<div id="' . $replaceElementIdString . '">' . $alternativeContent . '</div>'), isset($conf['layout.']) ? $this->cObj->stdWrap($conf['layout'], $conf['layout.']) : $conf['layout']); $width = isset($conf['width.']) ? $this->cObj->stdWrap($conf['width'], $conf['width.']) : $conf['width']; if (!$width) { $width = $conf[$type . '.']['defaultWidth']; } $height = isset($conf['height.']) ? $this->cObj->stdWrap($conf['height'], $conf['height.']) : $conf['height']; if (!$height) { $height = $conf[$type . '.']['defaultHeight']; } $fullFilename = $filename; // If the file name doesn't contain a scheme, prefix with appropriate data if (strpos($filename, '://') === false && !empty($prefix)) { $fullFilename = $prefix . $filename; } $embed = 'var ' . $qtObject . ' = new QTObject("' . $fullFilename . '", "' . $replaceElementIdString . '", "' . $width . '", "' . $height . '");'; $content = $layout . ' <script type="text/javascript"> ' . $embed . ' ' . $params . ' </script>'; if (isset($conf['stdWrap.'])) { $content = $this->cObj->stdWrap($content, $conf['stdWrap.']); } return $content; }
/** * Initialize new row with unique uid * * @param array $result * @return array * @throws \InvalidArgumentException */ public function addData(array $result) { if ($result['command'] !== 'new') { return $result; } // Throw exception if uid is already set if (isset($result['databaseRow']['uid'])) { throw new \InvalidArgumentException('uid is already set to ' . $result['databaseRow']['uid'], 1437991120); } $result['databaseRow']['uid'] = StringUtility::getUniqueId('NEW'); return $result; }
/** * At the end of the import process all file and DB relations should be set properly (that is relations * to imported records are all re-created so imported records are correctly related again) * Relations in flexform fields are processed in setFlexFormRelations() after this function * * @return void * @see setFlexFormRelations() */ public function setRelations() { $updateData = array(); // import_newId contains a register of all records that was in the import memorys "records" key foreach ($this->import_newId as $nId => $dat) { $table = $dat['table']; $uid = $dat['uid']; // original UID - NOT the new one! // If the record has been written and received a new id, then proceed: if (is_array($this->import_mapId[$table]) && isset($this->import_mapId[$table][$uid])) { $thisNewUid = BackendUtility::wsMapId($table, $this->import_mapId[$table][$uid]); if (is_array($this->dat['records'][$table . ':' . $uid]['rels'])) { $thisNewPageUid = 0; if ($this->legacyImport) { if ($table != 'pages') { $oldPid = $this->dat['records'][$table . ':' . $uid]['data']['pid']; $thisNewPageUid = BackendUtility::wsMapId($table, $this->import_mapId['pages'][$oldPid]); } else { $thisNewPageUid = $thisNewUid; } } // Traverse relation fields of each record foreach ($this->dat['records'][$table . ':' . $uid]['rels'] as $field => $config) { // uid_local of sys_file_reference needs no update because the correct reference uid was already written // @see ImportExport::fixUidLocalInSysFileReferenceRecords() if ($table === 'sys_file_reference' && $field === 'uid_local') { continue; } switch ((string) $config['type']) { case 'db': if (is_array($config['itemArray']) && !empty($config['itemArray'])) { $itemConfig = $GLOBALS['TCA'][$table]['columns'][$field]['config']; $valArray = $this->setRelations_db($config['itemArray'], $itemConfig); $updateData[$table][$thisNewUid][$field] = implode(',', $valArray); } break; case 'file': if (is_array($config['newValueFiles']) && !empty($config['newValueFiles'])) { $valArr = array(); foreach ($config['newValueFiles'] as $fI) { $valArr[] = $this->import_addFileNameToBeCopied($fI); } if ($this->legacyImport && $this->legacyImportFolder === null && isset($this->legacyImportMigrationTables[$table][$field])) { // Do nothing - the legacy import folder is missing } elseif ($this->legacyImport && $this->legacyImportFolder !== null && isset($this->legacyImportMigrationTables[$table][$field])) { $refIds = array(); foreach ($valArr as $tempFile) { $fileName = $this->alternativeFileName[$tempFile]; $fileObject = null; try { // check, if there is alreay the same file in the folder if ($this->legacyImportFolder->hasFile($fileName)) { $fileStorage = $this->legacyImportFolder->getStorage(); $file = $fileStorage->getFile($this->legacyImportFolder->getIdentifier() . $fileName); if ($file->getSha1() === sha1_file($tempFile)) { $fileObject = $file; } } } catch (Exception $e) { } if ($fileObject === null) { try { $fileObject = $this->legacyImportFolder->addFile($tempFile, $fileName, DuplicationBehavior::RENAME); } catch (Exception $e) { $this->error('Error: no file could be added to the storage for file name' . $this->alternativeFileName[$tempFile]); } } if ($fileObject !== null) { $refId = StringUtility::getUniqueId('NEW'); $refIds[] = $refId; $updateData['sys_file_reference'][$refId] = array('uid_local' => $fileObject->getUid(), 'uid_foreign' => $thisNewUid, 'tablenames' => $table, 'fieldname' => $field, 'pid' => $thisNewPageUid, 'table_local' => 'sys_file'); } } $updateData[$table][$thisNewUid][$field] = implode(',', $refIds); if (!empty($this->legacyImportMigrationTables[$table][$field])) { $this->legacyImportMigrationRecords[$table][$thisNewUid][$field] = $refIds; } } else { $updateData[$table][$thisNewUid][$field] = implode(',', $valArr); } } break; } } } else { $this->error('Error: no record was found in data array!'); } } else { $this->error('Error: this records is NOT created it seems! (' . $table . ':' . $uid . ')'); } } if (!empty($updateData)) { $tce = $this->getNewTCE(); $tce->isImporting = true; $this->callHook('before_setRelation', array('tce' => &$tce, 'data' => &$updateData)); $tce->start($updateData, array()); $tce->process_datamap(); // Replace the temporary "NEW" ids with the final ones. foreach ($this->legacyImportMigrationRecords as $table => $records) { foreach ($records as $uid => $fields) { foreach ($fields as $field => $referenceIds) { foreach ($referenceIds as $key => $referenceId) { $this->legacyImportMigrationRecords[$table][$uid][$field][$key] = $tce->substNEWwithIDs[$referenceId]; } } } } $this->callHook('after_setRelations', array('tce' => &$tce)); } }
/** * This will render a single-line input form field, possibly with various control/validation features * * @return array As defined in initializeResultArray() of AbstractNode */ public function render() { /** @var IconFactory $iconFactory */ $iconFactory = GeneralUtility::makeInstance(IconFactory::class); $languageService = $this->getLanguageService(); $table = $this->data['tableName']; $fieldName = $this->data['fieldName']; $row = $this->data['databaseRow']; $parameterArray = $this->data['parameterArray']; $resultArray = $this->initializeResultArray(); $isDateField = false; $config = $parameterArray['fieldConf']['config']; $specConf = BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras']); $size = MathUtility::forceIntegerInRange($config['size'] ?: $this->defaultInputWidth, $this->minimumInputWidth, $this->maxInputWidth); $evalList = GeneralUtility::trimExplode(',', $config['eval'], true); $classes = array(); $attributes = array(); if (!isset($config['checkbox'])) { $config['checkbox'] = '0'; $checkboxIsset = false; } else { $checkboxIsset = true; } // set all date times available $dateFormats = array('date' => '%d-%m-%Y', 'year' => '%Y', 'time' => '%H:%M', 'timesec' => '%H:%M:%S'); if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat']) { $dateFormats['date'] = '%m-%d-%Y'; } $dateFormats['datetime'] = $dateFormats['time'] . ' ' . $dateFormats['date']; $dateFormats['datetimesec'] = $dateFormats['timesec'] . ' ' . $dateFormats['date']; // readonly if ($config['readOnly']) { $itemFormElValue = $parameterArray['itemFormElValue']; if (in_array('date', $evalList)) { $config['format'] = 'date'; } elseif (in_array('datetime', $evalList)) { $config['format'] = 'datetime'; } elseif (in_array('time', $evalList)) { $config['format'] = 'time'; } if (in_array('password', $evalList)) { $itemFormElValue = $itemFormElValue ? '*********' : ''; } $options = $this->data; $options['parameterArray'] = array('fieldConf' => array('config' => $config), 'itemFormElValue' => $itemFormElValue); $options['renderType'] = 'none'; return $this->nodeFactory->create($options)->render(); } if (in_array('datetime', $evalList, true) || in_array('date', $evalList)) { $classes[] = 't3js-datetimepicker'; $isDateField = true; if (in_array('datetime', $evalList)) { $attributes['data-date-type'] = 'datetime'; $dateFormat = $dateFormats['datetime']; } elseif (in_array('date', $evalList)) { $attributes['data-date-type'] = 'date'; $dateFormat = $dateFormats['date']; } if ($parameterArray['itemFormElValue'] > 0) { $parameterArray['itemFormElValue'] += date('Z', $parameterArray['itemFormElValue']); } if (isset($config['range']['lower'])) { $attributes['data-date-minDate'] = (int) $config['range']['lower']; } if (isset($config['range']['upper'])) { $attributes['data-date-maxDate'] = (int) $config['range']['upper']; } } elseif (in_array('time', $evalList)) { $dateFormat = $dateFormats['time']; $isDateField = true; $classes[] = 't3js-datetimepicker'; $attributes['data-date-type'] = 'time'; } elseif (in_array('timesec', $evalList)) { $dateFormat = $dateFormats['timesec']; $isDateField = true; $classes[] = 't3js-datetimepicker'; $attributes['data-date-type'] = 'timesec'; } else { if ($checkboxIsset === false) { $config['checkbox'] = ''; } } // @todo: The whole eval handling is a mess and needs refactoring foreach ($evalList as $func) { switch ($func) { case 'required': $attributes['data-formengine-validation-rules'] = $this->getValidationDataAsJsonString(array('required' => true)); break; default: // @todo: This is ugly: The code should find out on it's own whether a eval definition is a // @todo: keyword like "date", or a class reference. The global registration could be dropped then // Pair hook to the one in \TYPO3\CMS\Core\DataHandling\DataHandler::checkValue_input_Eval() if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$func])) { if (class_exists($func)) { $evalObj = GeneralUtility::makeInstance($func); if (method_exists($evalObj, 'deevaluateFieldValue')) { $_params = array('value' => $parameterArray['itemFormElValue']); $parameterArray['itemFormElValue'] = $evalObj->deevaluateFieldValue($_params); } } } } } $paramsList = array('field' => $parameterArray['itemFormElName'], 'evalList' => implode(',', $evalList), 'is_in' => trim($config['is_in']), 'checkbox' => $config['checkbox'] ? 1 : 0, 'checkboxValue' => $config['checkbox']); // set classes $classes[] = 'form-control'; $classes[] = 't3js-clearable'; $classes[] = 'hasDefaultValue'; // calculate attributes $attributes['data-formengine-validation-rules'] = $this->getValidationDataAsJsonString($config); $attributes['data-formengine-input-params'] = json_encode($paramsList); $attributes['data-formengine-input-name'] = htmlspecialchars($parameterArray['itemFormElName']); $attributes['id'] = StringUtility::getUniqueId('formengine-input-'); $attributes['value'] = ''; if (isset($config['max']) && (int) $config['max'] > 0) { $attributes['maxlength'] = (int) $config['max']; } if (!empty($classes)) { $attributes['class'] = implode(' ', $classes); } // This is the EDITABLE form field. if (!empty($config['placeholder'])) { $attributes['placeholder'] = trim($config['placeholder']); } if (isset($config['autocomplete'])) { $attributes['autocomplete'] = empty($config['autocomplete']) ? 'off' : 'on'; } // Build the attribute string $attributeString = ''; foreach ($attributes as $attributeName => $attributeValue) { $attributeString .= ' ' . $attributeName . '="' . htmlspecialchars($attributeValue) . '"'; } $html = ' <input type="text"' . $attributeString . $parameterArray['onFocus'] . ' />'; // This is the ACTUAL form field - values from the EDITABLE field must be transferred to this field which is the one that is written to the database. $html .= '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" />'; // Going through all custom evaluations configured for this field // @todo: Similar to above code! foreach ($evalList as $evalData) { if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$evalData])) { if (class_exists($evalData)) { $evalObj = GeneralUtility::makeInstance($evalData); if (method_exists($evalObj, 'returnFieldJS')) { $resultArray['extJSCODE'] .= LF . 'TBE_EDITOR.customEvalFunctions[' . GeneralUtility::quoteJSvalue($evalData) . '] = function(value) {' . $evalObj->returnFieldJS() . '}'; } } } } // add HTML wrapper if ($isDateField) { $html = ' <div class="input-group"> ' . $html . ' <span class="input-group-btn"> <label class="btn btn-default" for="' . $attributes['id'] . '"> ' . $iconFactory->getIcon('actions-edit-pick-date', Icon::SIZE_SMALL)->render() . ' </label> </span> </div>'; } // Wrap a wizard around the item? $html = $this->renderWizards(array($html), $config['wizards'], $table, $row, $fieldName, $parameterArray, $parameterArray['itemFormElName'], $specConf); // Add a wrapper to remain maximum width $width = (int) $this->formMaxWidth($size); $html = '<div class="form-control-wrap"' . ($width ? ' style="max-width: ' . $width . 'px"' : '') . '>' . $html . '</div>'; $resultArray['html'] = $html; return $resultArray; }
/** * This will render a <textarea> * * @return array As defined in initializeResultArray() of AbstractNode */ public function render() { $languageService = $this->getLanguageService(); $table = $this->data['tableName']; $fieldName = $this->data['fieldName']; $row = $this->data['databaseRow']; $parameterArray = $this->data['parameterArray']; $resultArray = $this->initializeResultArray(); $backendUser = $this->getBackendUserAuthentication(); $config = $parameterArray['fieldConf']['config']; // Setting columns number $cols = MathUtility::forceIntegerInRange($config['cols'] ?: $this->defaultInputWidth, $this->minimumInputWidth, $this->maxInputWidth); // Setting number of rows $rows = MathUtility::forceIntegerInRange($config['rows'] ?: 5, 1, 20); $originalRows = $rows; $itemFormElementValueLength = strlen($parameterArray['itemFormElValue']); if ($itemFormElementValueLength > $this->charactersPerRow * 2) { $cols = $this->maxInputWidth; $rows = MathUtility::forceIntegerInRange(round($itemFormElementValueLength / $this->charactersPerRow), count(explode(LF, $parameterArray['itemFormElValue'])), 20); if ($rows < $originalRows) { $rows = $originalRows; } } // must be called after the cols and rows calculation, so the parameters are applied // to read-only fields as well. // @todo: Same as in InputTextElement ... if ($config['readOnly']) { $config['cols'] = $cols; $config['rows'] = $rows; $options = $this->data; $options['parameterArray'] = array('fieldConf' => array('config' => $config), 'itemFormElValue' => $parameterArray['itemFormElValue']); $options['renderType'] = 'none'; return $this->nodeFactory->create($options)->render(); } $evalList = GeneralUtility::trimExplode(',', $config['eval'], true); // "Extra" configuration; Returns configuration for the field based on settings found in the "types" fieldlist. Traditionally, this is where RTE configuration has been found. $specialConfiguration = BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras']); $html = ''; // Show message, if no RTE (field can only be edited with RTE!) if ($specialConfiguration['rte_only']) { $html = '<p><em>' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noRTEfound')) . '</em></p>'; } else { $attributes = array(); // validation foreach ($evalList as $func) { if ($func === 'required') { $attributes['data-formengine-validation-rules'] = $this->getValidationDataAsJsonString(array('required' => true)); } else { // @todo: This is ugly: The code should find out on it's own whether a eval definition is a // @todo: keyword like "date", or a class reference. The global registration could be dropped then // Pair hook to the one in \TYPO3\CMS\Core\DataHandling\DataHandler::checkValue_input_Eval() // There is a similar hook for "evaluateFieldValue" in DataHandler and InputTextElement if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$func])) { if (class_exists($func)) { $evalObj = GeneralUtility::makeInstance($func); if (method_exists($evalObj, 'deevaluateFieldValue')) { $_params = array('value' => $parameterArray['itemFormElValue']); $parameterArray['itemFormElValue'] = $evalObj->deevaluateFieldValue($_params); } } } } } // calculate classes $classes = array(); $classes[] = 'form-control'; $classes[] = 't3js-formengine-textarea'; $classes[] = 'formengine-textarea'; if ($specialConfiguration['fixed-font']) { $classes[] = 'text-monospace'; } if ($specialConfiguration['enable-tab']) { $classes[] = 't3js-enable-tab'; } // calculate styles $styles = array(); // add the max-height from the users' preference to it $maximumHeight = (int) $backendUser->uc['resizeTextareas_MaxHeight']; if ($maximumHeight > 0) { $styles[] = 'max-height: ' . $maximumHeight . 'px'; } // calculate attributes $attributes['id'] = StringUtility::getUniqueId('formengine-textarea-'); $attributes['name'] = htmlspecialchars($parameterArray['itemFormElName']); $attributes['data-formengine-input-name'] = htmlspecialchars($parameterArray['itemFormElName']); if (!empty($styles)) { $attributes['style'] = implode(' ', $styles); } if (!empty($classes)) { $attributes['class'] = implode(' ', $classes); } $attributes['rows'] = $rows; $attributes['wrap'] = $specialConfiguration['nowrap'] ? 'off' : ($config['wrap'] ?: 'virtual'); $attributes['onChange'] = implode('', $parameterArray['fieldChangeFunc']); if (isset($config['max']) && (int) $config['max'] > 0) { $attributes['maxlength'] = (int) $config['max']; } $attributeString = ''; foreach ($attributes as $attributeName => $attributeValue) { $attributeString .= ' ' . $attributeName . '="' . htmlspecialchars($attributeValue) . '"'; } // Build the textarea $placeholderAttribute = ''; if (!empty($config['placeholder'])) { $placeholderAttribute = ' placeholder="' . htmlspecialchars(trim($config['placeholder'])) . '" '; } $html .= '<textarea' . $attributeString . $placeholderAttribute . '>' . htmlspecialchars($parameterArray['itemFormElValue']) . '</textarea>'; // Wrap a wizard around the item? $html = $this->renderWizards(array($html), $config['wizards'], $table, $row, $fieldName, $parameterArray, $parameterArray['itemFormElName'], $specialConfiguration, false); $maximumWidth = (int) $this->formMaxWidth($cols); $html = '<div class="form-control-wrap"' . ($maximumWidth ? ' style="max-width: ' . $maximumWidth . 'px"' : '') . '>' . $html . '</div>'; } $resultArray['html'] = $html; return $resultArray; }
/** * Creates SELECT query components for selecting fields ($select) from two/three tables joined * Use $mm_table together with $local_table or $foreign_table to select over two tables. Or use all three tables to select the full MM-relation. * The JOIN is done with [$local_table].uid <--> [$mm_table].uid_local / [$mm_table].uid_foreign <--> [$foreign_table].uid * The function is very useful for selecting MM-relations between tables adhering to the MM-format used by TCE (TYPO3 Core Engine). See the section on $GLOBALS['TCA'] in Inside TYPO3 for more details. * * @param string $select See exec_SELECT_mm_query() * @param string $local_table See exec_SELECT_mm_query() * @param string $mm_table See exec_SELECT_mm_query() * @param string $foreign_table See exec_SELECT_mm_query() * @param string $whereClause See exec_SELECT_mm_query() * @param string $groupBy See exec_SELECT_mm_query() * @param string $orderBy See exec_SELECT_mm_query() * @param string $limit See exec_SELECT_mm_query() * @return array SQL query components */ protected function getSelectMmQueryParts($select, $local_table, $mm_table, $foreign_table, $whereClause = '', $groupBy = '', $orderBy = '', $limit = '') { $foreign_table_as = $foreign_table == $local_table ? $foreign_table . StringUtility::getUniqueId('_join') : ''; $mmWhere = $local_table ? $local_table . '.uid=' . $mm_table . '.uid_local' : ''; $mmWhere .= ($local_table and $foreign_table) ? ' AND ' : ''; $tables = ($local_table ? $local_table . ',' : '') . $mm_table; if ($foreign_table) { $mmWhere .= ($foreign_table_as ?: $foreign_table) . '.uid=' . $mm_table . '.uid_foreign'; $tables .= ',' . $foreign_table . ($foreign_table_as ? ' AS ' . $foreign_table_as : ''); } return array('SELECT' => $select, 'FROM' => $tables, 'WHERE' => $mmWhere . ' ' . $whereClause, 'GROUPBY' => $groupBy, 'ORDERBY' => $orderBy, 'LIMIT' => $limit); }
/** * @param int $pageId * @param array $tableRecordData */ public function modifyRecords($pageId, array $tableRecordData) { $dataMap = array(); $currentUid = null; $previousTableName = null; $previousUid = null; foreach ($tableRecordData as $tableName => $recordData) { if (empty($recordData['uid'])) { continue; } $recordData = $this->resolvePreviousUid($recordData, $currentUid); $currentUid = $recordData['uid']; if ($recordData['uid'] === '__NEW') { $recordData['pid'] = $pageId; $currentUid = StringUtility::getUniqueId('NEW'); } unset($recordData['uid']); $dataMap[$tableName][$currentUid] = $recordData; if ($previousTableName !== null && $previousUid !== null) { $dataMap[$previousTableName][$previousUid] = $this->resolveNextUid($dataMap[$previousTableName][$previousUid], $currentUid); } $previousTableName = $tableName; $previousUid = $currentUid; } $this->createDataHandler(); $this->dataHandler->start($dataMap, array()); $this->dataHandler->process_datamap(); }
/** * Prints the selector box form-field for the db/file/select elements (multiple) * * @param string $fName Form element name * @param string $mode Mode "db", "file" (internal_type for the "group" type) OR blank (then for the "select" type) * @param string $allowed Commalist of "allowed * @param array $itemArray The array of items. For "select" and "group"/"file" this is just a set of value. For "db" its an array of arrays with table/uid pairs. * @param string $selector Alternative selector box. * @param array $params An array of additional parameters, eg: "size", "info", "headers" (array with "selector" and "items"), "noBrowser", "thumbnails * @param null $_ unused (onFocus in the past), will be removed in TYPO3 CMS 9 * @param string $table (optional) Table name processing for * @param string $field (optional) Field of table name processing for * @param string $uid (optional) uid of table record processing for * @param array $config (optional) The TCA field config * @return string The form fields for the selection. * @throws \UnexpectedValueException * @todo: Hack this mess into pieces and inline to group / select element depending on what they need */ protected function dbFileIcons($fName, $mode, $allowed, $itemArray, $selector = '', $params = [], $_ = null, $table = '', $field = '', $uid = '', $config = []) { $languageService = $this->getLanguageService(); $disabled = ''; if ($params['readOnly']) { $disabled = ' disabled="disabled"'; } // INIT $uidList = []; $opt = []; $itemArrayC = 0; // Creating <option> elements: if (is_array($itemArray)) { $itemArrayC = count($itemArray); switch ($mode) { case 'db': foreach ($itemArray as $pp) { $pRec = BackendUtility::getRecordWSOL($pp['table'], $pp['id']); if (is_array($pRec)) { $pTitle = BackendUtility::getRecordTitle($pp['table'], $pRec, false, true); $pUid = $pp['table'] . '_' . $pp['id']; $uidList[] = $pUid; $title = htmlspecialchars($pTitle); $opt[] = '<option value="' . htmlspecialchars($pUid) . '" title="' . $title . '">' . $title . '</option>'; } } break; case 'file_reference': case 'file': foreach ($itemArray as $item) { $itemParts = explode('|', $item); $uidList[] = $pUid = $pTitle = $itemParts[0]; $title = htmlspecialchars(rawurldecode($itemParts[1])); $opt[] = '<option value="' . htmlspecialchars(rawurldecode($itemParts[0])) . '" title="' . $title . '">' . $title . '</option>'; } break; case 'folder': foreach ($itemArray as $pp) { $pParts = explode('|', $pp); $uidList[] = $pUid = $pTitle = $pParts[0]; $title = htmlspecialchars(rawurldecode($pParts[0])); $opt[] = '<option value="' . htmlspecialchars(rawurldecode($pParts[0])) . '" title="' . $title . '">' . $title . '</option>'; } break; default: foreach ($itemArray as $pp) { $pParts = explode('|', $pp, 2); $uidList[] = $pUid = $pParts[0]; $pTitle = $pParts[1]; $title = htmlspecialchars(rawurldecode($pTitle)); $opt[] = '<option value="' . htmlspecialchars(rawurldecode($pUid)) . '" title="' . $title . '">' . $title . '</option>'; } } } // Create selector box of the options $sSize = $params['autoSizeMax'] ? MathUtility::forceIntegerInRange($itemArrayC + 1, MathUtility::forceIntegerInRange($params['size'], 1), $params['autoSizeMax']) : $params['size']; if (!$selector) { $maxItems = (int) ($params['maxitems'] ?? 0); $size = (int) ($params['size'] ?? 0); $classes = ['form-control', 'tceforms-multiselect']; if ($maxItems === 1) { $classes[] = 'form-select-no-siblings'; } $isMultiple = $maxItems !== 1 && $size !== 1; $selector = '<select id="' . StringUtility::getUniqueId('tceforms-multiselect-') . '" ' . ($params['noList'] ? 'style="display: none"' : 'size="' . $sSize . '" class="' . implode(' ', $classes) . '"') . ($isMultiple ? ' multiple="multiple"' : '') . ' data-formengine-input-name="' . htmlspecialchars($fName) . '" ' . $this->getValidationDataAsDataAttribute($config) . $params['style'] . $disabled . '>' . implode('', $opt) . '</select>'; } $icons = ['L' => [], 'R' => []]; $rOnClickInline = ''; if (!$params['readOnly'] && !$params['noList']) { if (!$params['noBrowser']) { // Check against inline uniqueness /** @var InlineStackProcessor $inlineStackProcessor */ $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class); $inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']); $aOnClickInline = ''; if ($this->data['isInlineChild'] && $this->data['inlineParentUid']) { if ($this->data['inlineParentConfig']['foreign_table'] === $table && $this->data['inlineParentConfig']['foreign_unique'] === $field) { $objectPrefix = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']) . '-' . $table; $aOnClickInline = $objectPrefix . '|inline.checkUniqueElement|inline.setUniqueElement'; $rOnClickInline = 'inline.revertUnique(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',null,' . GeneralUtility::quoteJSvalue($uid) . ');'; } } if (is_array($config['appearance']) && isset($config['appearance']['elementBrowserType'])) { $elementBrowserType = $config['appearance']['elementBrowserType']; } else { $elementBrowserType = $mode; } if (is_array($config['appearance']) && isset($config['appearance']['elementBrowserAllowed'])) { $elementBrowserAllowed = $config['appearance']['elementBrowserAllowed']; } else { $elementBrowserAllowed = $allowed; } $aOnClick = 'setFormValueOpenBrowser(' . GeneralUtility::quoteJSvalue($elementBrowserType) . ',' . GeneralUtility::quoteJSvalue($fName . '|||' . $elementBrowserAllowed . '|' . $aOnClickInline) . '); return false;'; $icons['R'][] = ' <a href="#" onclick="' . htmlspecialchars($aOnClick) . '" class="btn btn-default" title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.browse_' . ($mode == 'db' ? 'db' : 'file'))) . '"> ' . $this->iconFactory->getIcon('actions-insert-record', Icon::SIZE_SMALL)->render() . ' </a>'; } if (!$params['dontShowMoveIcons']) { if ($sSize >= 5) { $icons['L'][] = ' <a href="#" class="btn btn-default t3js-btn-moveoption-top" data-fieldname="' . $fName . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.move_to_top')) . '"> ' . $this->iconFactory->getIcon('actions-move-to-top', Icon::SIZE_SMALL)->render() . ' </a>'; } $icons['L'][] = ' <a href="#" class="btn btn-default t3js-btn-moveoption-up" data-fieldname="' . $fName . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.move_up')) . '"> ' . $this->iconFactory->getIcon('actions-move-up', Icon::SIZE_SMALL)->render() . ' </a>'; $icons['L'][] = ' <a href="#" class="btn btn-default t3js-btn-moveoption-down" data-fieldname="' . $fName . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.move_down')) . '"> ' . $this->iconFactory->getIcon('actions-move-down', Icon::SIZE_SMALL)->render() . ' </a>'; if ($sSize >= 5) { $icons['L'][] = ' <a href="#" class="btn btn-default t3js-btn-moveoption-bottom" data-fieldname="' . $fName . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.move_to_bottom')) . '"> ' . $this->iconFactory->getIcon('actions-move-to-bottom', Icon::SIZE_SMALL)->render() . ' </a>'; } } $clipElements = $this->getClipboardElements($allowed, $mode); if (!empty($clipElements)) { $aOnClick = ''; foreach ($clipElements as $elValue) { if ($mode == 'db') { list($itemTable, $itemUid) = explode('|', $elValue); $recordTitle = BackendUtility::getRecordTitle($itemTable, BackendUtility::getRecordWSOL($itemTable, $itemUid)); $itemTitle = GeneralUtility::quoteJSvalue($recordTitle); $elValue = $itemTable . '_' . $itemUid; } else { // 'file', 'file_reference' and 'folder' mode $itemTitle = 'unescape(' . GeneralUtility::quoteJSvalue(rawurlencode(basename($elValue))) . ')'; } $aOnClick .= 'setFormValueFromBrowseWin(' . GeneralUtility::quoteJSvalue($fName) . ',unescape(' . GeneralUtility::quoteJSvalue(rawurlencode(str_replace('%20', ' ', $elValue))) . '),' . $itemTitle . ',' . $itemTitle . ');'; } $aOnClick .= 'return false;'; $icons['R'][] = ' <a href="#" class="btn btn-default" onclick="' . htmlspecialchars($aOnClick) . '" title="' . htmlspecialchars(sprintf($languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.clipInsert_' . ($mode == 'db' ? 'db' : 'file')), count($clipElements))) . '"> ' . $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)->render() . ' </a>'; } } if (!$params['readOnly'] && !$params['noDelete']) { $icons['L'][] = ' <a href="#" class="btn btn-default t3js-btn-removeoption" onClick="' . $rOnClickInline . '" data-fieldname="' . $fName . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.remove_selected')) . '"> ' . $this->iconFactory->getIcon('actions-selection-delete', Icon::SIZE_SMALL)->render() . ' </a>'; } // Thumbnails $imagesOnly = false; if ($params['thumbnails'] && $params['allowed']) { // In case we have thumbnails, check if only images are allowed. // In this case, render them below the field, instead of to the right $allowedExtensionList = $params['allowed']; $imageExtensionList = GeneralUtility::trimExplode(',', strtolower($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']), true); $imagesOnly = true; foreach ($allowedExtensionList as $allowedExtension) { if (!ArrayUtility::inArray($imageExtensionList, $allowedExtension)) { $imagesOnly = false; break; } } } $thumbnails = ''; if (is_array($params['thumbnails']) && !empty($params['thumbnails'])) { if ($imagesOnly) { $thumbnails .= '<ul class="list-inline">'; foreach ($params['thumbnails'] as $thumbnail) { $thumbnails .= '<li><span class="thumbnail">' . $thumbnail['image'] . '</span></li>'; } $thumbnails .= '</ul>'; } else { $thumbnails .= '<div class="table-fit"><table class="table table-white"><tbody>'; foreach ($params['thumbnails'] as $thumbnail) { $thumbnails .= ' <tr> <td class="col-icon"> ' . ($config['internal_type'] === 'db' ? BackendUtility::wrapClickMenuOnIcon($thumbnail['image'], $thumbnail['table'], $thumbnail['uid'], 1, '', '+copy,info,edit,view') : $thumbnail['image']) . ' </td> <td class="col-title"> ' . ($config['internal_type'] === 'db' ? BackendUtility::wrapClickMenuOnIcon($thumbnail['name'], $thumbnail['table'], $thumbnail['uid'], 1, '', '+copy,info,edit,view') : $thumbnail['name']) . ' ' . ($config['internal_type'] === 'db' ? ' <span class="text-muted">[' . $thumbnail['uid'] . ']</span>' : '') . ' </td> </tr> '; } $thumbnails .= '</tbody></table></div>'; } } // Allowed Tables $allowedTables = ''; if (is_array($params['allowedTables']) && !empty($params['allowedTables']) && !$params['hideAllowedTables']) { $allowedTables .= '<div class="help-block">'; foreach ($params['allowedTables'] as $key => $item) { if (is_array($item)) { if (empty($params['readOnly'])) { $allowedTables .= '<a href="#" onClick="' . htmlspecialchars($item['onClick']) . '" class="btn btn-default">' . $item['icon'] . ' ' . htmlspecialchars($item['name']) . '</a> '; } else { $allowedTables .= '<span>' . htmlspecialchars($item['name']) . '</span> '; } } elseif ($key === 'name') { $allowedTables .= '<span>' . htmlspecialchars($item) . '</span> '; } } $allowedTables .= '</div>'; } // Allowed $allowedList = ''; if (is_array($params['allowed']) && !empty($params['allowed'])) { foreach ($params['allowed'] as $item) { $allowedList .= '<span class="label label-success">' . strtoupper($item) . '</span> '; } } // Disallowed $disallowedList = ''; if (is_array($params['disallowed']) && !empty($params['disallowed'])) { foreach ($params['disallowed'] as $item) { $disallowedList .= '<span class="label label-danger">' . strtoupper($item) . '</span> '; } } // Rightbox $rightbox = $params['rightbox'] ?: ''; // Hook: dbFileIcons_postProcess (requested by FAL-team for use with the "fal" extension) if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tceforms.php']['dbFileIcons'])) { foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tceforms.php']['dbFileIcons'] as $classRef) { $hookObject = GeneralUtility::getUserObj($classRef); if (!$hookObject instanceof DatabaseFileIconsHookInterface) { throw new \UnexpectedValueException($classRef . ' must implement interface ' . DatabaseFileIconsHookInterface::class, 1290167704); } $additionalParams = ['mode' => $mode, 'allowed' => $allowed, 'itemArray' => $itemArray, 'table' => $table, 'field' => $field, 'uid' => $uid, 'config' => $GLOBALS['TCA'][$table]['columns'][$field]]; $hookObject->dbFileIcons_postProcess($params, $selector, $thumbnails, $icons, $rightbox, $fName, $uidList, $additionalParams, $this); } } // Output $str = ' ' . ($params['headers']['selector'] ? '<label>' . $params['headers']['selector'] . '</label>' : '') . ' <div class="form-wizards-wrap form-wizards-aside"> <div class="form-wizards-element"> ' . $selector . ' ' . (!$params['noList'] && !empty($allowedTables) ? $allowedTables : '') . ' ' . (!$params['noList'] && (!empty($allowedList) || !empty($disallowedList)) ? '<div class="help-block">' . $allowedList . $disallowedList . ' </div>' : '') . ' </div> ' . (!empty($icons['L']) ? '<div class="form-wizards-items"><div class="btn-group-vertical">' . implode('', $icons['L']) . '</div></div>' : '') . ' ' . (!empty($icons['R']) ? '<div class="form-wizards-items"><div class="btn-group-vertical">' . implode('', $icons['R']) . '</div></div>' : '') . ' </div> '; if ($rightbox) { $str = ' <div class="form-multigroup-wrap t3js-formengine-field-group"> <div class="form-multigroup-item form-multigroup-element">' . $str . '</div> <div class="form-multigroup-item form-multigroup-element"> ' . ($params['headers']['items'] ? '<label>' . $params['headers']['items'] . '</label>' : '') . ' ' . ($params['headers']['selectorbox'] ? '<div class="form-multigroup-item-wizard">' . $params['headers']['selectorbox'] . '</div>' : '') . ' ' . $rightbox . ' </div> </div> '; } $str .= $thumbnails; // Creating the hidden field which contains the actual value as a comma list. $str .= '<input type="hidden" name="' . $fName . '" value="' . htmlspecialchars(implode(',', $uidList)) . '" />'; return $str; }
/** * @test */ public function getUniqueIdReturnsIdWithoutDot() { $this->assertNotContains('.', StringUtility::getUniqueId()); }
/** * Entry method * * @return array As defined in initializeResultArray() of AbstractNode */ public function render() { /** @var IconFactory $iconFactory */ $iconFactory = GeneralUtility::makeInstance(IconFactory::class); $languageService = $this->getLanguageService(); $flexFormFieldsArray = $this->data['flexFormDataStructureArray']; $flexFormRowData = $this->data['flexFormRowData']; $flexFormFieldIdentifierPrefix = $this->data['flexFormFieldIdentifierPrefix']; $flexFormSectionType = $this->data['flexFormSectionType']; $flexFormSectionTitle = $this->data['flexFormSectionTitle']; $userHasAccessToDefaultLanguage = $this->getBackendUserAuthentication()->checkLanguageAccess(0); $resultArray = $this->initializeResultArray(); // Creating IDs for form fields: // It's important that the IDs "cascade" - otherwise we can't dynamically expand the flex form // because this relies on simple string substitution of the first parts of the id values. $flexFormFieldIdentifierPrefix = $flexFormFieldIdentifierPrefix . '-' . GeneralUtility::shortMd5(uniqid('id', true)); // Render each existing container foreach ($flexFormRowData as $flexFormContainerCounter => $existingSectionContainerData) { // @todo: This relies on the fact that "_TOGGLE" is *below* the real data in the saved xml structure if (is_array($existingSectionContainerData)) { $existingSectionContainerDataStructureType = key($existingSectionContainerData); $existingSectionContainerData = $existingSectionContainerData[$existingSectionContainerDataStructureType]; $containerDataStructure = $flexFormFieldsArray[$existingSectionContainerDataStructureType]; // There may be cases where a field is still in DB but does not exist in definition if (is_array($containerDataStructure)) { $sectionTitle = ''; if (!empty($containerDataStructure['title'])) { $sectionTitle = $languageService->sL($containerDataStructure['title']); } $options = $this->data; $options['flexFormRowData'] = $existingSectionContainerData['el']; $options['flexFormDataStructureArray'] = $containerDataStructure['el']; $options['flexFormFieldIdentifierPrefix'] = $flexFormFieldIdentifierPrefix; $options['flexFormFormPrefix'] = $this->data['flexFormFormPrefix'] . '[' . $flexFormSectionType . ']' . '[el]'; $options['flexFormContainerName'] = $existingSectionContainerDataStructureType; $options['flexFormContainerCounter'] = $flexFormContainerCounter; $options['flexFormContainerTitle'] = $sectionTitle; $options['flexFormContainerElementCollapsed'] = (bool) $existingSectionContainerData['el']['_TOGGLE']; $options['renderType'] = 'flexFormContainerContainer'; $flexFormContainerContainerResult = $this->nodeFactory->create($options)->render(); $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $flexFormContainerContainerResult); } } } // "New container" handling: Creates a "template" of each possible container and stuffs it // somewhere into DOM to be handled with JS magic. // Fun part: Handle the fact that such things may be set for children $containerTemplatesHtml = array(); foreach ($flexFormFieldsArray as $flexFormContainerName => $flexFormFieldDefinition) { $containerTemplateHtml = array(); $sectionTitle = ''; if (!empty($flexFormFieldDefinition['title'])) { $sectionTitle = $languageService->sL($flexFormFieldDefinition['title']); } $options = $this->data; // @todo: this should use the prepared templateRow parallel to the single elements to have support of default values! $options['flexFormRowData'] = array(); $options['flexFormDataStructureArray'] = $flexFormFieldDefinition['el']; $options['flexFormFieldIdentifierPrefix'] = $flexFormFieldIdentifierPrefix; $options['flexFormFormPrefix'] = $this->data['flexFormFormPrefix'] . '[' . $flexFormSectionType . ']' . '[el]'; $options['flexFormContainerName'] = $flexFormContainerName; $options['flexFormContainerCounter'] = $flexFormFieldIdentifierPrefix . '-form'; $options['flexFormContainerTitle'] = $sectionTitle; $options['flexFormContainerElementCollapsed'] = false; $options['renderType'] = 'flexFormContainerContainer'; $flexFormContainerContainerTemplateResult = $this->nodeFactory->create($options)->render(); $uniqueId = StringUtility::getUniqueId('idvar'); $identifierPrefixJs = 'replace(/' . $flexFormFieldIdentifierPrefix . '-/g,"' . $flexFormFieldIdentifierPrefix . '-"+' . $uniqueId . '+"-")'; $identifierPrefixJs .= '.replace(/(tceforms-(datetime|date)field-)/g,"$1" + (new Date()).getTime())'; $onClickInsert = array(); $onClickInsert[] = 'var ' . $uniqueId . ' = "' . 'idx"+(new Date()).getTime();'; $onClickInsert[] = 'TYPO3.jQuery("#' . $flexFormFieldIdentifierPrefix . '").append(TYPO3.jQuery(' . json_encode($flexFormContainerContainerTemplateResult['html']) . '.' . $identifierPrefixJs . '));'; $onClickInsert[] = 'TYPO3.jQuery("#' . $flexFormFieldIdentifierPrefix . '").t3FormEngineFlexFormElement();'; $onClickInsert[] = 'eval(unescape("' . rawurlencode(implode(';', $flexFormContainerContainerTemplateResult['additionalJavaScriptPost'])) . '").' . $identifierPrefixJs . ');'; $onClickInsert[] = 'TBE_EDITOR.addActionChecks("submit", unescape("' . rawurlencode(implode(';', $flexFormContainerContainerTemplateResult['additionalJavaScriptSubmit'])) . '").' . $identifierPrefixJs . ');'; $onClickInsert[] = 'TYPO3.FormEngine.reinitialize();'; $onClickInsert[] = 'return false;'; $containerTemplateHtml[] = '<a href="#" class="btn btn-default" onclick="' . htmlspecialchars(implode(LF, $onClickInsert)) . '">'; $containerTemplateHtml[] = $iconFactory->getIcon('actions-document-new', Icon::SIZE_SMALL)->render(); $containerTemplateHtml[] = htmlspecialchars(GeneralUtility::fixed_lgd_cs($sectionTitle, 30)); $containerTemplateHtml[] = '</a>'; $containerTemplatesHtml[] = implode(LF, $containerTemplateHtml); $flexFormContainerContainerTemplateResult['html'] = ''; $flexFormContainerContainerTemplateResult['additionalJavaScriptPost'] = array(); $flexFormContainerContainerTemplateResult['additionalJavaScriptSubmit'] = array(); $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $flexFormContainerContainerTemplateResult); } // Create new elements links $createElementsHtml = array(); if ($userHasAccessToDefaultLanguage) { $createElementsHtml[] = '<div class="t3-form-field-add-flexsection">'; $createElementsHtml[] = '<div class="btn-group">'; $createElementsHtml[] = implode('|', $containerTemplatesHtml); $createElementsHtml[] = '</div>'; $createElementsHtml[] = '</div>'; } // Wrap child stuff $toggleAll = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.toggleall', true); $html = array(); $html[] = '<div class="panel panel-tab">'; $html[] = '<div class="panel-body">'; $html[] = '<div class="t3-form-field-container t3-form-flex">'; $html[] = '<div class="t3-form-field-label-flexsection">'; $html[] = '<h4>'; $html[] = htmlspecialchars($flexFormSectionTitle); $html[] = '</h4>'; $html[] = '</div>'; $html[] = '<div class="t3js-form-field-toggle-flexsection t3-form-flexsection-toggle">'; $html[] = '<a class="btn btn-default" href="#" title="' . $toggleAll . '">'; $html[] = $iconFactory->getIcon('actions-move-right', Icon::SIZE_SMALL)->render() . $toggleAll; $html[] = '</a>'; $html[] = '</div>'; $html[] = '<div'; $html[] = 'id="' . $flexFormFieldIdentifierPrefix . '"'; $html[] = 'class="panel-group panel-hover t3-form-field-container-flexsection t3-flex-container"'; $html[] = 'data-t3-flex-allow-restructure="' . ($userHasAccessToDefaultLanguage ? '1' : '0') . '"'; $html[] = '>'; $html[] = $resultArray['html']; $html[] = '</div>'; $html[] = implode(LF, $createElementsHtml); $html[] = '</div>'; $html[] = '</div>'; $html[] = '</div>'; $resultArray['html'] = implode(LF, $html); return $resultArray; }
/** * Renders a <select> element * * @param array $optionElements List of rendered <option> elements * @param array $parameterArray * @param array $config Field configuration * @return string */ protected function renderSelectElement(array $optionElements, array $parameterArray, array $config) { $selectItems = $parameterArray['fieldConf']['config']['items']; $size = (int) $config['size']; $cssPrefix = $size === 1 ? 'tceforms-select' : 'tceforms-multiselect'; if ($config['autoSizeMax']) { $size = MathUtility::forceIntegerInRange(count($selectItems) + 1, MathUtility::forceIntegerInRange($size, 1), $config['autoSizeMax']); } $attributes = ['name' => $parameterArray['itemFormElName'] . '[]', 'multiple' => 'multiple', 'onchange' => implode('', $parameterArray['fieldChangeFunc']), 'id' => StringUtility::getUniqueId($cssPrefix), 'class' => 'form-control ' . $cssPrefix]; if ($size) { $attributes['size'] = $size; } if ($config['readOnly']) { $attributes['disabled'] = 'disabled'; } if (isset($config['itemListStyle'])) { $attributes['style'] = $config['itemListStyle']; } $html = ['<select ' . $this->implodeAttributes($attributes) . ' ' . $parameterArray['onFocus'] . ' ' . $this->getValidationDataAsDataAttribute($config) . '>', implode(LF, $optionElements), '</select>']; return implode(LF, $html); }
/** * Render side by side element. * * @return array As defined in initializeResultArray() of AbstractNode */ public function render() { $table = $this->data['tableName']; $field = $this->data['fieldName']; $parameterArray = $this->data['parameterArray']; // Field configuration from TCA: $config = $parameterArray['fieldConf']['config']; // Creating the label for the "No Matching Value" entry. $noMatchingLabel = isset($parameterArray['fieldTSConfig']['noMatchingValue_label']) ? $this->getLanguageService()->sL($parameterArray['fieldTSConfig']['noMatchingValue_label']) : '[ ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue') . ' ]'; $selItems = $config['items']; $html = ''; $disabled = ''; if ($config['readOnly']) { $disabled = ' disabled="disabled"'; } // Setting this hidden field (as a flag that JavaScript can read out) if (!$disabled) { $html .= '<input type="hidden" data-formengine-input-name="' . htmlspecialchars($parameterArray['itemFormElName']) . '" value="' . ($config['multiple'] ? 1 : 0) . '" />'; } // Set max and min items: $maxitems = MathUtility::forceIntegerInRange($config['maxitems'], 0); if (!$maxitems) { $maxitems = 100000; } // Get "removeItems": $removeItems = GeneralUtility::trimExplode(',', $parameterArray['fieldTSConfig']['removeItems'], true); // Get the array with selected items: $itemsArray = $parameterArray['itemFormElValue'] ?: []; // Perform modification of the selected items array: // @todo: this part should probably be moved to TcaSelectItems provider?! foreach ($itemsArray as $itemNumber => $itemValue) { $itemArray = array(0 => $itemValue, 1 => ''); $itemIcon = null; $isRemoved = in_array($itemValue, $removeItems) || $config['type'] == 'select' && $config['authMode'] && !$this->getBackendUserAuthentication()->checkAuthMode($table, $field, $itemValue, $config['authMode']); if ($isRemoved && !$parameterArray['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) { $itemArray[1] = rawurlencode(@sprintf($noMatchingLabel, $itemValue)); } else { if (isset($parameterArray['fieldTSConfig']['altLabels.'][$itemValue])) { $itemArray[1] = rawurlencode($this->getLanguageService()->sL($parameterArray['fieldTSConfig']['altLabels.'][$itemValue])); } if (isset($parameterArray['fieldTSConfig']['altIcons.'][$itemValue])) { $itemArray[2] = $parameterArray['fieldTSConfig']['altIcons.'][$itemValue]; } } if ($itemArray[1] === '') { foreach ($selItems as $selItem) { if ($selItem[1] == $itemValue) { $itemArray[1] = $selItem[0]; break; } } } $itemsArray[$itemNumber] = implode('|', $itemArray); } // size must be at least two, as there are always maxitems > 1 (see parent function) if (isset($config['size'])) { $size = (int) $config['size']; } else { $size = 2; } $size = $config['autoSizeMax'] ? MathUtility::forceIntegerInRange(count($itemsArray) + 1, MathUtility::forceIntegerInRange($size, 1), $config['autoSizeMax']) : $size; $itemsToSelect = []; $filterTextfield = []; $filterSelectbox = ''; if (!$disabled) { // Create option tags: $opt = array(); foreach ($selItems as $p) { $opt[] = '<option value="' . htmlspecialchars($p[1]) . '" title="' . $p[0] . '">' . $p[0] . '</option>'; } // Put together the selector box: $selector_itemListStyle = isset($config['itemListStyle']) ? ' style="' . htmlspecialchars($config['itemListStyle']) . '"' : ''; $sOnChange = implode('', $parameterArray['fieldChangeFunc']); $multiSelectId = StringUtility::getUniqueId('tceforms-multiselect-'); $itemsToSelect[] = '<select data-relatedfieldname="' . htmlspecialchars($parameterArray['itemFormElName']) . '" ' . 'data-exclusivevalues="' . htmlspecialchars($config['exclusiveKeys']) . '" ' . 'id="' . $multiSelectId . '" ' . 'data-formengine-input-name="' . htmlspecialchars($parameterArray['itemFormElName']) . '" ' . 'class="form-control t3js-formengine-select-itemstoselect" ' . ($size ? ' size="' . $size . '" ' : '') . 'onchange="' . htmlspecialchars($sOnChange) . '" ' . $parameterArray['onFocus'] . $this->getValidationDataAsDataAttribute($config) . $selector_itemListStyle . '>'; $itemsToSelect[] = implode(LF, $opt); $itemsToSelect[] = '</select>'; // enable filter functionality via a text field if ($config['enableMultiSelectFilterTextfield']) { $filterTextfield[] = '<span class="input-group input-group-sm">'; $filterTextfield[] = '<span class="input-group-addon">'; $filterTextfield[] = '<span class="fa fa-filter"></span>'; $filterTextfield[] = '</span>'; $filterTextfield[] = '<input class="t3js-formengine-multiselect-filter-textfield form-control" value="">'; $filterTextfield[] = '</span>'; } // enable filter functionality via a select if (isset($config['multiSelectFilterItems']) && is_array($config['multiSelectFilterItems']) && count($config['multiSelectFilterItems']) > 1) { $filterDropDownOptions = array(); foreach ($config['multiSelectFilterItems'] as $optionElement) { $optionValue = $this->getLanguageService()->sL(isset($optionElement[1]) && $optionElement[1] != '' ? $optionElement[1] : $optionElement[0]); $filterDropDownOptions[] = '<option value="' . htmlspecialchars($this->getLanguageService()->sL($optionElement[0])) . '">' . htmlspecialchars($optionValue) . '</option>'; } $filterSelectbox = '<select class="form-control input-sm t3js-formengine-multiselect-filter-dropdown">' . implode(LF, $filterDropDownOptions) . '</select>'; } } if (!empty(trim($filterSelectbox)) && !empty($filterTextfield)) { $filterSelectbox = '<div class="form-multigroup-item form-multigroup-element">' . $filterSelectbox . '</div>'; $filterTextfield = '<div class="form-multigroup-item form-multigroup-element">' . implode(LF, $filterTextfield) . '</div>'; $selectBoxFilterContents = '<div class="t3js-formengine-multiselect-filter-container form-multigroup-wrap">' . $filterSelectbox . $filterTextfield . '</div>'; } else { $selectBoxFilterContents = trim($filterSelectbox . ' ' . implode(LF, $filterTextfield)); } // Pass to "dbFileIcons" function: $params = array('size' => $size, 'autoSizeMax' => MathUtility::forceIntegerInRange($config['autoSizeMax'], 0), 'style' => isset($config['selectedListStyle']) ? ' style="' . htmlspecialchars($config['selectedListStyle']) . '"' : '', 'dontShowMoveIcons' => $maxitems <= 1, 'maxitems' => $maxitems, 'info' => '', 'headers' => array('selector' => $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.selected'), 'items' => $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.items'), 'selectorbox' => $selectBoxFilterContents), 'noBrowser' => 1, 'rightbox' => implode(LF, $itemsToSelect), 'readOnly' => $disabled); $html .= $this->dbFileIcons($parameterArray['itemFormElName'], '', '', $itemsArray, '', $params, $parameterArray['onFocus']); // Wizards: if (!$disabled) { $html = $this->renderWizards(array($html), $config['wizards'], $table, $this->data['databaseRow'], $field, $parameterArray, $parameterArray['itemFormElName'], BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras'])); } $resultArray = $this->initializeResultArray(); $resultArray['html'] = $html; return $resultArray; }
/** * Creates a single-selector box * * @param string $table See getSingleField_typeSelect() * @param string $field See getSingleField_typeSelect() * @param array $row See getSingleField_typeSelect() * @param array $parameterArray See getSingleField_typeSelect() * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience) * @param array $selectItems Items available for selection * @param string $noMatchingLabel Label for no-matching-value * @return string The HTML code for the item */ protected function getSingleField_typeSelect_single($table, $field, $row, $parameterArray, $config, $selectItems, $noMatchingLabel) { // Initialization: $selectId = StringUtility::getUniqueId('tceforms-select-'); $selectedIndex = 0; $selectedIcon = ''; $selectedValueFound = FALSE; $onlySelectedIconShown = FALSE; $size = (int) $config['size']; // Style set on <select/> $out = ''; $options = ''; $disabled = FALSE; if ($config['readOnly']) { $disabled = TRUE; $onlySelectedIconShown = TRUE; } // Icon configuration: if ($config['suppress_icons'] === 'IF_VALUE_FALSE') { $suppressIcons = empty($parameterArray['itemFormElValue']); } elseif ($config['suppress_icons'] === 'ONLY_SELECTED') { $suppressIcons = FALSE; $onlySelectedIconShown = TRUE; } elseif ($config['suppress_icons']) { $suppressIcons = TRUE; } else { $suppressIcons = FALSE; } // Prepare groups $selectItemCounter = 0; $selectItemGroupCount = 0; $selectItemGroups = array(); $selectIcons = array(); $selectedValue = ''; if (!empty($parameterArray['itemFormElValue'])) { $selectedValue = (string) $parameterArray['itemFormElValue']; } foreach ($selectItems as $item) { if ($item[1] === '--div--') { // IS OPTGROUP if ($selectItemCounter !== 0) { $selectItemGroupCount++; } $selectItemGroups[$selectItemGroupCount]['header'] = array('title' => $item[0], 'icon' => !empty($item[2]) ? FormEngineUtility::getIconHtml($item[2]) : ''); } else { // IS ITEM $title = htmlspecialchars($item['0'], ENT_COMPAT, 'UTF-8', FALSE); $selected = $selectedValue === (string) $item[1]; if ($selected) { $selectedIndex = $selectItemCounter; $selectedIcon = $icon; $selectedValueFound = TRUE; } $icon = !empty($item[2]) ? $this->getIconHtml($item[2], $selected) : ''; $selectItemGroups[$selectItemGroupCount]['items'][] = array('title' => $title, 'value' => $item[1], 'icon' => $icon, 'selected' => $selected, 'index' => $selectItemCounter); // ICON if ($icon && !$suppressIcons && (!$onlySelectedIconShown || $selected)) { $onClick = 'document.editform[' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . '].selectedIndex=' . $selectItemCounter . ';'; $onClick .= implode('', $parameterArray['fieldChangeFunc']); $onClick .= 'this.blur();return false;'; $selectIcons[] = array('title' => $title, 'icon' => $icon, 'index' => $selectItemCounter, 'onClick' => $onClick); } $selectItemCounter++; } } // No-matching-value: if ($selectedValue && !$selectedValueFound && !$parameterArray['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) { $noMatchingLabel = @sprintf($noMatchingLabel, $selectedValue); $options = '<option value="' . htmlspecialchars($selectedValue) . '" selected="selected">' . htmlspecialchars($noMatchingLabel) . '</option>'; } elseif (!$selectedIcon && $selectItemGroups[0]['items'][0]['icon']) { $selectedIcon = $selectItemGroups[0]['items'][0]['icon']; } // Process groups foreach ($selectItemGroups as $selectItemGroup) { // suppress groups without items if (empty($selectItemGroup['items'])) { continue; } $optionGroup = is_array($selectItemGroup['header']); $options .= $optionGroup ? '<optgroup label="' . htmlspecialchars($selectItemGroup['header']['title'], ENT_COMPAT, 'UTF-8', FALSE) . '">' : ''; if (is_array($selectItemGroup['items'])) { foreach ($selectItemGroup['items'] as $item) { $options .= '<option value="' . htmlspecialchars($item['value']) . '" data-icon="' . htmlspecialchars($item['icon']) . '"' . ($item['selected'] ? ' selected="selected"' : '') . '>' . $item['title'] . '</option>'; } } $options .= $optionGroup ? '</optgroup>' : ''; } // Create item form fields: $sOnChange = 'if (this.options[this.selectedIndex].value==\'--div--\') {this.selectedIndex=' . $selectedIndex . ';} '; $sOnChange .= implode('', $parameterArray['fieldChangeFunc']); // Build the element $out .= ' <div class="form-control-wrap"> <select' . ' id="' . $selectId . '"' . ' name="' . htmlspecialchars($parameterArray['itemFormElName']) . '"' . $this->getValidationDataAsDataAttribute($config) . ' class="form-control form-control-adapt"' . ($size ? ' size="' . $size . '"' : '') . ' onchange="' . htmlspecialchars($sOnChange) . '"' . $parameterArray['onFocus'] . ($disabled ? ' disabled="disabled"' : '') . '> ' . $options . ' </select> </div>'; // Create icon table: if (!empty($selectIcons) && !$config['noIconsBelowSelect']) { $out .= '<ul name="IconSelectTable-' . $PA['itemFormElName'] . '" class="list-inline __icon-font-selector">'; $selectIconTotalCount = count($selectIcons); for ($selectIconCount = 0; $selectIconCount < $selectIconTotalCount; $selectIconCount++) { $out .= '<li>'; if (is_array($selectIcons[$selectIconCount])) { $out .= !$onlySelectedIconShown ? '<a href="#" title="' . $selectIcons[$selectIconCount]['title'] . '" onClick="' . htmlspecialchars($selectIcons[$selectIconCount]['onClick']) . '">' : ''; $out .= $selectIcons[$selectIconCount]['icon']; $out .= !$onlySelectedIconShown ? '</a>' : ''; } $out .= '</li>'; } $out .= '</ul>'; } return $out; }
/** * Inserts a record in the database, passing TCA configuration values through checkValue() but otherwise does NOTHING and checks nothing regarding permissions. * Passes the "version" parameter to insertDB() so the copy will look like a new version in the log - should probably be changed or modified a bit for more broad usage... * * @param string $table Table name * @param array $fieldArray Field array to insert as a record * @param int $realPid The value of PID field. -1 is indication that we are creating a new version! * @return int Returns the new ID of the record (if applicable) */ public function insertNewCopyVersion($table, $fieldArray, $realPid) { $id = StringUtility::getUniqueId('NEW'); // $fieldArray is set as current record. // The point is that when new records are created as copies with flex type fields there might be a field containing information about which DataStructure to use and without that information the flexforms cannot be correctly processed.... This should be OK since the $checkValueRecord is used by the flexform evaluation only anyways... $this->checkValue_currentRecord = $fieldArray; // Makes sure that transformations aren't processed on the copy. $backupDontProcessTransformations = $this->dontProcessTransformations; $this->dontProcessTransformations = true; // Traverse record and input-process each value: foreach ($fieldArray as $field => $fieldValue) { if (isset($GLOBALS['TCA'][$table]['columns'][$field])) { // Evaluating the value. $res = $this->checkValue($table, $field, $fieldValue, $id, 'new', $realPid, 0); if (isset($res['value'])) { $fieldArray[$field] = $res['value']; } } } // System fields being set: if ($GLOBALS['TCA'][$table]['ctrl']['crdate']) { $fieldArray[$GLOBALS['TCA'][$table]['ctrl']['crdate']] = $GLOBALS['EXEC_TIME']; } if ($GLOBALS['TCA'][$table]['ctrl']['cruser_id']) { $fieldArray[$GLOBALS['TCA'][$table]['ctrl']['cruser_id']] = $this->userid; } if ($GLOBALS['TCA'][$table]['ctrl']['tstamp']) { $fieldArray[$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = $GLOBALS['EXEC_TIME']; } // Finally, insert record: $this->insertDB($table, $id, $fieldArray, true); // Resets dontProcessTransformations to the previous state. $this->dontProcessTransformations = $backupDontProcessTransformations; // Return new id: return $this->substNEWwithIDs[$id]; }
/** * This method creates the "cli_scheduler" BE user if it doesn't exist * * @return void */ protected function createSchedulerUser() { // Check _cli_scheduler user status $checkUser = $this->checkSchedulerUser(); // Prepare default message $message = $this->getLanguageService()->getLL('msg.userExists'); $severity = FlashMessage::WARNING; // If the user does not exist, try creating it if ($checkUser == -1) { // Prepare necessary data for _cli_scheduler user creation $password = StringUtility::getUniqueId('scheduler'); if (SaltedPasswordsUtility::isUsageEnabled()) { $objInstanceSaltedPW = SaltFactory::getSaltingInstance(); $password = $objInstanceSaltedPW->getHashedPassword($password); } $data = array('be_users' => array('NEW' => array('username' => '_cli_scheduler', 'password' => $password, 'pid' => 0))); /** @var $tcemain \TYPO3\CMS\Core\DataHandling\DataHandler */ $tcemain = GeneralUtility::makeInstance(\TYPO3\CMS\Core\DataHandling\DataHandler::class); $tcemain->start($data, array()); $tcemain->process_datamap(); // Check if a new uid was indeed generated (i.e. a new record was created) // (counting TCEmain errors doesn't work as some failures don't report errors) $numberOfNewIDs = count($tcemain->substNEWwithIDs); if ($numberOfNewIDs === 1) { $message = $this->getLanguageService()->getLL('msg.userCreated'); $severity = FlashMessage::OK; } else { $message = $this->getLanguageService()->getLL('msg.userNotCreated'); $severity = FlashMessage::ERROR; } } $this->addMessage($message, $severity); }
/** * @return array */ public function render() { $this->initializeResultArray(); /** @var \TYPO3\CMS\Core\Authentication\BackendUserAuthentication $beUser */ $beUser = $GLOBALS['BE_USER']; /** @var string $showWizardByDefault */ $showWizardByDefault = $beUser->getTSConfigVal('setup.default.tx_form.showWizardByDefault'); if ((int) $showWizardByDefault == 0) { $content = ''; $record = $this->getRepository()->getRecord($this->getCurrentUid(), 'tt_content'); if ($record) { $content = $record->getBodytext(); } $id = StringUtility::getUniqueId('formengine-textarea-'); $this->resultArray['html'] = '<textarea id="formengine-textarea-' . $id . '"' . ' class="form-control t3js-formengine-textarea formengine-textarea" wrap="off"' . ' onchange="TBE_EDITOR.fieldChanged(\'tt_content\',\'' . $this->getCurrentUid() . '\',\'bodytext\',\'data[tt_content][' . $this->getCurrentUid() . '][bodytext]\');"' . ' rows="15" style="" name="data[tt_content][' . $this->getCurrentUid() . '][bodytext]">' . $content . '</textarea>'; return $this->resultArray; } $this->resultAddWizardCss(); $this->resultArray['additionalInlineLanguageLabelFiles'] += $this->getLocalization(); $settings = $this->getPlainPageWizardModTsConfigSettingsProperties(); $settingsCommand = $this->resultAddWizardSettingsJson($settings); $this->resultArray['requireJsModules'][] = array('TYPO3/CMS/Form/Wizard' => "function(){\n" . "\t" . $settingsCommand . "\n" . '}'); $attributes = []; $attributes['id'] = StringUtility::getUniqueId('formengine-form-wizard-'); /** * @see TYPO3.CMS/src/typo3/sysext/backend/Classes/Form/Element/AbstractFormElement.php:267 for wizard type=popup */ $parameterArray = $this->data['parameterArray']; $attributes['name'] = $parameterArray['itemFormElName']; $attributeString = ''; foreach ($attributes as $attributeName => $attributeValue) { $attributeString .= ' ' . $attributeName . '="' . htmlspecialchars($attributeValue) . '"'; } $input = '<input ' . $attributeString . ' type="hidden" />' . "\n"; $content = $input . '<div id="form-wizard-element"></div>'; $this->resultArray['html'] = '<div id="form-wizard-element-container" rel="' . $attributes['id'] . '">' . "\n" . $content . "\n" . '</div>'; return $this->resultArray; }
/** * Test gdlib functions * * @return \TYPO3\CMS\Install\Status\StatusInterface */ protected function gdlib() { $this->setUpDatabaseConnectionMock(); $imageProcessor = $this->initializeImageProcessor(); $parseTimeStart = GeneralUtility::milliseconds(); $gifOrPng = $imageProcessor->gifExtension; $testResults = array(); // GD with simple box $imageProcessor->IM_commands = array(); $image = imagecreatetruecolor(170, 136); $backgroundColor = imagecolorallocate($image, 0, 0, 0); imagefilledrectangle($image, 0, 0, 170, 136, $backgroundColor); $workArea = array(0, 0, 170, 136); $conf = array('dimensions' => '10,50,150,36', 'color' => 'olive'); $imageProcessor->makeBox($image, $conf, $workArea); $outputFile = $imageProcessor->tempPath . $imageProcessor->filenamePrefix . StringUtility::getUniqueId('gdSimple') . '.' . $gifOrPng; $imageProcessor->ImageWrite($image, $outputFile); $result = $imageProcessor->getImageDimensions($outputFile); $testResults['simple'] = array(); $testResults['simple']['title'] = 'Create simple image'; $testResults['simple']['outputFile'] = $result[3]; $testResults['simple']['referenceFile'] = $this->imageBasePath . 'TestReference/Gdlib-simple.' . $gifOrPng; // GD from image with box $imageProcessor->IM_commands = array(); $inputFile = $this->imageBasePath . 'TestInput/Test.' . $gifOrPng; $image = $imageProcessor->imageCreateFromFile($inputFile); $workArea = array(0, 0, 170, 136); $conf = array('dimensions' => '10,50,150,36', 'color' => 'olive'); $imageProcessor->makeBox($image, $conf, $workArea); $outputFile = $imageProcessor->tempPath . $imageProcessor->filenamePrefix . StringUtility::getUniqueId('gdBox') . '.' . $gifOrPng; $imageProcessor->ImageWrite($image, $outputFile); $result = $imageProcessor->getImageDimensions($outputFile); $testResults['box'] = array(); $testResults['box']['title'] = 'Create image from file'; $testResults['box']['outputFile'] = $result[3]; $testResults['box']['referenceFile'] = $this->imageBasePath . 'TestReference/Gdlib-box.' . $gifOrPng; // GD with text $imageProcessor->IM_commands = array(); $image = imagecreatetruecolor(170, 136); $backgroundColor = imagecolorallocate($image, 128, 128, 150); imagefilledrectangle($image, 0, 0, 170, 136, $backgroundColor); $workArea = array(0, 0, 170, 136); $conf = array('iterations' => 1, 'angle' => 0, 'antiAlias' => 1, 'text' => 'HELLO WORLD', 'fontColor' => '#003366', 'fontSize' => 18, 'fontFile' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('install') . 'Resources/Private/Font/vera.ttf', 'offset' => '17,40'); $conf['BBOX'] = $imageProcessor->calcBBox($conf); $imageProcessor->makeText($image, $conf, $workArea); $outputFile = $imageProcessor->tempPath . $imageProcessor->filenamePrefix . StringUtility::getUniqueId('gdText') . '.' . $gifOrPng; $imageProcessor->ImageWrite($image, $outputFile); $result = $imageProcessor->getImageDimensions($outputFile); $testResults['text'] = array(); $testResults['text']['title'] = 'Render text with TrueType font'; $testResults['text']['outputFile'] = $result[3]; $testResults['text']['referenceFile'] = $this->imageBasePath . 'TestReference/Gdlib-text.' . $gifOrPng; // GD with text, niceText $testResults['niceText'] = array(); if ($this->isImageMagickEnabledAndConfigured()) { // Warning: Re-uses $conf from above! $conf['offset'] = '17,65'; $conf['niceText'] = 1; $imageProcessor->makeText($image, $conf, $workArea); $outputFile = $imageProcessor->tempPath . $imageProcessor->filenamePrefix . StringUtility::getUniqueId('gdNiceText') . '.' . $gifOrPng; $imageProcessor->ImageWrite($image, $outputFile); $result = $imageProcessor->getImageDimensions($outputFile); $testResults['niceText']['title'] = 'Render text with TrueType font using \'niceText\' option'; $testResults['niceText']['outputFile'] = $result[3]; $testResults['niceText']['referenceFile'] = $this->imageBasePath . 'TestReference/Gdlib-niceText.' . $gifOrPng; $testResults['niceText']['command'] = $imageProcessor->IM_commands; /** @var \TYPO3\CMS\Install\Status\StatusInterface $message */ $message = $this->objectManager->get(\TYPO3\CMS\Install\Status\InfoStatus::class); $message->setTitle('Note on \'niceText\''); $message->setMessage('\'niceText\' is a concept that tries to improve the antialiasing of the rendered type by' . ' actually rendering the textstring in double size on a black/white mask, downscaling the mask' . ' and masking the text onto the image through this mask. This involves' . ' ImageMagick \'combine\'/\'composite\' and \'convert\'.'); $testResults['niceText']['message'] = $message; } else { $result['niceText']['error'] = $this->imageGenerationFailedMessage(); } // GD with text, niceText, shadow $testResults['shadow'] = array(); if ($this->isImageMagickEnabledAndConfigured()) { // Warning: Re-uses $conf from above! $conf['offset'] = '17,90'; $conf['niceText'] = 1; $conf['shadow.'] = array('offset' => '2,2', 'blur' => $imageProcessor->V5_EFFECTS ? '20' : '90', 'opacity' => '50', 'color' => 'black'); // Warning: Re-uses $image from above! $imageProcessor->makeShadow($image, $conf['shadow.'], $workArea, $conf); $imageProcessor->makeText($image, $conf, $workArea); $outputFile = $imageProcessor->tempPath . $imageProcessor->filenamePrefix . StringUtility::getUniqueId('GDwithText-niceText-shadow') . '.' . $gifOrPng; $imageProcessor->ImageWrite($image, $outputFile); $result = $imageProcessor->getImageDimensions($outputFile); $testResults['shadow']['title'] = 'Render \'niceText\' with a shadow under'; $testResults['shadow']['outputFile'] = $result[3]; $testResults['shadow']['referenceFile'] = $this->imageBasePath . 'TestReference/Gdlib-shadow.' . $gifOrPng; $testResults['shadow']['command'] = $imageProcessor->IM_commands; /** @var \TYPO3\CMS\Install\Status\StatusInterface $message */ $message = $this->objectManager->get(\TYPO3\CMS\Install\Status\InfoStatus::class); $message->setTitle('Note on \'shadow\''); $message->setMessage('This test makes sense only if the above test had a correct output. But if so, you may not see' . ' a soft dropshadow from the third text string as you should. In that case you are most likely' . ' using ImageMagick 5 and should set the flag TYPO3_CONF_VARS[GFX][im_v5effects].'); $testResults['shadow']['message'] = $message; } else { $result['shadow']['error'] = $this->imageGenerationFailedMessage(); } $this->view->assign('testResults', $testResults); return $this->imageTestDoneMessage(GeneralUtility::milliseconds() - $parseTimeStart); }
/** * Helper method for flushByTag() * Gets list of identifiers and tags and removes all relations of those tags * * Scales O(1) with number of cache entries * Scales O(n^2) with number of tags * * @param array $identifiers List of identifiers to remove * @param array $tags List of tags to be handled * @return void */ protected function removeIdentifierEntriesAndRelations(array $identifiers, array $tags) { // Set a temporary entry which holds all identifiers that need to be removed from // the tag to identifiers sets $uniqueTempKey = 'temp:' . StringUtility::getUniqueId(); $prefixedKeysToDelete = array($uniqueTempKey); $prefixedIdentifierToTagsKeysToDelete = array(); foreach ($identifiers as $identifier) { $prefixedKeysToDelete[] = self::IDENTIFIER_DATA_PREFIX . $identifier; $prefixedIdentifierToTagsKeysToDelete[] = self::IDENTIFIER_TAGS_PREFIX . $identifier; } foreach ($tags as $tag) { $prefixedKeysToDelete[] = self::TAG_IDENTIFIERS_PREFIX . $tag; } $tagToIdentifiersSetsToRemoveIdentifiersFrom = $this->redis->sUnion($prefixedIdentifierToTagsKeysToDelete); // Remove the tag to identifier set of the given tags, they will be removed anyway $tagToIdentifiersSetsToRemoveIdentifiersFrom = array_diff($tagToIdentifiersSetsToRemoveIdentifiersFrom, $tags); // Diff all identifiers that must be removed from tag to identifiers sets off from a // tag to identifiers set and store result in same tag to identifiers set again $queue = $this->redis->multi(\Redis::PIPELINE); foreach ($identifiers as $identifier) { $queue->sAdd($uniqueTempKey, $identifier); } foreach ($tagToIdentifiersSetsToRemoveIdentifiersFrom as $tagToIdentifiersSet) { $queue->sDiffStore(self::TAG_IDENTIFIERS_PREFIX . $tagToIdentifiersSet, self::TAG_IDENTIFIERS_PREFIX . $tagToIdentifiersSet, $uniqueTempKey); } $queue->delete(array_merge($prefixedKeysToDelete, $prefixedIdentifierToTagsKeysToDelete)); $queue->exec(); }
/** * Create a test file and delete again - helper for isWritable * * @return bool TRUE if test file creation was successful */ protected function canFileBeCreated() { $testFileName = StringUtility::getUniqueId('installToolTest_'); $result = @touch($this->getAbsolutePath() . '/' . $testFileName); if ($result === true) { unlink($this->getAbsolutePath() . '/' . $testFileName); } return $result; }
/** * Render check boxes * * @return array As defined in initializeResultArray() of AbstractNode */ public function render() { $html = []; // Field configuration from TCA: $parameterArray = $this->data['parameterArray']; $config = $parameterArray['fieldConf']['config']; $disabled = !empty($config['readOnly']); $selItems = $config['items']; if (!empty($selItems)) { // Get values in an array (and make unique, which is fine because there can be no duplicates anyway): $itemArray = array_flip($parameterArray['itemFormElValue']); // Traverse the Array of selector box items: $groups = array(); $currentGroup = 0; $c = 0; $sOnChange = ''; if (!$disabled) { $sOnChange = implode('', $parameterArray['fieldChangeFunc']); // Used to accumulate the JS needed to restore the original selection. foreach ($selItems as $p) { // Non-selectable element: if ($p[1] === '--div--') { $selIcon = ''; if (isset($p[2]) && $p[2] != 'empty-empty') { $selIcon = FormEngineUtility::getIconHtml($p[2]); } $currentGroup++; $groups[$currentGroup]['header'] = array('icon' => $selIcon, 'title' => htmlspecialchars($p[0])); } else { // Check if some help text is available // Since TYPO3 4.5 help text is expected to be an associative array // with two key, "title" and "description" // For the sake of backwards compatibility, we test if the help text // is a string and use it as a description (this could happen if items // are modified with an itemProcFunc) $hasHelp = false; $help = ''; $helpArray = array(); if (!empty($p[3])) { $hasHelp = true; if (is_array($p[3])) { $helpArray = $p[3]; } else { $helpArray['description'] = $p[3]; } } if ($hasHelp) { $help = BackendUtility::wrapInHelp('', '', '', $helpArray); } // Selected or not by default: $checked = 0; if (isset($itemArray[$p[1]])) { $checked = 1; unset($itemArray[$p[1]]); } // Build item array $groups[$currentGroup]['items'][] = array('id' => StringUtility::getUniqueId('select_checkbox_row_'), 'name' => $parameterArray['itemFormElName'] . '[' . $c . ']', 'value' => $p[1], 'checked' => $checked, 'disabled' => false, 'class' => '', 'icon' => !empty($p[2]) ? FormEngineUtility::getIconHtml($p[2]) : $this->iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render(), 'title' => htmlspecialchars($p[0], ENT_COMPAT, 'UTF-8', false), 'help' => $help); $c++; } } } // Add an empty hidden field which will send a blank value if all items are unselected. $html[] = '<input type="hidden" class="select-checkbox" name="' . htmlspecialchars($parameterArray['itemFormElName']) . '" value="">'; // Building the checkboxes foreach ($groups as $groupKey => $group) { $groupId = htmlspecialchars($parameterArray['itemFormElID']) . '-group-' . $groupKey; $html[] = '<div class="panel panel-default">'; if (is_array($group['header'])) { $html[] = '<div class="panel-heading">'; $html[] = '<a data-toggle="collapse" href="#' . $groupId . '" aria-expanded="false" aria-controls="' . $groupId . '">'; $html[] = $group['header']['icon']; $html[] = $group['header']['title']; $html[] = '</a>'; $html[] = '</div>'; } if (is_array($group['items']) && !empty($group['items'])) { $tableRows = []; $checkGroup = array(); $uncheckGroup = array(); $resetGroup = array(); // Render rows foreach ($group['items'] as $item) { $tableRows[] = '<tr class="' . $item['class'] . '">'; $tableRows[] = '<td class="col-checkbox">'; $tableRows[] = '<input type="checkbox" ' . 'id="' . $item['id'] . '" ' . 'name="' . htmlspecialchars($item['name']) . '" ' . 'value="' . htmlspecialchars($item['value']) . '" ' . 'onclick="' . htmlspecialchars($sOnChange) . '" ' . ($item['checked'] ? 'checked=checked ' : '') . ($item['disabled'] ? 'disabled=disabled ' : '') . $parameterArray['onFocus'] . '>'; $tableRows[] = '</td>'; $tableRows[] = '<td class="col-icon">'; $tableRows[] = '<label class="label-block" for="' . $item['id'] . '">' . $item['icon'] . '</label>'; $tableRows[] = '</td>'; $tableRows[] = '<td class="col-title">'; $tableRows[] = '<label class="label-block" for="' . $item['id'] . '">' . $item['title'] . '</label>'; $tableRows[] = '</td>'; $tableRows[] = '<td>' . $item['help'] . '</td>'; $tableRows[] = '</tr>'; $checkGroup[] = 'document.editform[' . GeneralUtility::quoteJSvalue($item['name']) . '].checked=1;'; $uncheckGroup[] = 'document.editform[' . GeneralUtility::quoteJSvalue($item['name']) . '].checked=0;'; $resetGroup[] = 'document.editform[' . GeneralUtility::quoteJSvalue($item['name']) . '].checked=' . $item['checked'] . ';'; } // Build toggle group checkbox $toggleGroupCheckbox = ''; if (!empty($resetGroup)) { $toggleGroupCheckbox = '<input type="checkbox" ' . 'class="checkbox" ' . 'onclick="if (checked) {' . htmlspecialchars(implode('', $checkGroup) . '} else {' . implode('', $uncheckGroup)) . '}">'; } // Build reset group button $resetGroupBtn = ''; if (!empty($resetGroup)) { $title = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.revertSelection', true); $resetGroupBtn = '<a href="#" ' . 'class="btn btn-default" ' . 'onclick="' . implode('', $resetGroup) . ' return false;" ' . 'title="' . $title . '">' . $this->iconFactory->getIcon('actions-edit-undo', Icon::SIZE_SMALL)->render() . ' ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.revertSelection') . '</a>'; } if (is_array($group['header'])) { $html[] = '<div id="' . $groupId . '" class="panel-collapse collapse" role="tabpanel">'; } $html[] = '<div class="table-fit">'; $html[] = '<table class="table table-transparent table-hover">'; $html[] = '<thead>'; $html[] = '<tr>'; $html[] = '<th class="col-checkbox">' . $toggleGroupCheckbox . '</th>'; $html[] = '<th class="col-icon"></th>'; $html[] = '<th class="text-right" colspan="2">' . $resetGroupBtn . '</th>'; $html[] = '</tr>'; $html[] = '</thead>'; $html[] = '<tbody>' . implode(LF, $tableRows) . '</tbody>'; $html[] = '</table>'; $html[] = '</div>'; if (is_array($group['header'])) { $html[] = '</div>'; } } $html[] = '</div>'; } } if (!$disabled) { $html = $this->renderWizards(array(implode(LF, $html)), $config['wizards'], $this->data['tableName'], $this->data['databaseRow'], $this->data['fieldName'], $parameterArray, $parameterArray['itemFormElName'], BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras'])); } $resultArray = $this->initializeResultArray(); $resultArray['html'] = $html; return $resultArray; }
/** * Flushes a directory by first moving to a temporary resource, and then * triggering the remove process. This way directories can be flushed faster * to prevent race conditions on concurrent processes accessing the same directory. * * @param string $directory The directory to be renamed and flushed * @param bool $keepOriginalDirectory Whether to only empty the directory and not remove it * @param bool $flushOpcodeCache Also flush the opcode cache right after renaming the directory. * @return bool Whether the action was successful */ public static function flushDirectory($directory, $keepOriginalDirectory = false, $flushOpcodeCache = false) { $result = false; if (is_dir($directory)) { $temporaryDirectory = rtrim($directory, '/') . '.' . StringUtility::getUniqueId('remove') . '/'; if (rename($directory, $temporaryDirectory)) { if ($flushOpcodeCache) { self::makeInstance(OpcodeCacheService::class)->clearAllActive($directory); } if ($keepOriginalDirectory) { static::mkdir($directory); } clearstatcache(); $result = static::rmdir($temporaryDirectory, true); } } return $result; }
/** * Render single element * * @return array As defined in initializeResultArray() of AbstractNode */ public function render() { $table = $this->data['tableName']; $field = $this->data['fieldName']; $row = $this->data['databaseRow']; $parameterArray = $this->data['parameterArray']; $config = $parameterArray['fieldConf']['config']; $selectItems = $parameterArray['fieldConf']['config']['items']; // Check against inline uniqueness /** @var InlineStackProcessor $inlineStackProcessor */ $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class); $inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']); $uniqueIds = null; if ($this->data['isInlineChild'] && $this->data['inlineParentUid']) { $inlineObjectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']); $inlineFormName = $inlineStackProcessor->getCurrentStructureFormPrefix(); if ($this->data['inlineParentConfig']['foreign_table'] === $table && $this->data['inlineParentConfig']['foreign_unique'] === $field) { $uniqueIds = $this->data['inlineData']['unique'][$inlineObjectName . '-' . $table]['used']; $parameterArray['fieldChangeFunc']['inlineUnique'] = 'inline.updateUnique(this,' . GeneralUtility::quoteJSvalue($inlineObjectName . '-' . $table) . ',' . GeneralUtility::quoteJSvalue($inlineFormName) . ',' . GeneralUtility::quoteJSvalue($row['uid']) . ');'; } // hide uid of parent record for symmetric relations if ($this->data['inlineParentConfig']['foreign_table'] === $table && ($this->data['inlineParentConfig']['foreign_field'] === $field || $this->data['inlineParentConfig']['symmetric_field'] === $field)) { $uniqueIds[] = $this->data['inlineParentUid']; } } // Initialization: $selectId = StringUtility::getUniqueId('tceforms-select-'); $selectedIcon = ''; $size = (int) $config['size']; // Style set on <select/> $options = ''; $disabled = false; if (!empty($config['readOnly'])) { $disabled = true; } // Prepare groups $selectItemCounter = 0; $selectItemGroupCount = 0; $selectItemGroups = array(); $selectIcons = array(); $selectedValue = ''; $hasIcons = false; if (!empty($parameterArray['itemFormElValue'])) { $selectedValue = (string) $parameterArray['itemFormElValue'][0]; } foreach ($selectItems as $item) { if ($item[1] === '--div--') { // IS OPTGROUP if ($selectItemCounter !== 0) { $selectItemGroupCount++; } $selectItemGroups[$selectItemGroupCount]['header'] = array('title' => $item[0]); } else { // IS ITEM $title = htmlspecialchars($item['0'], ENT_COMPAT, 'UTF-8', false); $icon = !empty($item[2]) ? FormEngineUtility::getIconHtml($item[2], $title, $title) : ''; $selected = $selectedValue === (string) $item[1]; if ($selected) { $selectedIcon = $icon; } $selectItemGroups[$selectItemGroupCount]['items'][] = array('title' => $title, 'value' => $item[1], 'icon' => $icon, 'selected' => $selected, 'index' => $selectItemCounter); // ICON if ($icon) { $selectIcons[] = array('title' => $title, 'icon' => $icon, 'index' => $selectItemCounter); } $selectItemCounter++; } } // Fallback icon // @todo: assign a special icon for non matching values? if (!$selectedIcon && $selectItemGroups[0]['items'][0]['icon']) { $selectedIcon = $selectItemGroups[0]['items'][0]['icon']; } // Process groups foreach ($selectItemGroups as $selectItemGroup) { // suppress groups without items if (empty($selectItemGroup['items'])) { continue; } $optionGroup = is_array($selectItemGroup['header']); $options .= $optionGroup ? '<optgroup label="' . htmlspecialchars($selectItemGroup['header']['title'], ENT_COMPAT, 'UTF-8', false) . '">' : ''; if (is_array($selectItemGroup['items'])) { foreach ($selectItemGroup['items'] as $item) { $options .= '<option value="' . htmlspecialchars($item['value']) . '" data-icon="' . htmlspecialchars($item['icon']) . '"' . ($item['selected'] ? ' selected="selected"' : '') . '>' . $item['title'] . '</option>'; } $hasIcons = !empty($item['icon']); } $options .= $optionGroup ? '</optgroup>' : ''; } // Build the element $html = ['<div class="form-control-wrap">']; if ($hasIcons) { $html[] = '<div class="input-group">'; $html[] = '<span class="input-group-addon input-group-icon">'; $html[] = $selectedIcon; $html[] = '</span>'; } $html[] = '<select' . ' id="' . $selectId . '"' . ' name="' . htmlspecialchars($parameterArray['itemFormElName']) . '"' . $this->getValidationDataAsDataAttribute($config) . ' class="form-control form-control-adapt"' . ($size ? ' size="' . $size . '"' : '') . ($disabled ? ' disabled="disabled"' : '') . '>'; $html[] = $options; $html[] = '</select>'; if ($hasIcons) { $html[] = '</div>'; } $html[] = '</div>'; // Create icon table: if (!empty($selectIcons) && !empty($config['showIconTable'])) { $selectIconColumns = (int) $config['selicon_cols']; if (!$selectIconColumns) { $selectIconColumns = count($selectIcons); } $selectIconColumns = $selectIconColumns > 12 ? 12 : $selectIconColumns; $selectIconRows = ceil(count($selectIcons) / $selectIconColumns); $selectIcons = array_pad($selectIcons, $selectIconRows * $selectIconColumns, ''); $html[] = '<div class="t3js-forms-select-single-icons table-icons table-fit table-fit-inline-block">'; $html[] = '<table class="table table-condensed table-white table-center">'; $html[] = '<tbody>'; $html[] = '<tr>'; foreach ($selectIcons as $i => $selectIcon) { if ($i % $selectIconColumns === 0 && $i !== 0) { $html[] = '</tr>'; $html[] = '<tr>'; } $html[] = '<td>'; if (is_array($selectIcon)) { $html[] = '<a href="#" title="' . $selectIcon['title'] . '" data-select-index="' . $selectIcon['index'] . '">'; $html[] = $selectIcon['icon']; $html[] = '</a>'; } $html[] = '</td>'; } $html[] = '</tr>'; $html[] = '</tbody>'; $html[] = '</table>'; $html[] = '</div>'; } $html = implode(LF, $html); // Wizards: if (!$disabled) { $html = $this->renderWizards(array($html), $config['wizards'], $table, $row, $field, $parameterArray, $parameterArray['itemFormElName'], BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras'])); } $resultArray = $this->initializeResultArray(); $resultArray['html'] = $html; $resultArray['requireJsModules'][] = ['TYPO3/CMS/Backend/FormEngine/Element/SelectSingleElement' => implode(LF, ['function(SelectSingleElement) {', 'SelectSingleElement.initialize(', GeneralUtility::quoteJSvalue('#' . $selectId) . ',', '{', 'onChange: function() {', implode('', $parameterArray['fieldChangeFunc']), '}', '}', ');', '}'])]; return $resultArray; }
/** * This will render a single-line input form field, possibly with various control/validation features * * @return array As defined in initializeResultArray() of AbstractNode */ public function render() { $languageService = $this->getLanguageService(); $table = $this->data['tableName']; $fieldName = $this->data['fieldName']; $row = $this->data['databaseRow']; $parameterArray = $this->data['parameterArray']; $resultArray = $this->initializeResultArray(); $resultArray['requireJsModules'] = array('TYPO3/CMS/Rsaauth/RsaEncryptionModule'); $config = $parameterArray['fieldConf']['config']; $specConf = BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras']); $size = MathUtility::forceIntegerInRange($config['size'] ?: $this->defaultInputWidth, $this->minimumInputWidth, $this->maxInputWidth); $evalList = GeneralUtility::trimExplode(',', $config['eval'], true); $classes = array(); $attributes = array('type' => 'text', 'value' => ''); if ($config['readOnly']) { $itemFormElValue = $parameterArray['itemFormElValue']; $options = $this->data; $options['parameterArray'] = array('fieldConf' => array('config' => $config), 'itemFormElValue' => $itemFormElValue); $options['renderType'] = 'none'; return $this->nodeFactory->create($options)->render(); } // @todo: The whole eval handling is a mess and needs refactoring foreach ($evalList as $func) { switch ($func) { case 'required': $attributes['data-formengine-validation-rules'] = $this->getValidationDataAsJsonString(array('required' => true)); break; case 'password': $attributes['type'] = 'password'; $attributes['value'] = '********'; $attributes['autocomplete'] = 'off'; break; default: // @todo: This is ugly: The code should find out on it's own whether a eval definition is a // @todo: keyword like "date", or a class reference. The global registration could be dropped then // Pair hook to the one in \TYPO3\CMS\Core\DataHandling\DataHandler::checkValue_input_Eval() if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$func])) { if (class_exists($func)) { $evalObj = GeneralUtility::makeInstance($func); if (method_exists($evalObj, 'deevaluateFieldValue')) { $_params = array('value' => $parameterArray['itemFormElValue']); $parameterArray['itemFormElValue'] = $evalObj->deevaluateFieldValue($_params); } } } } } $evalList = array_filter($evalList, function ($value) { return $value !== 'password'; }); $paramsList = array('field' => $parameterArray['itemFormElName'], 'evalList' => implode(',', $evalList), 'is_in' => trim($config['is_in'])); // set classes $classes[] = 'form-control'; $classes[] = 't3js-clearable'; $classes[] = 'hasDefaultValue'; // calculate attributes $attributes['data-formengine-validation-rules'] = $this->getValidationDataAsJsonString($config); $attributes['data-formengine-input-params'] = json_encode($paramsList); $attributes['data-formengine-input-name'] = htmlspecialchars($parameterArray['itemFormElName']); $attributes['id'] = StringUtility::getUniqueId('formengine-input-'); if (isset($config['max']) && (int) $config['max'] > 0) { $attributes['maxlength'] = (int) $config['max']; } if (!empty($classes)) { $attributes['class'] = implode(' ', $classes); } if (isset($config['max']) && (int) $config['max'] > 0) { $attributes['maxlength'] = (int) $config['max']; } // This is the EDITABLE form field. if (!empty($config['placeholder'])) { $attributes['placeholder'] = trim($config['placeholder']); } if (isset($config['autocomplete'])) { $attributes['autocomplete'] = empty($config['autocomplete']) ? 'off' : 'on'; } // Build the attribute string $attributeString = ''; foreach ($attributes as $attributeName => $attributeValue) { $attributeString .= ' ' . $attributeName . '="' . htmlspecialchars($attributeValue) . '"'; } $html = ' <input' . $attributeString . $parameterArray['onFocus'] . ' />'; // This is the ACTUAL form field - values from the EDITABLE field must be transferred to this field which is the one that is written to the database. $html .= '<input type="hidden" data-rsa-encryption="" id="' . $parameterArray['itemFormElID'] . '_hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" />'; // Going through all custom evaluations configured for this field // @todo: Similar to above code! foreach ($evalList as $evalData) { if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$evalData])) { if (class_exists($evalData)) { $evalObj = GeneralUtility::makeInstance($evalData); if (method_exists($evalObj, 'returnFieldJS')) { $resultArray['extJSCODE'] .= LF . 'TBE_EDITOR.customEvalFunctions[' . GeneralUtility::quoteJSvalue($evalData) . '] = function(value) {' . $evalObj->returnFieldJS() . '}'; } } } } // Wrap a wizard around the item? $html = $this->renderWizards(array($html), $config['wizards'], $table, $row, $fieldName, $parameterArray, $parameterArray['itemFormElName'], $specConf); // Add a wrapper to remain maximum width $width = (int) $this->formMaxWidth($size); $html = '<div class="form-control-wrap"' . ($width ? ' style="max-width: ' . $width . 'px"' : '') . '>' . $html . '</div>'; $resultArray['html'] = $html; return $resultArray; }
/** * @param string $privateKey The private key (obtained from a call to createNewKeyPair()) * @param string $data Data to decrypt (base64-encoded) * @return string Decrypted data or NULL in case of an error * @see \TYPO3\CMS\Rsaauth\Backend\AbstractBackend::decrypt() */ public function decrypt($privateKey, $data) { // Key must be put to the file $privateKeyFile = tempnam($this->temporaryDirectory, StringUtility::getUniqueId()); file_put_contents($privateKeyFile, $privateKey); $dataFile = tempnam($this->temporaryDirectory, StringUtility::getUniqueId()); file_put_contents($dataFile, base64_decode($data)); // Prepare the command $command = $this->opensslPath . ' rsautl -inkey ' . escapeshellarg($privateKeyFile) . ' -in ' . escapeshellarg($dataFile) . ' -decrypt'; // Execute the command and capture the result $output = array(); CommandUtility::exec($command, $output); // Remove the file @unlink($privateKeyFile); @unlink($dataFile); return implode(LF, $output); }
/** * Renders Content Elements from the tt_content table from page id * * @param int $id Page id * @return string HTML for the listing */ public function getTable_tt_content($id) { $backendUser = $this->getBackendUser(); $this->pageinfo = BackendUtility::readPageAccess($this->id, $backendUser->getPagePermsClause($this->ext_CALC_PERMS)); $this->initializeLanguages(); $this->initializeClipboard(); $pageTitleParamForAltDoc = '&recTitle=' . rawurlencode(BackendUtility::getRecordTitle('pages', BackendUtility::getRecordWSOL('pages', $id), true)); /** @var $pageRenderer PageRenderer */ $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class); $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/LayoutModule/DragDrop'); $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Modal'); $userCanEditPage = $this->ext_CALC_PERMS & Permission::PAGE_EDIT && !empty($this->id) && ($backendUser->isAdmin() || (int) $this->pageinfo['editlock'] === 0); if ($this->tt_contentConfig['languageColsPointer'] > 0) { $userCanEditPage = $this->getBackendUser()->check('tables_modify', 'pages_language_overlay'); } if ($userCanEditPage) { $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/PageActions', 'function(PageActions) { PageActions.setPageId(' . (int) $this->id . '); PageActions.setLanguageOverlayId(' . $this->tt_contentConfig['languageColsPointer'] . '); PageActions.initializePageTitleRenaming(); }'); } // Get labels for CTypes and tt_content element fields in general: $this->CType_labels = array(); foreach ($GLOBALS['TCA']['tt_content']['columns']['CType']['config']['items'] as $val) { $this->CType_labels[$val[1]] = $this->getLanguageService()->sL($val[0]); } $this->itemLabels = array(); foreach ($GLOBALS['TCA']['tt_content']['columns'] as $name => $val) { $this->itemLabels[$name] = $this->getLanguageService()->sL($val['label']); } $languageColumn = array(); $out = ''; // Setting language list: $langList = $this->tt_contentConfig['sys_language_uid']; if ($this->tt_contentConfig['languageMode']) { if ($this->tt_contentConfig['languageColsPointer']) { $langList = '0,' . $this->tt_contentConfig['languageColsPointer']; } else { $langList = implode(',', array_keys($this->tt_contentConfig['languageCols'])); } $languageColumn = array(); } $langListArr = GeneralUtility::intExplode(',', $langList); $defLanguageCount = array(); $defLangBinding = array(); // For each languages... : // If not languageMode, then we'll only be through this once. foreach ($langListArr as $lP) { $lP = (int) $lP; if (!isset($this->contentElementCache[$lP])) { $this->contentElementCache[$lP] = array(); } if (count($langListArr) === 1 || $lP === 0) { $showLanguage = ' AND sys_language_uid IN (' . $lP . ',-1)'; } else { $showLanguage = ' AND sys_language_uid=' . $lP; } $cList = explode(',', $this->tt_contentConfig['cols']); $content = array(); $head = array(); // Select content records per column $contentRecordsPerColumn = $this->getContentRecordsPerColumn('table', $id, array_values($cList), $showLanguage); // For each column, render the content into a variable: foreach ($cList as $key) { if (!isset($this->contentElementCache[$lP][$key])) { $this->contentElementCache[$lP][$key] = array(); } if (!$lP) { $defLanguageCount[$key] = array(); } // Start wrapping div $content[$key] .= '<div data-colpos="' . $key . '" data-language-uid="' . $lP . '" class="t3js-sortable t3js-sortable-lang t3js-sortable-lang-' . $lP . ' t3-page-ce-wrapper'; if (empty($contentRecordsPerColumn[$key])) { $content[$key] .= ' t3-page-ce-empty'; } $content[$key] .= '">'; // Add new content at the top most position $link = ''; if ($this->getPageLayoutController()->pageIsNotLockedForEditors() && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT)) { $link = '<a href="#" onclick="' . htmlspecialchars($this->newContentElementOnClick($id, $key, $lP)) . '" title="' . $this->getLanguageService()->getLL('newContentElement', true) . '" class="btn btn-default btn-sm">' . $this->iconFactory->getIcon('actions-document-new', Icon::SIZE_SMALL)->render() . ' ' . $this->getLanguageService()->getLL('content', true) . '</a>'; } $content[$key] .= ' <div class="t3-page-ce t3js-page-ce" data-page="' . (int) $id . '" id="' . StringUtility::getUniqueId() . '"> <div class="t3js-page-new-ce t3-page-ce-wrapper-new-ce" id="colpos-' . $key . '-' . 'page-' . $id . '-' . StringUtility::getUniqueId() . '">' . $link . '</div> <div class="t3-page-ce-dropzone-available t3js-page-ce-dropzone-available"></div> </div> '; $editUidList = ''; $rowArr = $contentRecordsPerColumn[$key]; $this->generateTtContentDataArray($rowArr); foreach ((array) $rowArr as $rKey => $row) { $this->contentElementCache[$lP][$key][$row['uid']] = $row; if ($this->tt_contentConfig['languageMode']) { $languageColumn[$key][$lP] = $head[$key] . $content[$key]; if (!$this->defLangBinding) { $languageColumn[$key][$lP] .= $this->newLanguageButton($this->getNonTranslatedTTcontentUids($defLanguageCount[$key], $id, $lP), $lP, $key); } } if (is_array($row) && !VersionState::cast($row['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) { $singleElementHTML = ''; if (!$lP && ($this->defLangBinding || $row['sys_language_uid'] != -1)) { $defLanguageCount[$key][] = isset($row['_ORIG_uid']) ? $row['_ORIG_uid'] : $row['uid']; } $editUidList .= $row['uid'] . ','; $disableMoveAndNewButtons = $this->defLangBinding && $lP > 0; if (!$this->tt_contentConfig['languageMode']) { $singleElementHTML .= '<div class="t3-page-ce-dragitem" id="' . StringUtility::getUniqueId() . '">'; } $singleElementHTML .= $this->tt_content_drawHeader($row, $this->tt_contentConfig['showInfo'] ? 15 : 5, $disableMoveAndNewButtons, !$this->tt_contentConfig['languageMode'], $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT)); $innerContent = '<div ' . ($row['_ORIG_uid'] ? ' class="ver-element"' : '') . '>' . $this->tt_content_drawItem($row) . '</div>'; $singleElementHTML .= '<div class="t3-page-ce-body-inner">' . $innerContent . '</div>' . $this->tt_content_drawFooter($row); $isDisabled = $this->isDisabled('tt_content', $row); $statusHidden = $isDisabled ? ' t3-page-ce-hidden t3js-hidden-record' : ''; $displayNone = !$this->tt_contentConfig['showHidden'] && $isDisabled ? ' style="display: none;"' : ''; $singleElementHTML = '<div class="t3-page-ce t3js-page-ce t3js-page-ce-sortable ' . $statusHidden . '" id="element-tt_content-' . $row['uid'] . '" data-table="tt_content" data-uid="' . $row['uid'] . '"' . $displayNone . '>' . $singleElementHTML . '</div>'; if ($this->tt_contentConfig['languageMode']) { $singleElementHTML .= '<div class="t3-page-ce t3js-page-ce">'; } $singleElementHTML .= '<div class="t3js-page-new-ce t3-page-ce-wrapper-new-ce" id="colpos-' . $key . '-' . 'page-' . $id . '-' . StringUtility::getUniqueId() . '">'; // Add icon "new content element below" if (!$disableMoveAndNewButtons && $this->getPageLayoutController()->pageIsNotLockedForEditors() && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT)) { // New content element: if ($this->option_newWizard) { $onClick = 'window.location.href=' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('new_content_element') . '&id=' . $row['pid'] . '&sys_language_uid=' . $row['sys_language_uid'] . '&colPos=' . $row['colPos'] . '&uid_pid=' . -$row['uid'] . '&returnUrl=' . rawurlencode(GeneralUtility::getIndpEnv('REQUEST_URI'))) . ';'; } else { $params = '&edit[tt_content][' . -$row['uid'] . ']=new'; $onClick = BackendUtility::editOnClick($params); } $singleElementHTML .= ' <a href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . $this->getLanguageService()->getLL('newContentElement', true) . '" class="btn btn-default btn-sm">' . $this->iconFactory->getIcon('actions-document-new', Icon::SIZE_SMALL)->render() . ' ' . $this->getLanguageService()->getLL('content', true) . '</a> '; } $singleElementHTML .= '</div></div><div class="t3-page-ce-dropzone-available t3js-page-ce-dropzone-available"></div></div>'; if ($this->defLangBinding && $this->tt_contentConfig['languageMode']) { $defLangBinding[$key][$lP][$row[$lP ? 'l18n_parent' : 'uid']] = $singleElementHTML; } else { $content[$key] .= $singleElementHTML; } } else { unset($rowArr[$rKey]); } } $content[$key] .= '</div>'; // Add new-icon link, header: $newP = $this->newContentElementOnClick($id, $key, $lP); $colTitle = BackendUtility::getProcessedValue('tt_content', 'colPos', $key); $tcaItems = GeneralUtility::callUserFunction(\TYPO3\CMS\Backend\View\BackendLayoutView::class . '->getColPosListItemsParsed', $id, $this); foreach ($tcaItems as $item) { if ($item[1] == $key) { $colTitle = $this->getLanguageService()->sL($item[0]); } } $pasteP = array('colPos' => $key, 'sys_language_uid' => $lP); $editParam = $this->doEdit && !empty($rowArr) ? '&edit[tt_content][' . $editUidList . ']=edit' . $pageTitleParamForAltDoc : ''; $head[$key] .= $this->tt_content_drawColHeader($colTitle, $editParam, $newP, $pasteP); } // For each column, fit the rendered content into a table cell: $out = ''; if ($this->tt_contentConfig['languageMode']) { // in language mode process the content elements, but only fill $languageColumn. output will be generated later $sortedLanguageColumn = array(); foreach ($cList as $key) { $languageColumn[$key][$lP] = $head[$key] . $content[$key]; if (!$this->defLangBinding) { $languageColumn[$key][$lP] .= $this->newLanguageButton($this->getNonTranslatedTTcontentUids($defLanguageCount[$key], $id, $lP), $lP, $key); } // We sort $languageColumn again according to $cList as it may contain data already from above. $sortedLanguageColumn[$key] = $languageColumn[$key]; } $languageColumn = $sortedLanguageColumn; } else { $backendLayout = $this->getBackendLayoutView()->getSelectedBackendLayout($this->id); // GRID VIEW: $grid = '<div class="t3-grid-container"><table border="0" cellspacing="0" cellpadding="0" width="100%" class="t3-page-columns t3-grid-table t3js-page-columns">'; // Add colgroups $colCount = (int) $backendLayout['__config']['backend_layout.']['colCount']; $rowCount = (int) $backendLayout['__config']['backend_layout.']['rowCount']; $grid .= '<colgroup>'; for ($i = 0; $i < $colCount; $i++) { $grid .= '<col style="width:' . 100 / $colCount . '%"></col>'; } $grid .= '</colgroup>'; // Cycle through rows for ($row = 1; $row <= $rowCount; $row++) { $rowConfig = $backendLayout['__config']['backend_layout.']['rows.'][$row . '.']; if (!isset($rowConfig)) { continue; } $grid .= '<tr>'; for ($col = 1; $col <= $colCount; $col++) { $columnConfig = $rowConfig['columns.'][$col . '.']; if (!isset($columnConfig)) { continue; } // Which tt_content colPos should be displayed inside this cell $columnKey = (int) $columnConfig['colPos']; // Render the grid cell $colSpan = (int) $columnConfig['colspan']; $rowSpan = (int) $columnConfig['rowspan']; $grid .= '<td valign="top"' . ($colSpan > 0 ? ' colspan="' . $colSpan . '"' : '') . ($rowSpan > 0 ? ' rowspan="' . $rowSpan . '"' : '') . ' data-colpos="' . (int) $columnConfig['colPos'] . '" data-language-uid="' . $lP . '" class="t3js-page-lang-column-' . $lP . ' t3js-page-column t3-grid-cell t3-page-column t3-page-column-' . $columnKey . (!isset($columnConfig['colPos']) || $columnConfig['colPos'] === '' ? ' t3-grid-cell-unassigned' : '') . (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== '' && !$head[$columnKey] || !GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos']) ? ' t3-grid-cell-restricted' : '') . ($colSpan > 0 ? ' t3-gridCell-width' . $colSpan : '') . ($rowSpan > 0 ? ' t3-gridCell-height' . $rowSpan : '') . '">'; // Draw the pre-generated header with edit and new buttons if a colPos is assigned. // If not, a new header without any buttons will be generated. if (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== '' && $head[$columnKey] && GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])) { $grid .= $head[$columnKey] . $content[$columnKey]; } elseif (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== '' && GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])) { $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->getLL('noAccess'), '', ''); } elseif (isset($columnConfig['colPos']) && $columnConfig['colPos'] !== '' && !GeneralUtility::inList($this->tt_contentConfig['activeCols'], $columnConfig['colPos'])) { $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->sL($columnConfig['name']) . ' (' . $this->getLanguageService()->getLL('noAccess') . ')', '', ''); } elseif (isset($columnConfig['name']) && $columnConfig['name'] !== '') { $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->sL($columnConfig['name']) . ' (' . $this->getLanguageService()->getLL('notAssigned') . ')', '', ''); } else { $grid .= $this->tt_content_drawColHeader($this->getLanguageService()->getLL('notAssigned'), '', ''); } $grid .= '</td>'; } $grid .= '</tr>'; } $out .= $grid . '</table></div>'; } // CSH: $out .= BackendUtility::cshItem($this->descrTable, 'columns_multi'); } // If language mode, then make another presentation: // Notice that THIS presentation will override the value of $out! // But it needs the code above to execute since $languageColumn is filled with content we need! if ($this->tt_contentConfig['languageMode']) { // Get language selector: $languageSelector = $this->languageSelector($id); // Reset out - we will make new content here: $out = ''; // Traverse languages found on the page and build up the table displaying them side by side: $cCont = array(); $sCont = array(); foreach ($langListArr as $lP) { // Header: $lP = (int) $lP; $cCont[$lP] = ' <td valign="top" class="t3-page-column" data-language-uid="' . $lP . '"> <h2>' . htmlspecialchars($this->tt_contentConfig['languageCols'][$lP]) . '</h2> </td>'; // "View page" icon is added: $viewLink = ''; if (!VersionState::cast($this->getPageLayoutController()->pageinfo['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) { $onClick = BackendUtility::viewOnClick($this->id, '', BackendUtility::BEgetRootLine($this->id), '', '', '&L=' . $lP); $viewLink = '<a href="#" class="btn btn-default btn-sm" onclick="' . htmlspecialchars($onClick) . '" title="' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.showPage', true) . '">' . $this->iconFactory->getIcon('actions-view', Icon::SIZE_SMALL)->render() . '</a>'; } // Language overlay page header: if ($lP) { list($lpRecord) = BackendUtility::getRecordsByField('pages_language_overlay', 'pid', $id, 'AND sys_language_uid=' . $lP); BackendUtility::workspaceOL('pages_language_overlay', $lpRecord); $params = '&edit[pages_language_overlay][' . $lpRecord['uid'] . ']=edit&overrideVals[pages_language_overlay][sys_language_uid]=' . $lP; $recordIcon = BackendUtility::wrapClickMenuOnIcon($this->iconFactory->getIconForRecord('pages_language_overlay', $lpRecord, Icon::SIZE_SMALL)->render(), 'pages_language_overlay', $lpRecord['uid']); $editLink = $this->getBackendUser()->check('tables_modify', 'pages_language_overlay') ? '<a href="#" class="btn btn-default btn-sm" onclick="' . htmlspecialchars(BackendUtility::editOnClick($params)) . '" title="' . $this->getLanguageService()->getLL('edit', true) . '">' . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>' : ''; $lPLabel = '<div class="btn-group">' . $viewLink . $editLink . '</div>' . ' ' . $recordIcon . ' ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($lpRecord['title'], 20)); } else { $params = '&edit[pages][' . $this->id . ']=edit'; $recordIcon = BackendUtility::wrapClickMenuOnIcon($this->iconFactory->getIconForRecord('pages', $this->pageRecord, Icon::SIZE_SMALL)->render(), 'pages', $this->id); $editLink = $this->getBackendUser()->check('tables_modify', 'pages_language_overlay') ? '<a href="#" class="btn btn-default btn-sm" onclick="' . htmlspecialchars(BackendUtility::editOnClick($params)) . '" title="' . $this->getLanguageService()->getLL('edit', true) . '">' . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>' : ''; $lPLabel = '<div class="btn-group">' . $viewLink . $editLink . '</div>' . ' ' . $recordIcon . ' ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($this->pageRecord['title'], 20)); } $sCont[$lP] = ' <td nowrap="nowrap" class="t3-page-column t3-page-lang-label">' . $lPLabel . '</td>'; } // Add headers: $out .= '<tr>' . implode($cCont) . '</tr>'; $out .= '<tr>' . implode($sCont) . '</tr>'; unset($cCont, $sCont); // Traverse previously built content for the columns: foreach ($languageColumn as $cKey => $cCont) { $out .= '<tr>'; foreach ($cCont as $languageId => $columnContent) { $out .= '<td valign="top" class="t3-grid-cell t3-page-column t3js-page-column t3js-page-lang-column t3js-page-lang-column-' . $languageId . '">' . $columnContent . '</td>'; } $out .= '</tr>'; if ($this->defLangBinding) { // "defLangBinding" mode foreach ($defLanguageCount[$cKey] as $defUid) { $cCont = array(); foreach ($langListArr as $lP) { $cCont[] = $defLangBinding[$cKey][$lP][$defUid] . $this->newLanguageButton($this->getNonTranslatedTTcontentUids(array($defUid), $id, $lP), $lP, $cKey); } $out .= ' <tr> <td valign="top" class="t3-grid-cell">' . implode('</td>' . ' <td valign="top" class="t3-grid-cell">', $cCont) . '</td> </tr>'; } } } // Finally, wrap it all in a table and add the language selector on top of it: $out = $languageSelector . ' <div class="t3-grid-container"> <table cellpadding="0" cellspacing="0" class="t3-page-columns t3-grid-table t3js-page-columns"> ' . $out . ' </table> </div>'; // CSH: $out .= BackendUtility::cshItem($this->descrTable, 'language_list'); } return $out; }
/** * Rendering the cObject, SWFOBJECT * * @param array $conf Array of TypoScript properties * @return string Output */ public function render($conf = array()) { $params = $prefix = ''; if ($GLOBALS['TSFE']->baseUrl) { $prefix = $GLOBALS['TSFE']->baseUrl; } if ($GLOBALS['TSFE']->absRefPrefix) { $prefix = $GLOBALS['TSFE']->absRefPrefix; } // Initialize content $replaceElementIdString = StringUtility::getUniqueId('mmswf'); $GLOBALS['TSFE']->register['MMSWFID'] = $replaceElementIdString; $layout = isset($conf['layout.']) ? $this->cObj->stdWrap($conf['layout'], $conf['layout.']) : $conf['layout']; $content = str_replace('###ID###', $replaceElementIdString, $layout); $type = isset($conf['type.']) ? $this->cObj->stdWrap($conf['type'], $conf['type.']) : $conf['type']; $typeConf = $conf[$type . '.']; // Add Flowplayer js-file $this->getPageRenderer()->addJsFile($this->getPathToLibrary('flowplayer/flowplayer-3.2.13.min.js')); // Add Flowpayer css for express install $this->getPageRenderer()->addCssFile($this->getPathToLibrary('flowplayer/express-install/express-install.css')); // Add videoJS js-file $this->getPageRenderer()->addJsFile($this->getPathToLibrary('videojs/video-js/video.js')); // Add videoJS css-file $this->getPageRenderer()->addCssFile($this->getPathToLibrary('videojs/video-js/video-js.css')); // Add extended videoJS control bar $this->getPageRenderer()->addJsFile($this->getPathToLibrary('videojs/video-js/controls/control-bar.js')); $this->getPageRenderer()->addCssFile($this->getPathToLibrary('videojs/video-js/controls/control-bar.css')); // Build Flash configuration $player = isset($typeConf['player.']) ? $this->cObj->stdWrap($typeConf['player'], $typeConf['player.']) : $typeConf['player']; if (!$player) { $player = $prefix . $this->getPathToLibrary('flowplayer/flowplayer-3.2.18.swf'); } elseif (strpos($player, 'EXT:') === 0) { $player = $prefix . $GLOBALS['TSFE']->tmpl->getFileName($player); } $installUrl = isset($conf['installUrl.']) ? $this->cObj->stdWrap($conf['installUrl'], $conf['installUrl.']) : $conf['installUrl']; if (!$installUrl) { $installUrl = $prefix . $this->getPathToLibrary('flowplayer/expressinstall.swf'); } elseif (strpos($installUrl, 'EXT:') === 0) { $installUrl = $prefix . $GLOBALS['TSFE']->tmpl->getFileName($installUrl); } $flashVersion = isset($conf['flashVersion.']) ? $this->cObj->stdWrap($conf['flashVersion'], $conf['flashVersion.']) : $conf['flashVersion']; if (!$flashVersion) { $flashVersion = array(9, 115); } $flashConfiguration = array('src' => $player, 'expressInstall' => $installUrl, 'version' => $flashVersion, 'onFail' => '###ONFAIL###'); $flashDownloadUrl = 'http://www.adobe.com/go/getflashplayer'; $onFail = 'function() { if (!(flashembed.getVersion()[0] > 0)) { var message = "<p>" + "' . $GLOBALS['TSFE']->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:media.needFlashPlugin') . '" + "</p>" + "<p>" + "<a href=\\"' . $flashDownloadUrl . '\\">' . $GLOBALS['TSFE']->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:media.downloadFlash') . '</a>" + "</p>"; document.getElementById("' . $replaceElementIdString . '_flash_install_info").innerHTML = "<div class=\\"message\\">" + message + "</div>"; } }'; $flashConfiguration = json_encode($flashConfiguration); $flashConfiguration = str_replace('"###ONFAIL###"', $onFail, $flashConfiguration); $filename = isset($conf['file.']) ? $this->cObj->stdWrap($conf['file'], $conf['file.']) : $conf['file']; if ($filename) { if (strpos($filename, '://') !== false) { $conf['flashvars.']['url'] = $filename; } else { if ($prefix) { $conf['flashvars.']['url'] = $prefix . $filename; } else { $conf['flashvars.']['url'] = str_repeat('../', substr_count($player, '/')) . $filename; } } } if (is_array($conf['sources'])) { foreach ($conf['sources'] as $key => $source) { if (strpos($source, '://') === false) { $conf['sources'][$key] = $prefix . $source; } } } if (is_array($conf['audioSources'])) { foreach ($conf['audioSources'] as $key => $source) { if (strpos($source, '://') === false) { $conf['audioSources'][$key] = $prefix . $source; } } } if (isset($conf['audioFallback']) && strpos($conf['audioFallback'], '://') === false) { $conf['audioFallback'] = $prefix . $conf['audioFallback']; } if (isset($conf['caption']) && strpos($conf['caption'], '://') === false) { $conf['caption'] = $prefix . $conf['caption']; } // Write calculated values in conf for the hook $conf['player'] = $player ?: $filename; $conf['installUrl'] = $installUrl; $conf['filename'] = $conf['flashvars.']['url']; $conf['prefix'] = $prefix; // merge with default parameters $conf['flashvars.'] = array_merge((array) $typeConf['default.']['flashvars.'], (array) $conf['flashvars.']); $conf['params.'] = array_merge((array) $typeConf['default.']['params.'], (array) $conf['params.']); $conf['attributes.'] = array_merge((array) $typeConf['default.']['attributes.'], (array) $conf['attributes.']); $conf['embedParams'] = 'flashvars, params, attributes'; // Hook for manipulating the conf array, it's needed for some players like flowplayer if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/hooks/class.tx_cms_mediaitems.php']['swfParamTransform'])) { foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/hooks/class.tx_cms_mediaitems.php']['swfParamTransform'] as $classRef) { GeneralUtility::callUserFunction($classRef, $conf, $this); } } // Flowplayer config $flowplayerVideoConfig = array(); $flowplayerAudioConfig = array(); if (is_array($conf['flashvars.']) && is_array($typeConf['mapping.']['flashvars.'])) { ArrayUtility::remapArrayKeys($conf['flashvars.'], $typeConf['mapping.']['flashvars.']); } else { $conf['flashvars.'] = array(); } $conf['videoflashvars'] = $conf['flashvars.']; $conf['audioflashvars'] = $conf['flashvars.']; $conf['audioflashvars']['url'] = $conf['audioFallback']; // Render video sources $videoSources = ''; if (is_array($conf['sources'])) { foreach ($conf['sources'] as $source) { $fileinfo = GeneralUtility::split_fileref($source); $mimeType = $this->mimeTypes[$fileinfo['fileext']]['video']; $videoSources .= '<source src="' . $source . '"' . ($mimeType ? ' type="' . $mimeType . '"' : '') . ' />' . LF; } } // Render audio sources $audioSources = ''; if (is_array($conf['audioSources'])) { foreach ($conf['audioSources'] as $source) { $fileinfo = GeneralUtility::split_fileref($source); $mimeType = $this->mimeTypes[$fileinfo['fileext']]['audio']; $audioSources .= '<source src="' . $source . '"' . ($mimeType ? ' type="' . $mimeType . '"' : '') . ' />' . LF; } } // Configure captions if ($conf['type'] === 'video' && isset($conf['caption'])) { // Assemble captions track tag $videoCaptions = '<track id="' . $replaceElementIdString . '_captions_track" kind="captions" src="' . $conf['caption'] . '" default>' . LF; // Add videoJS extension for captions $this->getPageRenderer()->addJsFile($this->getPathToLibrary('videojs/video-js/controls/captions.js')); // Flowplayer captions $conf['videoflashvars']['captionUrl'] = $conf['caption']; // Flowplayer captions plugin configuration $flowplayerVideoConfig = array_merge_recursive($flowplayerVideoConfig, $this->flowplayerCaptionsConfig); } // Configure flowplayer audio fallback if (isset($conf['audioFallback'])) { $flowplayerAudioConfig = array_merge_recursive($flowplayerAudioConfig, $this->flowplayerAudioConfig); } // Configure audio description if ($conf['type'] == 'video') { if (is_array($conf['audioSources']) && !empty($conf['audioSources'])) { // Add videoJS audio description toggle $this->getPageRenderer()->addJsFile($this->getPathToLibrary('videojs/video-js/controls/audio-description.js')); } if (isset($conf['audioFallback'])) { // Audio description flowplayer config (remove controls) $flowplayerAudioConfig = array_merge_recursive($flowplayerAudioConfig, $this->flowplayerAudioDescriptionConfig); } } // Assemble Flowplayer configuration if (!empty($conf['videoflashvars'])) { $flowplayerVideoConfig = array_merge_recursive($flowplayerVideoConfig, array('clip' => $conf['videoflashvars'])); } $flowplayerVideoJsonConfig = str_replace(array('"true"', '"false"'), array('true', 'false'), json_encode($flowplayerVideoConfig)); if (!empty($conf['audioflashvars'])) { $flowplayerAudioConfig = array_merge_recursive($flowplayerAudioConfig, array('clip' => $conf['audioflashvars'])); } $flowplayerAudioJsonConfig = str_replace(array('"true"', '"false"'), array('true', 'false'), json_encode($flowplayerAudioConfig)); // Assemble param tags (required?) if (is_array($conf['params.']) && is_array($typeConf['mapping.']['params.'])) { ArrayUtility::remapArrayKeys($conf['params.'], $typeConf['mapping.']['params.']); } $videoFlashParams = ''; if (is_array($conf['params.'])) { foreach ($conf['params.'] as $name => $value) { $videoFlashParams .= '<param name="' . $name . '" value="' . $value . '" />' . LF; } } $audioFlashParams = $videoFlashParams; // Required param tags $videoFlashParams .= '<param name="movie" value="' . $player . '" />' . LF; $videoFlashParams .= '<param name="flashvars" value=\'config=' . $flowplayerVideoJsonConfig . '\' />' . LF; $audioFlashParams .= '<param name="movie" value="' . $player . '" />' . LF; $audioFlashParams .= '<param name="flashvars" value=\'config=' . $flowplayerAudioJsonConfig . '\' />' . LF; // Assemble audio/video tag attributes $attributes = ''; if (is_array($conf['attributes.']) && is_array($typeConf['attributes.']['params.'])) { ArrayUtility::remapArrayKeys($conf['attributes.'], $typeConf['attributes.']['params.']); } foreach ($this->html5TagAttributes as $attribute) { if ($conf['attributes.'][$attribute] === 'true' || $conf['attributes.'][$attribute] === strtolower($attribute) || $conf['attributes.'][$attribute] === $attribute) { $attributes .= strtolower($attribute) . '="' . strtolower($attribute) . '" '; } } // Media dimensions $width = isset($conf['width.']) ? $this->cObj->stdWrap($conf['width'], $conf['width.']) : $conf['width']; if (!$width) { $width = $conf[$type . '.']['defaultWidth']; } $height = isset($conf['height.']) ? $this->cObj->stdWrap($conf['height'], $conf['height.']) : $conf['height']; if (!$height) { $height = $conf[$type . '.']['defaultHeight']; } // Alternate content $alternativeContent = isset($conf['alternativeContent.']) ? $this->cObj->stdWrap($conf['alternativeContent'], $conf['alternativeContent.']) : $conf['alternativeContent']; // Render video if ($conf['type'] === 'video') { // add preview image, html5 poster attribute $sourceBasePath = substr($conf['sources'][1], 0, strrpos($conf['sources'][1], '.')); foreach (['.jpg', '.jpeg', '.png'] as $fileExtension) { $posterFilePath = $GLOBALS['TSFE']->tmpl->getFileName($sourceBasePath . $fileExtension); if (file_exists($posterFilePath)) { $attributes .= ' poster="' . htmlspecialchars($posterFilePath) . '"'; break; } } if ($conf['preferFlashOverHtml5']) { // Flash with video tag fallback $conf['params.']['playerFallbackOrder'] = array('flash', 'html5'); $flashDivContent = $videoFlashParams . LF . '<video id="' . $replaceElementIdString . '_video_js" class="video-js" ' . $attributes . 'controls="controls" mediagroup="' . $replaceElementIdString . '" width="' . $width . '" height="' . $height . '">' . LF . $videoSources . $videoCaptions . $alternativeContent . LF . '</video>' . LF; $divContent = ' <div id="' . $replaceElementIdString . '_flash_install_info" class="flash-install-info"></div>' . LF . '<noscript>' . LF . '<object id="' . $replaceElementIdString . '_vjs_flash" type="application/x-shockwave-flash" data="' . $player . '" width="' . $width . '" height="' . $height . '">' . LF . $flashDivContent . '</object>' . LF . '</noscript>' . LF; $content = str_replace('###SWFOBJECT###', '<div id="' . $replaceElementIdString . '_video" class="flashcontainer" style="width:' . $width . 'px; height:' . $height . 'px;">' . LF . $divContent . '</div>', $content); } else { // Video tag with Flash fallback $conf['params.']['playerFallbackOrder'] = array('html5', 'flash'); $videoTagContent = $videoSources . $videoCaptions; if (isset($conf['videoflashvars']['url'])) { $videoTagContent .= ' <noscript>' . LF . '<object class="vjs-flash-fallback" id="' . $replaceElementIdString . '_vjs_flash_fallback" type="application/x-shockwave-flash" data="' . $player . '" width="' . $width . '" height="' . $height . '">' . LF . $videoFlashParams . LF . $alternativeContent . LF . '</object>' . LF . '</noscript>'; } $divContent = ' <div id="' . $replaceElementIdString . '_flash_install_info" class="flash-install-info"></div>' . LF . '<video id="' . $replaceElementIdString . '_video_js" class="video-js" ' . $attributes . 'controls="controls" mediagroup="' . $replaceElementIdString . '" width="' . $width . '" height="' . $height . '">' . LF . $videoTagContent . '</video>'; $content = str_replace('###SWFOBJECT###', '<div id="' . $replaceElementIdString . '_video" class="video-js-box" style="width:' . $width . 'px; height:' . $height . 'px;">' . LF . $divContent . '</div>', $content); } } // Render audio if ($conf['type'] === 'audio' || $audioSources || isset($conf['audioFallback'])) { if ($conf['preferFlashOverHtml5']) { // Flash with audio tag fallback $flashDivContent = $audioFlashParams . LF . '<audio id="' . $replaceElementIdString . '_audio_element"' . $attributes . ($conf['type'] === 'video' ? ' mediagroup="' . $replaceElementIdString . 'style="position:absolute;left:-10000px;"' : ' controls="controls"') . ' style="width:' . $width . 'px; height:' . $height . 'px;">' . LF . $audioSources . $alternativeContent . LF . '</audio>' . LF; $divContent = ($conf['type'] === 'video' ? '' : '<div id="' . $replaceElementIdString . '_flash_install_info" class="flash-install-info"></div>' . LF) . '<noscript>' . LF . '<object id="' . $replaceElementIdString . '_audio_flash" type="application/x-shockwave-flash" data="' . $player . '" width="' . ($conf['type'] === 'video' ? 0 : $width) . '" height="' . ($conf['type'] === 'video' ? 0 : $height) . '">' . LF . $flashDivContent . '</object>' . LF . '</noscript>' . LF; $audioContent = '<div id="' . $replaceElementIdString . '_audio_box" class="audio-flash-container" style="width:' . ($conf['type'] === 'video' ? 0 : $width) . 'px; height:' . ($conf['type'] === 'video' ? 0 : $height) . 'px;">' . LF . $divContent . '</div>'; } else { // Audio tag with Flash fallback $audioTagContent = $audioSources; if (isset($conf['audioflashvars']['url'])) { $audioTagContent .= ' <noscript>' . LF . '<object class="audio-flash-fallback" id="' . $replaceElementIdString . '_audio_flash" type="application/x-shockwave-flash" data="' . $player . '" width="' . $width . '" height="' . $height . '">' . LF . $audioFlashParams . LF . $alternativeContent . LF . '</object>' . LF . '</noscript>'; } $divContent = ($conf['type'] === 'video' ? '' : '<div id="' . $replaceElementIdString . '_flash_install_info" class="flash-install-info"></div>' . LF) . '<audio id="' . $replaceElementIdString . '_audio_element" class="audio-element"' . $attributes . ($conf['type'] === 'video' ? ' mediagroup="' . $replaceElementIdString . '" style="position:absolute;left:-10000px;"' : ' controls="controls"') . '>' . LF . $audioTagContent . '</audio>' . LF . $audioSourcesEmbeddingJsScript; $audioContent = '<div id="' . $replaceElementIdString . '_audio_box" class="audio-box" style="width:' . ($conf['type'] === 'video' ? 0 : $width) . 'px; height:' . ($conf['type'] === 'video' ? 0 : $height) . 'px;">' . LF . $divContent . '</div>'; } if ($conf['type'] === 'audio') { $content = str_replace('###SWFOBJECT###', $audioContent, $content); } else { $content .= LF . $audioContent; } } // Assemble inline JS code $videoJsSetup = ''; $flowplayerHandlers = ''; if ($conf['type'] === 'video') { // Assemble videoJS options $videoJsOptions = array(); foreach ($this->videoJsOptions as $videoJsOption) { if (isset($conf['params.'][$videoJsOption])) { $videoJsOptions[$videoJsOption] = $conf['params.'][$videoJsOption]; } } $videoJsOptions = !empty($videoJsOptions) ? json_encode($videoJsOptions) : '{}'; // videoJS setup and videoJS listeners for audio description synchronisation if ($audioSources || isset($conf['audioFallback'])) { $videoJsSetup = ' var ' . $replaceElementIdString . '_video = VideoJS.setup("' . $replaceElementIdString . '_video_js", ' . $videoJsOptions . '); var ' . $replaceElementIdString . '_video_element = document.getElementById("' . $replaceElementIdString . '_video_js"); var ' . $replaceElementIdString . '_audio_element = document.getElementById("' . $replaceElementIdString . '_audio_element"); if (!!' . $replaceElementIdString . '_video_element && !!' . $replaceElementIdString . '_audio_element) { ' . $replaceElementIdString . '_audio_element.muted = true; VideoJS.addListener(' . $replaceElementIdString . '_video_element, "pause", function () { document.getElementById("' . $replaceElementIdString . '_audio_element").pause(); }); VideoJS.addListener(' . $replaceElementIdString . '_video_element, "play", function () { try {document.getElementById("' . $replaceElementIdString . '_audio_element").currentTime = document.getElementById("' . $replaceElementIdString . '_video_js").currentTime} catch(e) {}; document.getElementById("' . $replaceElementIdString . '_audio_element").play(); }); VideoJS.addListener(' . $replaceElementIdString . '_video_element, "seeked", function () { document.getElementById("' . $replaceElementIdString . '_audio_element").currentTime = document.getElementById("' . $replaceElementIdString . '_video_js").currentTime; }); VideoJS.addListener(' . $replaceElementIdString . '_video_element, "volumechange", function () { document.getElementById("' . $replaceElementIdString . '_audio_element").volume = document.getElementById("' . $replaceElementIdString . '_video_js").volume; }); }'; } else { $videoJsSetup = ' var ' . $replaceElementIdString . '_video = VideoJS.setup("' . $replaceElementIdString . '_video_js", ' . $videoJsOptions . '); '; } // Prefer Flash or fallback to Flash $videoSourcesEmbedding = ''; // If we have a video file for Flash if (isset($conf['filename'])) { // If we prefer Flash if ($conf['preferFlashOverHtml5']) { $videoTagAssembly = ''; // Create "source" elements if (is_array($conf['sources']) && !empty($conf['sources'])) { foreach ($conf['sources'] as $source) { $fileinfo = GeneralUtility::split_fileref($source); $mimeType = $this->mimeTypes[$fileinfo['fileext']]['video']; $videoTagAssembly .= ' ' . $replaceElementIdString . '_video_js.appendChild($f.extend(document.createElement("source"), { src: "' . $source . '", type: "' . $mimeType . '" }));'; } // Create "track" elements if (isset($conf['caption'])) { // Assemble captions track tag // It will take a while before the captions are loaded and parsed... $videoTagAssembly .= ' var track = document.createElement("track"); track.setAttribute("src", "' . $conf['caption'] . '"); track.setAttribute("id", "' . $replaceElementIdString . '_captions_track"); track.setAttribute("kind", "captions"); track.setAttribute("default", "default"); ' . $replaceElementIdString . '_video_js.appendChild(track);'; } $videoTagAssembly .= ' $f.extend(' . $replaceElementIdString . '_video_js, { id: "' . $replaceElementIdString . '_video_js", className: "video-js", controls: "controls", mediagroup: "' . $replaceElementIdString . '", preload: "none", width: "' . $width . '", height: "' . $height . '" }); ' . $replaceElementIdString . '_video.appendChild(' . $replaceElementIdString . '_video_js); ' . $replaceElementIdString . '_video.className = "video-js-box";'; $videoTagAssembly .= $videoJsSetup; } $videoSourcesEmbedding = ' var ' . $replaceElementIdString . '_video = document.getElementById("' . $replaceElementIdString . '_video"); var ' . $replaceElementIdString . '_video_js = document.createElement("video"); if (flashembed.getVersion()[0] > 0) { // Flash is available var videoPlayer = flowplayer("' . $replaceElementIdString . '_video", ' . $flashConfiguration . ', ' . $flowplayerVideoJsonConfig . ').load(); videoPlayer.onBeforeUnload(function () { return false; }); } else if (!!' . $replaceElementIdString . '_video_js.canPlayType) { // Flash is not available: fallback to videoJS if video tag is supported ' . $videoTagAssembly . ' } else { // Neither Flash nor video is available: offer to install Flash flashembed("' . $replaceElementIdString . '_video", ' . $flashConfiguration . '); }'; } elseif (is_array($conf['sources'])) { // HTML5 is the preferred rendering method // Test whether the browser supports any of types of the provided sources $supported = array(); foreach ($conf['sources'] as $source) { $fileinfo = GeneralUtility::split_fileref($source); $mimeType = $this->mimeTypes[$fileinfo['fileext']]['video']; $supported[] = $replaceElementIdString . '_videoTag.canPlayType("' . $mimeType . '") != ""'; } // Testing whether the browser supports the video tag with any of the provided source types // If no support, embed flowplayer $videoSourcesEmbedding = ' var ' . $replaceElementIdString . '_videoTag = document.createElement(\'video\'); var ' . $replaceElementIdString . '_video_box = document.getElementById("' . $replaceElementIdString . '_video"); if (' . $replaceElementIdString . '_video_box) { if (!' . $replaceElementIdString . '_videoTag || !' . $replaceElementIdString . '_videoTag.canPlayType || !(' . (!empty($supported) ? implode(' || ', $supported) : 'false') . ')) { // Avoid showing an empty video element if (document.getElementById("' . $replaceElementIdString . '_video_js")) { document.getElementById("' . $replaceElementIdString . '_video_js").style.display = "none"; } if (flashembed.getVersion()[0] > 0) { // Flash is available var videoPlayer = flowplayer("' . $replaceElementIdString . '_video", ' . $flashConfiguration . ', ' . $flowplayerVideoJsonConfig . ').load(); videoPlayer.onBeforeUnload(function () { return false; }); } else { // Neither Flash nor video is available: offer to install Flash flashembed("' . $replaceElementIdString . '_video", ' . $flashConfiguration . '); } } else {' . $videoJsSetup . ' } }'; } } } // Audio fallback to Flash $audioSourcesEmbedding = ''; // If we have an audio file for Flash if (isset($conf['audioFallback'])) { // If we prefer Flash in if ($conf['preferFlashOverHtml5']) { $audioTagAssembly = ''; // Create "source" elements if (is_array($conf['audioSources']) && !empty($conf['audioSources'])) { foreach ($conf['audioSources'] as $source) { $fileinfo = GeneralUtility::split_fileref($source); $mimeType = $this->mimeTypes[$fileinfo['fileext']]['audio']; $audioTagAssembly .= ' ' . $replaceElementIdString . '_audio_element.appendChild($f.extend(document.createElement("source"), { src: "' . $source . '", type: "' . $mimeType . '" }));'; } $audioTagAssembly .= ' $f.extend(' . $replaceElementIdString . '_audio_element, { id: "' . $replaceElementIdString . '_audio_element", className: "audio-element", controls: "' . ($conf['type'] === 'video' ? '' : 'controls') . '", mediagroup: "' . $replaceElementIdString . '", preload: "none", width: "' . ($conf['type'] === 'video' ? 0 : $width) . 'px", height: "' . ($conf['type'] === 'video' ? 0 : $height) . 'px" }); ' . $replaceElementIdString . '_audio_box.appendChild(' . $replaceElementIdString . '_audio_element); ' . $replaceElementIdString . '_audio_box.className = "audio-box";'; } $audioSourcesEmbedding = ' var ' . $replaceElementIdString . '_audio_box = document.getElementById("' . $replaceElementIdString . '_audio_box"); var ' . $replaceElementIdString . '_audio_element = document.createElement("audio"); if (flashembed.getVersion()[0] > 0) { // Flash is available var audioPlayer = flowplayer("' . $replaceElementIdString . '_audio_box", ' . $flashConfiguration . ', ' . $flowplayerAudioJsonConfig . ').load(); audioPlayer.onBeforeUnload(function () { return false; }); ' . ($conf['type'] === 'video' ? 'audioPlayer.mute();' : '') . ' } else if (!!' . $replaceElementIdString . '_audio_element.canPlayType) { // Flash is not available: fallback to audio element if audio tag is supported ' . $audioTagAssembly . ' } else { // Neither Flash nor audio is available: offer to install Flash if this is not an audio description of a video ' . ($conf['type'] === 'video' ? '' : 'flashembed("' . $replaceElementIdString . '_audio_box", ' . $flashConfiguration . ');') . ' }'; } elseif (is_array($conf['audioSources'])) { // HTML5 is the preferred rendering method // Test whether the browser supports any of types of the provided sources $supported = array(); foreach ($conf['audioSources'] as $source) { $fileinfo = GeneralUtility::split_fileref($source); $mimeType = $this->mimeTypes[$fileinfo['fileext']]['audio']; $supported[] = $replaceElementIdString . '_audioTag.canPlayType("' . $mimeType . '") != ""'; } // Testing whether the browser supports the audio tag with any of the provided source types // If no support, embed flowplayer $audioSourcesEmbedding = ' var ' . $replaceElementIdString . '_audioTag = document.createElement(\'audio\'); var ' . $replaceElementIdString . '_audio_box = document.getElementById("' . $replaceElementIdString . '_audio_box"); if (' . $replaceElementIdString . '_audio_box) { if (!' . $replaceElementIdString . '_audioTag || !' . $replaceElementIdString . '_audioTag.canPlayType || !(' . (!empty($supported) ? implode(' || ', $supported) : 'false') . ')) { // Avoid showing an empty audio element if (document.getElementById("' . $replaceElementIdString . '_audio_element")) { document.getElementById("' . $replaceElementIdString . '_audio_element").style.display = "none"; } if (flashembed.getVersion()[0] > 0) { var audioPlayer = flowplayer("' . $replaceElementIdString . '_audio_box", ' . $flashConfiguration . ', ' . $flowplayerAudioJsonConfig . ').load(); audioPlayer.onBeforeUnload(function () { return false; }); ' . ($conf['type'] === 'video' ? 'audioPlayer.mute()' : '') . ' } else { // Neither Flash nor audio is available: offer to install Flash if this is not an audio description of a video ' . ($conf['type'] === 'video' ? '' : 'flashembed("' . $replaceElementIdString . '_audio_box", ' . $flashConfiguration . ');') . ' } } }'; } // Flowplayer eventHandlers for audio description synchronisation $flowplayerHandlers = ''; if ($conf['type'] === 'video') { $flowplayerHandlers = ' if (flashembed.getVersion()[0] > 0) { // Flash is available var videoPlayer = flowplayer("' . $replaceElementIdString . '_video"); if (videoPlayer) { // Control audio description through video control bar videoPlayer.onVolume(function (volume) { flowplayer("' . $replaceElementIdString . '_audio_box").setVolume(volume); }); videoPlayer.onMute(function () { flowplayer("' . $replaceElementIdString . '_audio_box").mute(); }); videoPlayer.onUnmute(function () { flowplayer("' . $replaceElementIdString . '_audio_box").unmute(); }); videoPlayer.onPause(function () { flowplayer("' . $replaceElementIdString . '_audio_box").pause(); }); videoPlayer.onResume(function () { flowplayer("' . $replaceElementIdString . '_audio_box").resume(); }); videoPlayer.onStart(function () { flowplayer("' . $replaceElementIdString . '_audio_box").play(); }); videoPlayer.onStop(function () { flowplayer("' . $replaceElementIdString . '_audio_box").stop(); }); videoPlayer.onSeek(function (clip, seconds) { flowplayer("' . $replaceElementIdString . '_audio_box").seek(seconds); }); // Mute audio description on start flowplayer("' . $replaceElementIdString . '_audio_box").onStart(function () { this.mute()}); // Audio description toggle var videoContainer = document.getElementById("' . $replaceElementIdString . '_video"); var buttonContainer = document.createElement("div"); $f.extend(buttonContainer, { id: "' . $replaceElementIdString . '_audio_description_toggle", className: "vjs-audio-description-control" }); var button = document.createElement("div"); buttonContainer.appendChild(button); buttonContainer.style.position = "relative"; buttonContainer.style.left = (parseInt(' . $width . ', 10)-27) + "px"; videoContainer.parentNode.insertBefore(buttonContainer, videoContainer.nextSibling); VideoJS.addListener(buttonContainer, "click", function () { var buttonContainer = document.getElementById("' . $replaceElementIdString . '_audio_description_toggle"); var state = buttonContainer.getAttribute("data-state"); if (state == "enabled") { buttonContainer.setAttribute("data-state", "disabled"); flowplayer("' . $replaceElementIdString . '_audio_box").mute(); } else { buttonContainer.setAttribute("data-state", "enabled"); flowplayer("' . $replaceElementIdString . '_audio_box").unmute(); } }); } }'; } } // Wrap up inline JS code $jsInlineCode = $audioSourcesEmbedding . $videoSourcesEmbedding . $flowplayerHandlers; if ($jsInlineCode) { $jsInlineCode = 'VideoJS.DOMReady(function(){' . $jsInlineCode . LF . '});'; } $this->getPageRenderer()->addJsInlineCode($replaceElementIdString, $jsInlineCode); if (isset($conf['stdWrap.'])) { $content = $this->cObj->stdWrap($content, $conf['stdWrap.']); } return $content; }
/** * Check if an update is possible at all * * @param string $version The target version number * @return bool TRUE on success * @throws \TYPO3\CMS\Install\Status\Exception */ public function checkPreConditions($version) { $success = true; $messages = array(); /** @var StatusUtility $statusUtility */ $statusUtility = $this->objectManager->get(StatusUtility::class); // Folder structure test: Update can be done only if folder structure returns no errors /** @var $folderStructureFacade \TYPO3\CMS\Install\FolderStructure\StructureFacade */ $folderStructureFacade = $this->objectManager->get(DefaultFactory::class)->getStructure(); $folderStructureErrors = $statusUtility->filterBySeverity($folderStructureFacade->getStatus(), 'error'); $folderStructureWarnings = $statusUtility->filterBySeverity($folderStructureFacade->getStatus(), 'warning'); if (!empty($folderStructureErrors) || !empty($folderStructureWarnings)) { $success = false; /** @var $message StatusInterface */ $message = $this->objectManager->get(ErrorStatus::class); $message->setTitle('Automatic TYPO3 CMS core update not possible: Folder structure has errors or warnings'); $message->setMessage('To perform an update, the folder structure of this TYPO3 CMS instance must' . ' stick to the conventions, or the update process could lead to unexpected' . ' results and may be hazardous to your system'); $messages[] = $message; } // No core update on windows if (TYPO3_OS === 'WIN') { $success = false; /** @var $message StatusInterface */ $message = $this->objectManager->get(ErrorStatus::class); $message->setTitle('Automatic TYPO3 CMS core update not possible: Update not supported on Windows OS'); $messages[] = $message; } if ($success) { // Explicit write check to document root $file = PATH_site . StringUtility::getUniqueId('install-core-update-test-'); $result = @touch($file); if (!$result) { $success = false; /** @var $message StatusInterface */ $message = $this->objectManager->get(ErrorStatus::class); $message->setTitle('Automatic TYPO3 CMS core update not possible: No write access to document root'); $message->setMessage('Could not write a file in path "' . PATH_site . '"!'); $messages[] = $message; } else { unlink($file); } if (!$this->checkCoreFilesAvailable($version)) { // Explicit write check to upper directory of current core location $coreLocation = @realPath($this->symlinkToCoreFiles . '/../'); $file = $coreLocation . '/' . StringUtility::getUniqueId('install-core-update-test-'); $result = @touch($file); if (!$result) { $success = false; /** @var $message StatusInterface */ $message = $this->objectManager->get(ErrorStatus::class); $message->setTitle('Automatic TYPO3 CMS core update not possible: No write access to TYPO3 CMS core location'); $message->setMessage('New TYPO3 CMS core should be installed in "' . $coreLocation . '", but this directory is not writable!'); $messages[] = $message; } else { unlink($file); } } } if ($success && !$this->coreVersionService->isInstalledVersionAReleasedVersion()) { $success = false; /** @var $message StatusInterface */ $message = $this->objectManager->get(ErrorStatus::class); $message->setTitle('Automatic TYPO3 CMS core update not possible: You are running a development version of TYPO3'); $message->setMessage('Your current version is specified as ' . $this->coreVersionService->getInstalledVersion() . '.' . ' This is a development version and can not be updated automatically. If this is a "git"' . ' checkout, please update using git directly.'); $messages[] = $message; } $this->messages = $messages; return $success; }
/** * @param string $name the field name * @param int $timestamp the unix timestamp * @param string $type [datetime, date, time, timesec, year] * * @return string */ protected function getDateTimePickerField($name, $timestamp, $type) { $dateFormat = $GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? '%H:%M %m-%d-%Y' : '%H:%M %d-%m-%Y'; $value = $timestamp > 0 ? strftime($dateFormat, $timestamp) : ''; $id = StringUtility::getUniqueId('dt_'); $html = array(); $html[] = '<div class="input-group" id="' . $id . '-wrapper">'; $html[] = ' <input data-formengine-input-name="' . htmlspecialchars($name) . '" value="' . $value . '" class="form-control t3js-datetimepicker t3js-clearable" data-date-type="' . htmlspecialchars($type) . '" data-date-offset="0" type="text" id="' . $id . '">'; $html[] = ' <input name="' . htmlspecialchars($name) . '" value="' . (int) $timestamp . '" type="hidden">'; $html[] = ' <span class="input-group-btn">'; $html[] = ' <label class="btn btn-default" for="' . $id . '">'; $html[] = ' <span class="fa fa-calendar"></span>'; $html[] = ' </label>'; $html[] = ' </span>'; $html[] = '</div>'; return implode(LF, $html); }