/**
  * @dataProvider structureStringIsParsedDataProvider
  * @test
  */
 public function getCurrentStructureDomObjectIdPrefixReturnsExceptedStringAfterInitializationByStructureString($string, array $_, array $expectedFormName)
 {
     /** @var InlineStackProcessor|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface $subject */
     $subject = new InlineStackProcessor();
     $subject->initializeByParsingDomObjectIdString($string);
     $this->assertEquals($expectedFormName['object'], $subject->getCurrentStructureDomObjectIdPrefix('pageId'));
 }
    /**
     * Get a selector as used for the select type, to select from all available
     * records and to create a relation to the embedding record (e.g. like MM).
     *
     * @param array $selItems Array of all possible records
     * @param array $conf TCA configuration of the parent(!) field
     * @param array $PA An array with additional configuration options
     * @param array $uniqueIds The uids that have already been used and should be unique
     * @return string A HTML <select> box with all possible records
     */
    protected function renderPossibleRecordsSelectorTypeSelect($selItems, $conf, &$PA, $uniqueIds = array())
    {
        $foreign_table = $conf['foreign_table'];
        $foreign_selector = $conf['foreign_selector'];
        $PA = array();
        $PA['fieldConf'] = $GLOBALS['TCA'][$foreign_table]['columns'][$foreign_selector];
        $PA['fieldTSConfig'] = FormEngineUtility::getTSconfigForTableRow($foreign_table, array(), $foreign_selector);
        $config = $PA['fieldConf']['config'];
        $item = '';
        // @todo $disabled is not present - should be read from config?
        $disabled = FALSE;
        if (!$disabled) {
            $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid']);
            // Create option tags:
            $opt = array();
            $styleAttrValue = '';
            foreach ($selItems as $p) {
                if ($config['iconsInOptionTags']) {
                    $styleAttrValue = FormEngineUtility::optionTagStyle($p[2]);
                }
                if (!in_array($p[1], $uniqueIds)) {
                    $opt[] = '<option value="' . htmlspecialchars($p[1]) . '"' . ($styleAttrValue ? ' style="' . htmlspecialchars($styleAttrValue) . '"' : '') . '>' . htmlspecialchars($p[0]) . '</option>';
                }
            }
            // Put together the selector box:
            $itemListStyle = isset($config['itemListStyle']) ? ' style="' . htmlspecialchars($config['itemListStyle']) . '"' : '';
            $size = (int) $conf['size'];
            $size = $conf['autoSizeMax'] ? MathUtility::forceIntegerInRange(count($selItems) + 1, MathUtility::forceIntegerInRange($size, 1), $conf['autoSizeMax']) : $size;
            $onChange = 'return inline.importNewRecord(' . GeneralUtility::quoteJSvalue($nameObject . '-' . $conf['foreign_table']) . ')';
            $item = '
				<select id="' . $nameObject . '-' . $conf['foreign_table'] . '_selector" class="form-control"' . ($size ? ' size="' . $size . '"' : '') . ' onchange="' . htmlspecialchars($onChange) . '"' . $PA['onFocus'] . $itemListStyle . ($conf['foreign_unique'] ? ' isunique="isunique"' : '') . '>
					' . implode('', $opt) . '
				</select>';
            if ($size <= 1) {
                // Add a "Create new relation" link for adding new relations
                // This is necessary, if the size of the selector is "1" or if
                // there is only one record item in the select-box, that is selected by default
                // The selector-box creates a new relation on using a onChange event (see some line above)
                if (!empty($conf['appearance']['createNewRelationLinkTitle'])) {
                    $createNewRelationText = $this->getLanguageService()->sL($conf['appearance']['createNewRelationLinkTitle'], TRUE);
                } else {
                    $createNewRelationText = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.createNewRelation', TRUE);
                }
                $item .= '
				<span class="input-group-btn">
					<a href="#" class="btn btn-default" onclick="' . htmlspecialchars($onChange) . '">
						' . IconUtility::getSpriteIcon('actions-document-new', array('title' => $createNewRelationText)) . $createNewRelationText . '
					</a>
				</span>';
            } else {
                $item .= '
				<span class="input-group-btn btn"></span>';
            }
            // Wrap the selector and add a spacer to the bottom
            $item = '<div class="input-group form-group t3js-formengine-validation-marker ' . $this->inlineData['config'][$nameObject]['md5'] . '">' . $item . '</div>';
        }
        return $item;
    }
 /**
  * Get a selector as used for the select type, to select from all available
  * records and to create a relation to the embedding record (e.g. like MM).
  *
  * @param array $config TCA inline configuration of the parent(!) field
  * @param array $uniqueIds The uids that have already been used and should be unique
  * @return string A HTML <select> box with all possible records
  */
 protected function renderPossibleRecordsSelectorTypeSelect(array $config, array $uniqueIds)
 {
     $possibleRecords = $config['selectorOrUniquePossibleRecords'];
     $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
     // Create option tags:
     $opt = [];
     foreach ($possibleRecords as $p) {
         if (!in_array($p[1], $uniqueIds)) {
             $opt[] = '<option value="' . htmlspecialchars($p[1]) . '">' . htmlspecialchars($p[0]) . '</option>';
         }
     }
     // Put together the selector box:
     $size = (int) $config['size'];
     $size = $config['autoSizeMax'] ? MathUtility::forceIntegerInRange(count($possibleRecords) + 1, MathUtility::forceIntegerInRange($size, 1), $config['autoSizeMax']) : $size;
     $onChange = 'return inline.importNewRecord(' . GeneralUtility::quoteJSvalue($nameObject . '-' . $config['foreign_table']) . ')';
     $item = '
         <select id="' . $nameObject . '-' . $config['foreign_table'] . '_selector" class="form-control"' . ($size ? ' size="' . $size . '"' : '') . ' onchange="' . htmlspecialchars($onChange) . '"' . ($config['foreign_unique'] ? ' isunique="isunique"' : '') . '>
             ' . implode('', $opt) . '
         </select>';
     if ($size <= 1) {
         // Add a "Create new relation" link for adding new relations
         // This is necessary, if the size of the selector is "1" or if
         // there is only one record item in the select-box, that is selected by default
         // The selector-box creates a new relation on using an onChange event (see some line above)
         if (!empty($config['appearance']['createNewRelationLinkTitle'])) {
             $createNewRelationText = $this->getLanguageService()->sL($config['appearance']['createNewRelationLinkTitle'], true);
         } else {
             $createNewRelationText = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.createNewRelation', true);
         }
         $item .= '
         <span class="input-group-btn">
             <a href="#" class="btn btn-default" onclick="' . htmlspecialchars($onChange) . '" title="' . $createNewRelationText . '">
                 ' . $this->iconFactory->getIcon('actions-document-new', Icon::SIZE_SMALL)->render() . $createNewRelationText . '
             </a>
         </span>';
     } else {
         $item .= '
         <span class="input-group-btn btn"></span>';
     }
     // Wrap the selector and add a spacer to the bottom
     $item = '<div class="input-group form-group t3js-formengine-validation-marker ' . $this->inlineData['config'][$nameObject]['md5'] . '">' . $item . '</div>';
     return $item;
 }
    /**
     * Render the control-icons for a record header (create new, sorting, delete, disable/enable).
     * Most of the parts are copy&paste from TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList and
     * modified for the JavaScript calls here
     *
     * @param array $data Current data
     * @return string The HTML code with the control-icons
     */
    protected function renderForeignRecordHeaderControl(array $data)
    {
        $rec = $data['databaseRow'];
        $inlineConfig = $data['inlineParentConfig'];
        $foreignTable = $inlineConfig['foreign_table'];
        $languageService = $this->getLanguageService();
        $backendUser = $this->getBackendUserAuthentication();
        // Initialize:
        $cells = array();
        $additionalCells = array();
        $isNewItem = substr($rec['uid'], 0, 3) == 'NEW';
        $isParentExisting = MathUtility::canBeInterpretedAsInteger($data['inlineParentUid']);
        $tcaTableCtrl =& $GLOBALS['TCA'][$foreignTable]['ctrl'];
        $tcaTableCols =& $GLOBALS['TCA'][$foreignTable]['columns'];
        $isPagesTable = $foreignTable === 'pages';
        $isSysFileReferenceTable = $foreignTable === 'sys_file_reference';
        $enableManualSorting = $tcaTableCtrl['sortby'] || $inlineConfig['MM'] || !$data['isOnSymmetricSide'] && $inlineConfig['foreign_sortby'] || $data['isOnSymmetricSide'] && $inlineConfig['symmetric_sortby'];
        $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($data['inlineFirstPid']);
        $nameObjectFt = $nameObject . '-' . $foreignTable;
        $nameObjectFtId = $nameObjectFt . '-' . $rec['uid'];
        $calcPerms = $backendUser->calcPerms(BackendUtility::readPageAccess($rec['pid'], $backendUser->getPagePermsClause(1)));
        // If the listed table is 'pages' we have to request the permission settings for each page:
        $localCalcPerms = false;
        if ($isPagesTable) {
            $localCalcPerms = $backendUser->calcPerms(BackendUtility::getRecord('pages', $rec['uid']));
        }
        // This expresses the edit permissions for this particular element:
        $permsEdit = $isPagesTable && $localCalcPerms & Permission::PAGE_EDIT || !$isPagesTable && $calcPerms & Permission::CONTENT_EDIT;
        // Controls: Defines which controls should be shown
        $enabledControls = $inlineConfig['appearance']['enabledControls'];
        // Hook: Can disable/enable single controls for specific child records:
        foreach ($this->hookObjects as $hookObj) {
            /** @var InlineElementHookInterface $hookObj */
            $hookObj->renderForeignRecordHeaderControl_preProcess($data['inlineParentUid'], $foreignTable, $rec, $inlineConfig, $data['isInlineDefaultLanguageRecordInLocalizedParentContext'], $enabledControls);
        }
        if ($data['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
            $cells['localize.isLocalizable'] = '<span title="' . $languageService->sL('LLL:EXT:lang/locallang_misc.xlf:localize.isLocalizable', true) . '">' . $this->iconFactory->getIcon('actions-edit-localize-status-low', Icon::SIZE_SMALL)->render() . '</span>';
        }
        // "Info": (All records)
        if ($enabledControls['info'] && !$isNewItem) {
            if ($rec['table_local'] === 'sys_file') {
                $uid = (int) substr($rec['uid_local'], 9);
                $table = '_FILE';
            } else {
                $uid = $rec['uid'];
                $table = $foreignTable;
            }
            $cells['info'] = '
				<a class="btn btn-default" href="#" onclick="' . htmlspecialchars('top.launchView(' . GeneralUtility::quoteJSvalue($table) . ', ' . GeneralUtility::quoteJSvalue($uid) . '); return false;') . '" title="' . $languageService->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:showInfo', true) . '">
					' . $this->iconFactory->getIcon('actions-document-info', Icon::SIZE_SMALL)->render() . '
				</a>';
        }
        // If the table is NOT a read-only table, then show these links:
        if (!$tcaTableCtrl['readOnly'] && !$data['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
            // "New record after" link (ONLY if the records in the table are sorted by a "sortby"-row or if default values can depend on previous record):
            if ($enabledControls['new'] && ($enableManualSorting || $tcaTableCtrl['useColumnsForDefaultValues'])) {
                if (!$isPagesTable && $calcPerms & Permission::CONTENT_EDIT || $isPagesTable && $calcPerms & Permission::PAGE_NEW) {
                    $onClick = 'return inline.createNewRecord(' . GeneralUtility::quoteJSvalue($nameObjectFt) . ',' . GeneralUtility::quoteJSvalue($rec['uid']) . ')';
                    $style = '';
                    if ($inlineConfig['inline']['inlineNewButtonStyle']) {
                        $style = ' style="' . $inlineConfig['inline']['inlineNewButtonStyle'] . '"';
                    }
                    $cells['new'] = '
						<a class="btn btn-default inlineNewButton ' . $this->inlineData['config'][$nameObject]['md5'] . '" href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . $languageService->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:new' . ($isPagesTable ? 'Page' : 'Record'), true) . '" ' . $style . '>
							' . $this->iconFactory->getIcon('actions-' . ($isPagesTable ? 'page' : 'document') . '-new', Icon::SIZE_SMALL)->render() . '
						</a>';
                }
            }
            // "Up/Down" links
            if ($enabledControls['sort'] && $permsEdit && $enableManualSorting) {
                // Up
                $onClick = 'return inline.changeSorting(' . GeneralUtility::quoteJSvalue($nameObjectFtId) . ', \'1\')';
                $style = $inlineConfig['inline']['first'] == $rec['uid'] ? 'style="visibility: hidden;"' : '';
                $cells['sort.up'] = '
					<a class="btn btn-default sortingUp" href="#" onclick="' . htmlspecialchars($onClick) . '" ' . $style . ' title="' . $languageService->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:moveUp', true) . '">
						' . $this->iconFactory->getIcon('actions-move-up', Icon::SIZE_SMALL)->render() . '
					</a>';
                // Down
                $onClick = 'return inline.changeSorting(' . GeneralUtility::quoteJSvalue($nameObjectFtId) . ', \'-1\')';
                $style = $inlineConfig['inline']['last'] == $rec['uid'] ? 'style="visibility: hidden;"' : '';
                $cells['sort.down'] = '
					<a class="btn btn-default sortingDown" href="#" onclick="' . htmlspecialchars($onClick) . '" ' . $style . ' title="' . $languageService->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:moveDown', true) . '">
						' . $this->iconFactory->getIcon('actions-move-down', Icon::SIZE_SMALL)->render() . '
					</a>';
            }
            // "Edit" link:
            if ($rec['table_local'] === 'sys_file' && !$isNewItem) {
                $sys_language_uid = 0;
                if (!empty($rec['sys_language_uid'])) {
                    $sys_language_uid = $rec['sys_language_uid'][0];
                }
                $recordInDatabase = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('uid', 'sys_file_metadata', 'file = ' . (int) substr($rec['uid_local'], 9) . ' AND sys_language_uid = ' . $sys_language_uid);
                if ($backendUser->check('tables_modify', 'sys_file_metadata')) {
                    $url = BackendUtility::getModuleUrl('record_edit', array('edit[sys_file_metadata][' . (int) $recordInDatabase['uid'] . ']' => 'edit'));
                    $editOnClick = 'if (top.content.list_frame) {' . 'top.content.list_frame.location.href=' . GeneralUtility::quoteJSvalue($url . '&returnUrl=') . '+top.rawurlencode(top.content.list_frame.document.location.pathname+top.content.list_frame.document.location.search)' . ';' . '}';
                    $title = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:cm.editMetadata');
                    $cells['editmetadata'] = '
						<a class="btn btn-default" href="#" class="btn" onclick="' . htmlspecialchars($editOnClick) . '" title="' . htmlspecialchars($title) . '">
							' . $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() . '
						</a>';
                }
            }
            // "Delete" link:
            if ($enabledControls['delete'] && ($isPagesTable && $localCalcPerms & Permission::PAGE_DELETE || !$isPagesTable && $calcPerms & Permission::CONTENT_EDIT || $isSysFileReferenceTable && $calcPerms & Permission::PAGE_EDIT)) {
                $title = $languageService->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:delete', true);
                $icon = $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render();
                $cells['delete'] = '<a href="#" class="btn btn-default t3js-editform-delete-inline-record" data-objectid="' . htmlspecialchars($nameObjectFtId) . '" title="' . $title . '">' . $icon . '</a>';
            }
            // "Hide/Unhide" links:
            $hiddenField = $tcaTableCtrl['enablecolumns']['disabled'];
            if ($enabledControls['hide'] && $permsEdit && $hiddenField && $tcaTableCols[$hiddenField] && (!$tcaTableCols[$hiddenField]['exclude'] || $backendUser->check('non_exclude_fields', $foreignTable . ':' . $hiddenField))) {
                $onClick = 'return inline.enableDisableRecord(' . GeneralUtility::quoteJSvalue($nameObjectFtId) . ')';
                $className = 't3js-' . $nameObjectFtId . '_disabled';
                if ($rec[$hiddenField]) {
                    $title = $languageService->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:unHide' . ($isPagesTable ? 'Page' : ''), true);
                    $cells['hide.unhide'] = '
						<a class="btn btn-default hiddenHandle ' . $className . '" href="#" onclick="' . htmlspecialchars($onClick) . '"' . 'title="' . $title . '">' . $this->iconFactory->getIcon('actions-edit-unhide', Icon::SIZE_SMALL)->render() . '
						</a>';
                } else {
                    $title = $languageService->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:hide' . ($isPagesTable ? 'Page' : ''), true);
                    $cells['hide.hide'] = '
						<a class="btn btn-default hiddenHandle ' . $className . '" href="#" onclick="' . htmlspecialchars($onClick) . '"' . 'title="' . $title . '">' . $this->iconFactory->getIcon('actions-edit-hide', Icon::SIZE_SMALL)->render() . '
						</a>';
                }
            }
            // Drag&Drop Sorting: Sortable handler for script.aculo.us
            if ($enabledControls['dragdrop'] && $permsEdit && $enableManualSorting && $inlineConfig['appearance']['useSortable']) {
                $additionalCells['dragdrop'] = '
					<span class="btn btn-default sortableHandle" data-id="' . htmlspecialchars($rec['uid']) . '" title="' . $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.move', true) . '">
						' . $this->iconFactory->getIcon('actions-move-move', Icon::SIZE_SMALL)->render() . '
					</span>';
            }
        } elseif ($data['isInlineDefaultLanguageRecordInLocalizedParentContext'] && $isParentExisting) {
            if ($enabledControls['localize'] && $data['isInlineDefaultLanguageRecordInLocalizedParentContext']) {
                $onClick = 'inline.synchronizeLocalizeRecords(' . GeneralUtility::quoteJSvalue($nameObjectFt) . ', ' . GeneralUtility::quoteJSvalue($rec['uid']) . ');';
                $cells['localize'] = '
					<a class="btn btn-default" href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . $languageService->sL('LLL:EXT:lang/locallang_misc.xlf:localize', true) . '">
						' . $this->iconFactory->getIcon('actions-document-localize', Icon::SIZE_SMALL)->render() . '
					</a>';
            }
        }
        // If the record is edit-locked by another user, we will show a little warning sign:
        if ($lockInfo = BackendUtility::isRecordLocked($foreignTable, $rec['uid'])) {
            $cells['locked'] = '
				<a class="btn btn-default" href="#" onclick="alert(' . GeneralUtility::quoteJSvalue($lockInfo['msg']) . ');return false;">
					' . '<span title="' . htmlspecialchars($lockInfo['msg']) . '">' . $this->iconFactory->getIcon('status-warning-in-use', Icon::SIZE_SMALL)->render() . '</span>' . '
				</a>';
        }
        // Hook: Post-processing of single controls for specific child records:
        foreach ($this->hookObjects as $hookObj) {
            $hookObj->renderForeignRecordHeaderControl_postProcess($data['inlineParentUid'], $foreignTable, $rec, $inlineConfig, $data['isInlineDefaultLanguageRecordInLocalizedParentContext'], $cells);
        }
        $out = '';
        if (!empty($cells)) {
            $out .= ' <div class="btn-group btn-group-sm" role="group">' . implode('', $cells) . '</div>';
        }
        if (!empty($additionalCells)) {
            $out .= ' <div class="btn-group btn-group-sm" role="group">' . implode('', $additionalCells) . '</div>';
        }
        return $out;
    }
Example #5
0
 /**
  * Determines and sets several script calls to a JSON array, that would have been executed if processed in non-AJAX mode.
  *
  * @param array &$jsonArray Reference of the array to be used for JSON
  * @param array $config The configuration of the IRRE field of the parent record
  * @param int $inlineFirstPid Inline first pid
  * @return array Modified array
  * @todo: Basically, this methods shouldn't be there at all ...
  */
 protected function getInlineAjaxCommonScriptCalls($jsonArray, $config, $inlineFirstPid)
 {
     // Add data that would have been added at the top of a regular FormEngine call:
     if ($headTags = $this->getInlineHeadTags()) {
         $jsonArray['headData'] = $headTags;
     }
     // Add the JavaScript data that would have been added at the bottom of a regular FormEngine call:
     $jsonArray['scriptCall'][] = $this->JSbottom('editform', TRUE);
     // If script.aculo.us Sortable is used, update the Observer to know the record:
     if ($config['appearance']['useSortable']) {
         $inlineObjectName = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
         $jsonArray['scriptCall'][] = 'inline.createDragAndDropSorting(' . GeneralUtility::quoteJSvalue($inlineObjectName . '_records') . ');';
     }
     // If FormEngine has some JavaScript code to be executed, just do it
     // @todo: this is done by JSBottom() already?!
     if ($this->extJSCODE) {
         $jsonArray['scriptCall'][] = $this->extJSCODE;
     }
     // require js handling
     foreach ($this->requireJsModules as $moduleName => $callbacks) {
         if (!is_array($callbacks)) {
             $callbacks = array($callbacks);
         }
         foreach ($callbacks as $callback) {
             $inlineCodeKey = $moduleName;
             $javaScriptCode = 'require(["' . $moduleName . '"]';
             if ($callback !== NULL) {
                 $inlineCodeKey .= sha1($callback);
                 $javaScriptCode .= ', ' . $callback;
             }
             $javaScriptCode .= ');';
             $jsonArray['scriptCall'][] = '/*RequireJS-Module-' . $inlineCodeKey . '*/' . LF . $javaScriptCode;
         }
     }
     return $jsonArray;
 }