[TASK] Replace sprite icon "actions-document-new" with the new IconFactory
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / Container / InlineControlContainer.php
1 <?php
2 namespace TYPO3\CMS\Backend\Form\Container;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Backend\Utility\BackendUtility;
18 use TYPO3\CMS\Core\Imaging\Icon;
19 use TYPO3\CMS\Core\Imaging\IconFactory;
20 use TYPO3\CMS\Core\Resource\Folder;
21 use TYPO3\CMS\Core\Utility\MathUtility;
22 use TYPO3\CMS\Core\Utility\GeneralUtility;
23 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
24 use TYPO3\CMS\Backend\Form\DataPreprocessor;
25 use TYPO3\CMS\Lang\LanguageService;
26 use TYPO3\CMS\Core\Utility\ArrayUtility;
27 use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
28 use TYPO3\CMS\Backend\Utility\IconUtility;
29 use TYPO3\CMS\Backend\Form\InlineStackProcessor;
30 use TYPO3\CMS\Backend\Form\InlineRelatedRecordResolver;
31 use TYPO3\CMS\Backend\Form\Exception\AccessDeniedException;
32 use TYPO3\CMS\Backend\Form\NodeFactory;
33
34 /**
35 * Inline element entry container.
36 *
37 * This container is the entry step to rendering an inline element. It is created by SingleFieldContainer.
38 *
39 * The code creates the main structure for the single inline elements, initializes
40 * the inlineData array, that is manipulated and also returned back in its manipulated state.
41 * The "control" stuff of inline elements is rendered here, for example the "create new" button.
42 *
43 * For each existing inline relation an InlineRecordContainer is called for further processing.
44 */
45 class InlineControlContainer extends AbstractContainer {
46
47 /**
48 * Inline data array used in JS, returned as JSON object to frontend
49 *
50 * @var array
51 */
52 protected $inlineData = array();
53
54 /**
55 * @var InlineStackProcessor
56 */
57 protected $inlineStackProcessor;
58
59 /**
60 * @var IconFactory
61 */
62 protected $iconFactory;
63
64 /**
65 * Construct to initialize class variables.
66 */
67 public function __construct() {
68 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
69 }
70
71 /**
72 * Entry method
73 *
74 * @return array As defined in initializeResultArray() of AbstractNode
75 */
76 public function render() {
77 $languageService = $this->getLanguageService();
78
79 $this->inlineData = $this->globalOptions['inlineData'];
80
81 /** @var InlineStackProcessor $inlineStackProcessor */
82 $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
83 $this->inlineStackProcessor = $inlineStackProcessor;
84 $inlineStackProcessor->initializeByGivenStructure($this->globalOptions['inlineStructure']);
85
86 $table = $this->globalOptions['table'];
87 $row = $this->globalOptions['databaseRow'];
88 $field = $this->globalOptions['fieldName'];
89 $parameterArray = $this->globalOptions['parameterArray'];
90
91 $resultArray = $this->initializeResultArray();
92 $html = '';
93
94 // An inline field must have a foreign_table, if not, stop all further inline actions for this field
95 if (
96 !$parameterArray['fieldConf']['config']['foreign_table']
97 || !is_array($GLOBALS['TCA'][$parameterArray['fieldConf']['config']['foreign_table']])
98 ) {
99 return $resultArray;
100 }
101
102 $config = FormEngineUtility::mergeInlineConfiguration($parameterArray['fieldConf']['config']);
103 $foreign_table = $config['foreign_table'];
104
105 $language = 0;
106 if (BackendUtility::isTableLocalizable($table)) {
107 $language = (int)$row[$GLOBALS['TCA'][$table]['ctrl']['languageField']];
108 }
109 $minItems = MathUtility::forceIntegerInRange($config['minitems'], 0);
110 $maxItems = MathUtility::forceIntegerInRange($config['maxitems'], 0);
111 if (!$maxItems) {
112 $maxItems = 100000;
113 }
114
115 // Add the current inline job to the structure stack
116 $newStructureItem = array(
117 'table' => $table,
118 'uid' => $row['uid'],
119 'field' => $field,
120 'config' => $config,
121 'localizationMode' => BackendUtility::getInlineLocalizationMode($table, $config),
122 );
123 // Extract FlexForm parts (if any) from element name, e.g. array('vDEF', 'lDEF', 'FlexField', 'vDEF')
124 if (!empty($parameterArray['itemFormElName'])) {
125 $flexFormParts = FormEngineUtility::extractFlexFormParts($parameterArray['itemFormElName']);
126 if ($flexFormParts !== NULL) {
127 $newStructureItem['flexform'] = $flexFormParts;
128 }
129 }
130 $inlineStackProcessor->pushStableStructureItem($newStructureItem);
131
132 // e.g. data[<table>][<uid>][<field>]
133 $nameForm = $inlineStackProcessor->getCurrentStructureFormPrefix();
134 // e.g. data-<pid>-<table1>-<uid1>-<field1>-<table2>-<uid2>-<field2>
135 $nameObject = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid']);
136
137 // Get the records related to this inline record
138 $inlineRelatedRecordResolver = GeneralUtility::makeInstance(InlineRelatedRecordResolver::class);
139 $relatedRecords = $inlineRelatedRecordResolver->getRelatedRecords($table, $field, $row, $parameterArray, $config, $this->globalOptions['inlineFirstPid']);
140
141 // Set the first and last record to the config array
142 $relatedRecordsUids = array_keys($relatedRecords['records']);
143 $config['inline']['first'] = reset($relatedRecordsUids);
144 $config['inline']['last'] = end($relatedRecordsUids);
145
146 $top = $inlineStackProcessor->getStructureLevel(0);
147
148 $this->inlineData['config'][$nameObject] = array(
149 'table' => $foreign_table,
150 'md5' => md5($nameObject)
151 );
152 $this->inlineData['config'][$nameObject . '-' . $foreign_table] = array(
153 'min' => $minItems,
154 'max' => $maxItems,
155 'sortable' => $config['appearance']['useSortable'],
156 'top' => array(
157 'table' => $top['table'],
158 'uid' => $top['uid']
159 ),
160 'context' => array(
161 'config' => $config,
162 'hmac' => GeneralUtility::hmac(serialize($config)),
163 ),
164 );
165 $this->inlineData['nested'][$nameObject] = $this->globalOptions['tabAndInlineStack'];
166
167 // If relations are required to be unique, get the uids that have already been used on the foreign side of the relation
168 if ($config['foreign_unique']) {
169 // If uniqueness *and* selector are set, they should point to the same field - so, get the configuration of one:
170 $selConfig = FormEngineUtility::getInlinePossibleRecordsSelectorConfig($config, $config['foreign_unique']);
171 // Get the used unique ids:
172 $uniqueIds = $this->getUniqueIds($relatedRecords['records'], $config, $selConfig['type'] == 'groupdb');
173 $possibleRecords = $this->getPossibleRecords($table, $field, $row, $config, 'foreign_unique');
174 $uniqueMax = $config['appearance']['useCombination'] || $possibleRecords === FALSE ? -1 : count($possibleRecords);
175 $this->inlineData['unique'][$nameObject . '-' . $foreign_table] = array(
176 'max' => $uniqueMax,
177 'used' => $uniqueIds,
178 'type' => $selConfig['type'],
179 'table' => $config['foreign_table'],
180 'elTable' => $selConfig['table'],
181 // element/record table (one step down in hierarchy)
182 'field' => $config['foreign_unique'],
183 'selector' => $selConfig['selector'],
184 'possible' => $this->getPossibleRecordsFlat($possibleRecords)
185 );
186 }
187
188 $resultArray['inlineData'] = $this->inlineData;
189
190 // Render the localization links
191 $localizationLinks = '';
192 if ($language > 0 && $row[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] > 0 && MathUtility::canBeInterpretedAsInteger($row['uid'])) {
193 // Add the "Localize all records" link before all child records:
194 if (isset($config['appearance']['showAllLocalizationLink']) && $config['appearance']['showAllLocalizationLink']) {
195 $localizationLinks .= ' ' . $this->getLevelInteractionLink('localize', $nameObject . '-' . $foreign_table, $config);
196 }
197 // Add the "Synchronize with default language" link before all child records:
198 if (isset($config['appearance']['showSynchronizationLink']) && $config['appearance']['showSynchronizationLink']) {
199 $localizationLinks .= ' ' . $this->getLevelInteractionLink('synchronize', $nameObject . '-' . $foreign_table, $config);
200 }
201 }
202
203 // Define how to show the "Create new record" link - if there are more than maxitems, hide it
204 if ($relatedRecords['count'] >= $maxItems || $uniqueMax > 0 && $relatedRecords['count'] >= $uniqueMax) {
205 $config['inline']['inlineNewButtonStyle'] = 'display: none;';
206 $config['inline']['inlineNewRelationButtonStyle'] = 'display: none;';
207 }
208
209 // Render the level links (create new record):
210 $levelLinks = $this->getLevelInteractionLink('newRecord', $nameObject . '-' . $foreign_table, $config);
211
212 // Wrap all inline fields of a record with a <div> (like a container)
213 $html .= '<div class="form-group" id="' . $nameObject . '">';
214 // Add the level links before all child records:
215 if ($config['appearance']['levelLinksPosition'] === 'both' || $config['appearance']['levelLinksPosition'] === 'top') {
216 $html .= '<div class="form-group t3js-formengine-validation-marker">' . $levelLinks . $localizationLinks . '</div>';
217 }
218 // If it's required to select from possible child records (reusable children), add a selector box
219 if ($config['foreign_selector'] && $config['appearance']['showPossibleRecordsSelector'] !== FALSE) {
220 // If not already set by the foreign_unique, set the possibleRecords here and the uniqueIds to an empty array
221 if (!$config['foreign_unique']) {
222 $possibleRecords = $this->getPossibleRecords($table, $field, $row, $config);
223 $uniqueIds = array();
224 }
225 $selectorBox = $this->renderPossibleRecordsSelector($possibleRecords, $config, $uniqueIds);
226 $html .= $selectorBox . $localizationLinks;
227 }
228 $title = $languageService->sL($parameterArray['fieldConf']['label']);
229 $html .= '<div class="panel-group panel-hover" data-title="' . htmlspecialchars($title) . '" id="' . $nameObject . '_records">';
230
231 $relationList = array();
232 if (!empty($relatedRecords['records'])) {
233 foreach ($relatedRecords['records'] as $rec) {
234 $options = $this->globalOptions;
235 $options['inlineRelatedRecordToRender'] = $rec;
236 $options['inlineRelatedRecordConfig'] = $config;
237 $options['inlineData'] = $this->inlineData;
238 $options['inlineStructure'] = $inlineStackProcessor->getStructure();
239 $options['renderType'] = 'inlineRecordContainer';
240 /** @var NodeFactory $nodeFactory */
241 $nodeFactory = $this->globalOptions['nodeFactory'];
242 try {
243 // This container may raise an access denied exception, to not kill further processing,
244 // just a simple "empty" return is created here to ignore this field.
245 $childArray = $nodeFactory->create($options)->render();
246 } catch (AccessDeniedException $e) {
247 $childArray = $this->initializeResultArray();
248 }
249 $html .= $childArray['html'];
250 $childArray['html'] = '';
251 $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $childArray);
252 if (!isset($rec['__virtual']) || !$rec['__virtual']) {
253 $relationList[] = $rec['uid'];
254 }
255 }
256 }
257 $html .= '</div>';
258 // Add the level links after all child records:
259 if ($config['appearance']['levelLinksPosition'] === 'both' || $config['appearance']['levelLinksPosition'] === 'bottom') {
260 $html .= $levelLinks . $localizationLinks;
261 }
262 if (is_array($config['customControls'])) {
263 $html .= '<div id="' . $nameObject . '_customControls">';
264 foreach ($config['customControls'] as $customControlConfig) {
265 $parameters = array(
266 'table' => $table,
267 'field' => $field,
268 'row' => $row,
269 'nameObject' => $nameObject,
270 'nameForm' => $nameForm,
271 'config' => $config
272 );
273 $html .= GeneralUtility::callUserFunction($customControlConfig, $parameters, $this);
274 }
275 $html .= '</div>';
276 }
277 // Add Drag&Drop functions for sorting to FormEngine::$additionalJS_post
278 if (count($relationList) > 1 && $config['appearance']['useSortable']) {
279 $resultArray['additionalJavaScriptPost'][] = 'inline.createDragAndDropSorting("' . $nameObject . '_records' . '");';
280 }
281 // Publish the uids of the child records in the given order to the browser
282 $html .= '<input type="hidden" name="' . $nameForm . '" value="' . implode(',', $relationList) . '" ' . $this->getValidationDataAsDataAttribute(array('type' => 'inline', 'minitems' => $minItems, 'maxitems' => $maxItems)) . ' class="inlineRecord" />';
283 // Close the wrap for all inline fields (container)
284 $html .= '</div>';
285
286 $resultArray['html'] = $html;
287 return $resultArray;
288 }
289
290 /**
291 * Gets the uids of a select/selector that should be unique and have already been used.
292 *
293 * @param array $records All inline records on this level
294 * @param array $conf The TCA field configuration of the inline field to be rendered
295 * @param bool $splitValue For usage with group/db, values come like "tx_table_123|Title%20abc", but we need "tx_table" and "123
296 * @return array The uids, that have been used already and should be used unique
297 */
298 protected function getUniqueIds($records, $conf = array(), $splitValue = FALSE) {
299 $uniqueIds = array();
300 if (isset($conf['foreign_unique']) && $conf['foreign_unique'] && !empty($records)) {
301 foreach ($records as $rec) {
302 // Skip virtual records (e.g. shown in localization mode):
303 if (!isset($rec['__virtual']) || !$rec['__virtual']) {
304 $value = $rec[$conf['foreign_unique']];
305 // Split the value and extract the table and uid:
306 if ($splitValue) {
307 $valueParts = GeneralUtility::trimExplode('|', $value);
308 $itemParts = explode('_', $valueParts[0]);
309 $value = array(
310 'uid' => array_pop($itemParts),
311 'table' => implode('_', $itemParts)
312 );
313 }
314 $uniqueIds[$rec['uid']] = $value;
315 }
316 }
317 }
318 return $uniqueIds;
319 }
320
321 /**
322 * Get possible records.
323 * Copied from FormEngine and modified.
324 *
325 * @param string $table The table name of the record
326 * @param string $field The field name which this element is supposed to edit
327 * @param array $row The record data array where the value(s) for the field can be found
328 * @param array $conf An array with additional configuration options.
329 * @param string $checkForConfField For which field in the foreign_table the possible records should be fetched
330 * @return mixed Array of possible record items; FALSE if type is "group/db", then everything could be "possible
331 */
332 protected function getPossibleRecords($table, $field, $row, $conf, $checkForConfField = 'foreign_selector') {
333 $backendUser = $this->getBackendUserAuthentication();
334 $languageService = $this->getLanguageService();
335
336 // ctrl configuration from TCA:
337 $tcaTableCtrl = $GLOBALS['TCA'][$table]['ctrl'];
338 // Field configuration from TCA:
339 $foreign_check = $conf[$checkForConfField];
340 $foreignConfig = FormEngineUtility::getInlinePossibleRecordsSelectorConfig($conf, $foreign_check);
341 $PA = $foreignConfig['PA'];
342 $config = $PA['fieldConf']['config'];
343 if ($foreignConfig['type'] == 'select') {
344 // Getting the selector box items from the system
345 $selItems = FormEngineUtility::addSelectOptionsToItemArray(
346 FormEngineUtility::initItemArray($PA['fieldConf']),
347 $PA['fieldConf'],
348 FormEngineUtility::getTSconfigForTableRow($table, $row),
349 $field
350 );
351
352 // Possibly filter some items:
353 $selItems = ArrayUtility::keepItemsInArray(
354 $selItems,
355 $PA['fieldTSConfig']['keepItems'],
356 function ($value) {
357 return $value[1];
358 }
359 );
360
361 // Possibly add some items:
362 $selItems = FormEngineUtility::addItems($selItems, $PA['fieldTSConfig']['addItems.']);
363 if (isset($config['itemsProcFunc']) && $config['itemsProcFunc']) {
364 $dataPreprocessor = GeneralUtility::makeInstance(DataPreprocessor::class);
365 $selItems = $dataPreprocessor->procItems($selItems, $PA['fieldTSConfig']['itemsProcFunc.'], $config, $table, $row, $field);
366 }
367 // Possibly remove some items:
368 $removeItems = GeneralUtility::trimExplode(',', $PA['fieldTSConfig']['removeItems'], TRUE);
369 foreach ($selItems as $tk => $p) {
370 // Checking languages and authMode:
371 $languageDeny = $tcaTableCtrl['languageField'] && (string)$tcaTableCtrl['languageField'] === $field && !$backendUser->checkLanguageAccess($p[1]);
372 $authModeDeny = $config['type'] == 'select' && $config['authMode'] && !$backendUser->checkAuthMode($table, $field, $p[1], $config['authMode']);
373 if (in_array($p[1], $removeItems) || $languageDeny || $authModeDeny) {
374 unset($selItems[$tk]);
375 } else {
376 if (isset($PA['fieldTSConfig']['altLabels.'][$p[1]])) {
377 $selItems[$tk][0] = htmlspecialchars($languageService->sL($PA['fieldTSConfig']['altLabels.'][$p[1]]));
378 }
379 if (isset($PA['fieldTSConfig']['altIcons.'][$p[1]])) {
380 $selItems[$tk][2] = $PA['fieldTSConfig']['altIcons.'][$p[1]];
381 }
382 }
383 // Removing doktypes with no access:
384 if (($table === 'pages' || $table === 'pages_language_overlay') && $field === 'doktype') {
385 if (!($backendUser->isAdmin() || GeneralUtility::inList($backendUser->groupData['pagetypes_select'], $p[1]))) {
386 unset($selItems[$tk]);
387 }
388 }
389 }
390 } else {
391 $selItems = FALSE;
392 }
393 return $selItems;
394 }
395
396 /**
397 * Makes a flat array from the $possibleRecords array.
398 * The key of the flat array is the value of the record,
399 * the value of the flat array is the label of the record.
400 *
401 * @param array $possibleRecords The possibleRecords array (for select fields)
402 * @return mixed A flat array with key=uid, value=label; if $possibleRecords isn't an array, FALSE is returned.
403 */
404 protected function getPossibleRecordsFlat($possibleRecords) {
405 $flat = FALSE;
406 if (is_array($possibleRecords)) {
407 $flat = array();
408 foreach ($possibleRecords as $record) {
409 $flat[$record[1]] = $record[0];
410 }
411 }
412 return $flat;
413 }
414
415 /**
416 * Creates the HTML code of a general link to be used on a level of inline children.
417 * The possible keys for the parameter $type are 'newRecord', 'localize' and 'synchronize'.
418 *
419 * @param string $type The link type, values are 'newRecord', 'localize' and 'synchronize'.
420 * @param string $objectPrefix The "path" to the child record to create (e.g. 'data-parentPageId-partenTable-parentUid-parentField-childTable]')
421 * @param array $conf TCA configuration of the parent(!) field
422 * @return string The HTML code of the new link, wrapped in a div
423 */
424 protected function getLevelInteractionLink($type, $objectPrefix, $conf = array()) {
425 $languageService = $this->getLanguageService();
426 $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid']);
427 $attributes = array();
428 switch ($type) {
429 case 'newRecord':
430 $title = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:cm.createnew', TRUE);
431 $icon = 'actions-document-new';
432 $className = 'typo3-newRecordLink';
433 $attributes['class'] = 'btn btn-default inlineNewButton ' . $this->inlineData['config'][$nameObject]['md5'];
434 $attributes['onclick'] = 'return inline.createNewRecord(' . GeneralUtility::quoteJSvalue($objectPrefix) . ')';
435 if (!empty($conf['inline']['inlineNewButtonStyle'])) {
436 $attributes['style'] = $conf['inline']['inlineNewButtonStyle'];
437 }
438 if (!empty($conf['appearance']['newRecordLinkAddTitle'])) {
439 $title = sprintf(
440 $languageService->sL('LLL:EXT:lang/locallang_core.xlf:cm.createnew.link', TRUE),
441 $languageService->sL($GLOBALS['TCA'][$conf['foreign_table']]['ctrl']['title'], TRUE)
442 );
443 } elseif (isset($conf['appearance']['newRecordLinkTitle']) && $conf['appearance']['newRecordLinkTitle'] !== '') {
444 $title = $languageService->sL($conf['appearance']['newRecordLinkTitle'], TRUE);
445 }
446 break;
447 case 'localize':
448 $title = $languageService->sL('LLL:EXT:lang/locallang_misc.xlf:localizeAllRecords', 1);
449 $icon = 'actions-document-localize';
450 $className = 'typo3-localizationLink';
451 $attributes['class'] = 'btn btn-default';
452 $attributes['onclick'] = 'return inline.synchronizeLocalizeRecords(' . GeneralUtility::quoteJSvalue($objectPrefix) . ', \'localize\')';
453 break;
454 case 'synchronize':
455 $title = $languageService->sL('LLL:EXT:lang/locallang_misc.xlf:synchronizeWithOriginalLanguage', TRUE);
456 $icon = 'actions-document-synchronize';
457 $className = 'typo3-synchronizationLink';
458 $attributes['class'] = 'btn btn-default inlineNewButton ' . $this->inlineData['config'][$nameObject]['md5'];
459 $attributes['onclick'] = 'return inline.synchronizeLocalizeRecords(' . GeneralUtility::quoteJSvalue($objectPrefix) . ', \'synchronize\')';
460 break;
461 default:
462 $title = '';
463 $icon = '';
464 $className = '';
465 }
466 // Create the link:
467 $icon = $icon ? IconUtility::getSpriteIcon($icon, array('title' => htmlspecialchars($title))) : '';
468 $link = $this->wrapWithAnchor($icon . $title, '#', $attributes);
469 return '<div' . ($className ? ' class="' . $className . '"' : '') . '>' . $link . '</div>';
470 }
471
472 /**
473 * Wraps a text with an anchor and returns the HTML representation.
474 *
475 * @param string $text The text to be wrapped by an anchor
476 * @param string $link The link to be used in the anchor
477 * @param array $attributes Array of attributes to be used in the anchor
478 * @return string The wrapped text as HTML representation
479 */
480 protected function wrapWithAnchor($text, $link, $attributes = array()) {
481 $link = trim($link);
482 $result = '<a href="' . ($link ?: '#') . '"';
483 foreach ($attributes as $key => $value) {
484 $result .= ' ' . $key . '="' . htmlspecialchars(trim($value)) . '"';
485 }
486 $result .= '>' . $text . '</a>';
487 return $result;
488 }
489
490 /**
491 * Get a selector as used for the select type, to select from all available
492 * records and to create a relation to the embedding record (e.g. like MM).
493 *
494 * @param array $selItems Array of all possible records
495 * @param array $conf TCA configuration of the parent(!) field
496 * @param array $uniqueIds The uids that have already been used and should be unique
497 * @return string A HTML <select> box with all possible records
498 */
499 protected function renderPossibleRecordsSelector($selItems, $conf, $uniqueIds = array()) {
500 $foreign_selector = $conf['foreign_selector'];
501 $selConfig = FormEngineUtility::getInlinePossibleRecordsSelectorConfig($conf, $foreign_selector);
502 $item = '';
503 if ($selConfig['type'] === 'select') {
504 $item = $this->renderPossibleRecordsSelectorTypeSelect($selItems, $conf, $selConfig['PA'], $uniqueIds);
505 } elseif ($selConfig['type'] === 'groupdb') {
506 $item = $this->renderPossibleRecordsSelectorTypeGroupDB($conf, $selConfig['PA']);
507 }
508 return $item;
509 }
510
511 /**
512 * Generate a link that opens an element browser in a new window.
513 * For group/db there is no way to use a "selector" like a <select>|</select>-box.
514 *
515 * @param array $conf TCA configuration of the parent(!) field
516 * @param array $PA An array with additional configuration options
517 * @return string A HTML link that opens an element browser in a new window
518 */
519 protected function renderPossibleRecordsSelectorTypeGroupDB($conf, &$PA) {
520 $backendUser = $this->getBackendUserAuthentication();
521
522 $config = $PA['fieldConf']['config'];
523 ArrayUtility::mergeRecursiveWithOverrule($config, $conf);
524 $foreign_table = $config['foreign_table'];
525 $allowed = $config['allowed'];
526 $objectPrefix = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid']) . '-' . $foreign_table;
527 $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid']);
528 $mode = 'db';
529 $showUpload = FALSE;
530 if (!empty($config['appearance']['createNewRelationLinkTitle'])) {
531 $createNewRelationText = $this->getLanguageService()->sL($config['appearance']['createNewRelationLinkTitle'], TRUE);
532 } else {
533 $createNewRelationText = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.createNewRelation', TRUE);
534 }
535 if (is_array($config['appearance'])) {
536 if (isset($config['appearance']['elementBrowserType'])) {
537 $mode = $config['appearance']['elementBrowserType'];
538 }
539 if ($mode === 'file') {
540 $showUpload = TRUE;
541 }
542 if (isset($config['appearance']['fileUploadAllowed'])) {
543 $showUpload = (bool)$config['appearance']['fileUploadAllowed'];
544 }
545 if (isset($config['appearance']['elementBrowserAllowed'])) {
546 $allowed = $config['appearance']['elementBrowserAllowed'];
547 }
548 }
549 $browserParams = '|||' . $allowed . '|' . $objectPrefix . '|inline.checkUniqueElement||inline.importElement';
550 $onClick = 'setFormValueOpenBrowser(' . GeneralUtility::quoteJSvalue($mode) . ', ' . GeneralUtility::quoteJSvalue($browserParams) . '); return false;';
551
552 $buttonStyle = '';
553 if (isset($config['inline']['inlineNewRelationButtonStyle'])) {
554 $buttonStyle = ' style="' . $config['inline']['inlineNewRelationButtonStyle'] . '"';
555 }
556
557 $item = '
558 <a href="#" class="btn btn-default inlineNewRelationButton ' . $this->inlineData['config'][$nameObject]['md5'] . '"
559 ' . $buttonStyle . ' onclick="' . htmlspecialchars($onClick) . '">
560 ' . IconUtility::getSpriteIcon('actions-insert-record', array('title' => $createNewRelationText)) . '
561 ' . $createNewRelationText . '
562 </a>';
563
564 $isDirectFileUploadEnabled = (bool)$this->getBackendUserAuthentication()->uc['edit_docModuleUpload'];
565 if ($showUpload && $isDirectFileUploadEnabled) {
566 $folder = $backendUser->getDefaultUploadFolder();
567 if (
568 $folder instanceof Folder
569 && $folder->checkActionPermission('add')
570 ) {
571 $maxFileSize = GeneralUtility::getMaxUploadFileSize() * 1024;
572 $item .= ' <a href="#" class="btn btn-default t3js-drag-uploader inlineNewFileUploadButton ' . $this->inlineData['config'][$nameObject]['md5'] . '"
573 ' . $buttonStyle . '
574 data-dropzone-target="#' . htmlspecialchars($this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid'])) . '"
575 data-insert-dropzone-before="1"
576 data-file-irre-object="' . htmlspecialchars($objectPrefix) . '"
577 data-file-allowed="' . htmlspecialchars($allowed) . '"
578 data-target-folder="' . htmlspecialchars($folder->getCombinedIdentifier()) . '"
579 data-max-file-size="' . htmlspecialchars($maxFileSize) . '"
580 ><span class="t3-icon t3-icon-actions t3-icon-actions-edit t3-icon-edit-upload">&nbsp;</span>';
581 $item .= $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:file_upload.select-and-submit', TRUE);
582 $item .= '</a>';
583 }
584 }
585
586 $item = '<div class="form-control-wrap">' . $item . '</div>';
587 $allowedList = '';
588 $allowedArray = GeneralUtility::trimExplode(',', $allowed, TRUE);
589 $allowedLabel = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.allowedFileExtensions', TRUE);
590 foreach ($allowedArray as $allowedItem) {
591 $allowedList .= '<span class="label label-success">' . strtoupper($allowedItem) . '</span> ';
592 }
593 if (!empty($allowedList)) {
594 $item .= '<div class="help-block">' . $allowedLabel . '<br>' . $allowedList . '</div>';
595 }
596 $item = '<div class="form-group t3js-formengine-validation-marker">' . $item . '</div>';
597 return $item;
598 }
599
600 /**
601 * Get a selector as used for the select type, to select from all available
602 * records and to create a relation to the embedding record (e.g. like MM).
603 *
604 * @param array $selItems Array of all possible records
605 * @param array $conf TCA configuration of the parent(!) field
606 * @param array $PA An array with additional configuration options
607 * @param array $uniqueIds The uids that have already been used and should be unique
608 * @return string A HTML <select> box with all possible records
609 */
610 protected function renderPossibleRecordsSelectorTypeSelect($selItems, $conf, &$PA, $uniqueIds = array()) {
611 $foreign_table = $conf['foreign_table'];
612 $foreign_selector = $conf['foreign_selector'];
613 $PA = array();
614 $PA['fieldConf'] = $GLOBALS['TCA'][$foreign_table]['columns'][$foreign_selector];
615 $PA['fieldTSConfig'] = FormEngineUtility::getTSconfigForTableRow($foreign_table, array(), $foreign_selector);
616 $config = $PA['fieldConf']['config'];
617 $item = '';
618 // @todo $disabled is not present - should be read from config?
619 $disabled = FALSE;
620 if (!$disabled) {
621 $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid']);;
622 // Create option tags:
623 $opt = array();
624 $styleAttrValue = '';
625 foreach ($selItems as $p) {
626 if ($config['iconsInOptionTags']) {
627 $styleAttrValue = FormEngineUtility::optionTagStyle($p[2]);
628 }
629 if (!in_array($p[1], $uniqueIds)) {
630 $opt[] = '<option value="' . htmlspecialchars($p[1]) . '"' . ($styleAttrValue ? ' style="' . htmlspecialchars($styleAttrValue) . '"' : '') . '>' . htmlspecialchars($p[0]) . '</option>';
631 }
632 }
633 // Put together the selector box:
634 $itemListStyle = isset($config['itemListStyle']) ? ' style="' . htmlspecialchars($config['itemListStyle']) . '"' : '';
635 $size = (int)$conf['size'];
636 $size = $conf['autoSizeMax'] ? MathUtility::forceIntegerInRange(count($selItems) + 1, MathUtility::forceIntegerInRange($size, 1), $conf['autoSizeMax']) : $size;
637 $onChange = 'return inline.importNewRecord(' . GeneralUtility::quoteJSvalue($nameObject . '-' . $conf['foreign_table']) . ')';
638 $item = '
639 <select id="' . $nameObject . '-' . $conf['foreign_table'] . '_selector" class="form-control"' . ($size ? ' size="' . $size . '"' : '') . ' onchange="' . htmlspecialchars($onChange) . '"' . $PA['onFocus'] . $itemListStyle . ($conf['foreign_unique'] ? ' isunique="isunique"' : '') . '>
640 ' . implode('', $opt) . '
641 </select>';
642
643 if ($size <= 1) {
644 // Add a "Create new relation" link for adding new relations
645 // This is necessary, if the size of the selector is "1" or if
646 // there is only one record item in the select-box, that is selected by default
647 // The selector-box creates a new relation on using an onChange event (see some line above)
648 if (!empty($conf['appearance']['createNewRelationLinkTitle'])) {
649 $createNewRelationText = $this->getLanguageService()->sL($conf['appearance']['createNewRelationLinkTitle'], TRUE);
650 } else {
651 $createNewRelationText = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:cm.createNewRelation', TRUE);
652 }
653 $item .= '
654 <span class="input-group-btn">
655 <a href="#" class="btn btn-default" onclick="' . htmlspecialchars($onChange) . '" . title="' . $createNewRelationText .'">
656 ' . $this->iconFactory->getIcon('actions-document-new', Icon::SIZE_SMALL) . $createNewRelationText . '
657 </a>
658 </span>';
659 } else {
660 $item .= '
661 <span class="input-group-btn btn"></span>';
662 }
663
664 // Wrap the selector and add a spacer to the bottom
665
666 $item = '<div class="input-group form-group t3js-formengine-validation-marker ' . $this->inlineData['config'][$nameObject]['md5'] . '">' . $item . '</div>';
667 }
668 return $item;
669 }
670
671 /**
672 * @return BackendUserAuthentication
673 */
674 protected function getBackendUserAuthentication() {
675 return $GLOBALS['BE_USER'];
676 }
677
678 /**
679 * @return LanguageService
680 */
681 protected function getLanguageService() {
682 return $GLOBALS['LANG'];
683 }
684
685 }