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