[FEATURE] Add AJAX logic for delete records in ListModule
[Packages/TYPO3.CMS.git] / typo3 / sysext / recordlist / Classes / RecordList / DatabaseRecordList.php
1 <?php
2 namespace TYPO3\CMS\Recordlist\RecordList;
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\Core\Database\DatabaseConnection;
18 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20 use TYPO3\CMS\Backend\Utility\BackendUtility;
21 use TYPO3\CMS\Backend\Utility\IconUtility;
22
23 /**
24 * Class for rendering of Web>List module
25 *
26 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
27 */
28 class DatabaseRecordList extends AbstractDatabaseRecordList {
29
30 // *********
31 // External:
32 // *********
33
34 /**
35 * Used to indicate which tables (values in the array) that can have a
36 * create-new-record link. If the array is empty, all tables are allowed.
37 *
38 * @var array
39 */
40 public $allowedNewTables = array();
41
42 /**
43 * Used to indicate which tables (values in the array) that cannot have a
44 * create-new-record link. If the array is empty, all tables are allowed.
45 *
46 * @var array
47 */
48 public $deniedNewTables = array();
49
50 /**
51 * If TRUE, the control panel will contain links to the create-new wizards for
52 * pages and tt_content elements (normally, the link goes to just creatinga new
53 * element without the wizards!).
54 *
55 * @var bool
56 */
57 public $newWizards = FALSE;
58
59 /**
60 * If TRUE, will disable the rendering of clipboard + control panels.
61 *
62 * @var bool
63 */
64 public $dontShowClipControlPanels = FALSE;
65
66 /**
67 * If TRUE, will show the clipboard in the field list.
68 *
69 * @var bool
70 */
71 public $showClipboard = FALSE;
72
73 /**
74 * If TRUE, will DISABLE all control panels in lists. (Takes precedence)
75 *
76 * @var bool
77 */
78 public $noControlPanels = FALSE;
79
80 /**
81 * If TRUE, clickmenus will be rendered
82 *
83 * @var bool
84 */
85 public $clickMenuEnabled = TRUE;
86
87 /**
88 * Count of record rows in view
89 *
90 * @var int
91 */
92 public $totalRowCount;
93
94 /**
95 * Space icon used for alignment
96 *
97 * @var string
98 */
99 public $spaceIcon;
100
101 /**
102 * Disable single table view
103 *
104 * @var bool
105 */
106 public $disableSingleTableView = FALSE;
107
108 // *********
109 // Internal:
110 // *********
111
112 /**
113 * Set to the page record (see writeTop())
114 *
115 * @var array
116 */
117 public $pageRow = array();
118
119 /**
120 * Used to accumulate CSV lines for CSV export.
121 *
122 * @var array
123 */
124 protected $csvLines = array();
125
126 /**
127 * If set, the listing is returned as CSV instead.
128 *
129 * @var bool
130 */
131 public $csvOutput = FALSE;
132
133 /**
134 * Clipboard object
135 *
136 * @var \TYPO3\CMS\Backend\Clipboard\Clipboard
137 */
138 public $clipObj;
139
140 /**
141 * Tracking names of elements (for clipboard use)
142 *
143 * @var array
144 */
145 public $CBnames = array();
146
147 /**
148 * Used to track which elements has duplicates and how many
149 *
150 * @var array
151 */
152 public $duplicateStack = array();
153
154 /**
155 * [$tablename][$uid] = number of references to this record
156 *
157 * @var array
158 */
159 protected $referenceCount = array();
160
161 /**
162 * Translations of the current record
163 *
164 * @var array
165 */
166 public $translations;
167
168 /**
169 * select fields for the query which fetches the translations of the current
170 * record
171 *
172 * @var string
173 */
174 public $selFieldList;
175
176 /**
177 * @var array
178 */
179 public $pageinfo;
180
181 /**
182 * Create the panel of buttons for submitting the form or otherwise perform
183 * operations.
184 *
185 * @return array All available buttons as an assoc. array
186 */
187 public function getButtons() {
188 $buttons = array(
189 'csh' => '',
190 'view' => '',
191 'edit' => '',
192 'hide_unhide' => '',
193 'move' => '',
194 'new_record' => '',
195 'paste' => '',
196 'level_up' => '',
197 'cache' => '',
198 'reload' => '',
199 'shortcut' => '',
200 'back' => '',
201 'csv' => '',
202 'export' => ''
203 );
204 // Get users permissions for this page record:
205 $localCalcPerms = $GLOBALS['BE_USER']->calcPerms($this->pageRow);
206 // CSH
207 if ((string)$this->id === '') {
208 $buttons['csh'] = BackendUtility::cshItem('xMOD_csh_corebe', 'list_module_noId');
209 } elseif (!$this->id) {
210 $buttons['csh'] = BackendUtility::cshItem('xMOD_csh_corebe', 'list_module_root');
211 } else {
212 $buttons['csh'] = BackendUtility::cshItem('xMOD_csh_corebe', 'list_module');
213 }
214 if (isset($this->id)) {
215 // View Exclude doktypes 254,255 Configuration:
216 // mod.web_list.noViewWithDokTypes = 254,255
217 if (isset($GLOBALS['SOBE']->modTSconfig['properties']['noViewWithDokTypes'])) {
218 $noViewDokTypes = GeneralUtility::trimExplode(',', $GLOBALS['SOBE']->modTSconfig['properties']['noViewWithDokTypes'], TRUE);
219 } else {
220 //default exclusion: doktype 254 (folder), 255 (recycler)
221 $noViewDokTypes = array(
222 \TYPO3\CMS\Frontend\Page\PageRepository::DOKTYPE_SYSFOLDER,
223 \TYPO3\CMS\Frontend\Page\PageRepository::DOKTYPE_RECYCLER
224 );
225 }
226 if (!in_array($this->pageRow['doktype'], $noViewDokTypes)) {
227 $onClick = htmlspecialchars(BackendUtility::viewOnClick($this->id, $this->backPath, BackendUtility::BEgetRootLine($this->id)));
228 $buttons['view'] = '<a href="#" onclick="' . $onClick . '" title="'
229 . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.showPage', TRUE) . '">'
230 . IconUtility::getSpriteIcon('actions-document-view') . '</a>';
231 }
232 // New record
233 if (!$GLOBALS['SOBE']->modTSconfig['properties']['noCreateRecordsLink']) {
234 $onClick = htmlspecialchars(('return jumpExt(\'' . $this->backPath . 'db_new.php?id=' . $this->id . '\');'));
235 $buttons['new_record'] = '<a href="#" onclick="' . $onClick . '" title="'
236 . $GLOBALS['LANG']->getLL('newRecordGeneral', TRUE) . '">'
237 . IconUtility::getSpriteIcon('actions-document-new') . '</a>';
238 }
239 // If edit permissions are set, see
240 // \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
241 if ($localCalcPerms & 2 && !empty($this->id)) {
242 // Edit
243 $params = '&edit[pages][' . $this->pageRow['uid'] . ']=edit';
244 $onClick = htmlspecialchars(BackendUtility::editOnClick($params, $this->backPath, -1));
245 $buttons['edit'] = '<a href="#" onclick="' . $onClick . '" title="'
246 . $GLOBALS['LANG']->getLL('editPage', TRUE) . '">'
247 . IconUtility::getSpriteIcon('actions-page-open') . '</a>';
248 }
249 // Paste
250 if ($localCalcPerms & 8 || $localCalcPerms & 16) {
251 $elFromTable = $this->clipObj->elFromTable('');
252 if (count($elFromTable)) {
253 $onClick = htmlspecialchars(('return ' . $this->clipObj->confirmMsg('pages', $this->pageRow, 'into', $elFromTable)));
254 $buttons['paste'] = '<a href="' . htmlspecialchars($this->clipObj->pasteUrl('', $this->id))
255 . '" onclick="' . $onClick . '" title="' . $GLOBALS['LANG']->getLL('clip_paste', TRUE) . '">'
256 . IconUtility::getSpriteIcon('actions-document-paste-after') . '</a>';
257 }
258 }
259 // Cache
260 $buttons['cache'] = '<a href="' . htmlspecialchars(($this->listURL() . '&clear_cache=1')) . '" title="'
261 . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.clear_cache', TRUE) . '">'
262 . IconUtility::getSpriteIcon('actions-system-cache-clear') . '</a>';
263 if (
264 $this->table && (!isset($GLOBALS['SOBE']->modTSconfig['properties']['noExportRecordsLinks'])
265 || (isset($GLOBALS['SOBE']->modTSconfig['properties']['noExportRecordsLinks'])
266 && !$GLOBALS['SOBE']->modTSconfig['properties']['noExportRecordsLinks']))
267 ) {
268 // CSV
269 $buttons['csv'] = '<a href="' . htmlspecialchars(($this->listURL() . '&csv=1')) . '" title="'
270 . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.csv', TRUE) . '">'
271 . IconUtility::getSpriteIcon('mimetypes-text-csv') . '</a>';
272 // Export
273 if (ExtensionManagementUtility::isLoaded('impexp')) {
274 $url = BackendUtility::getModuleUrl('xMOD_tximpexp', array('tx_impexp[action]' => 'export'));
275 $buttons['export'] = '<a href="' . htmlspecialchars($url . '&tx_impexp[list][]='
276 . rawurlencode($this->table . ':' . $this->id)) . '" title="'
277 . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:rm.export', TRUE) . '">'
278 . IconUtility::getSpriteIcon('actions-document-export-t3d') . '</a>';
279 }
280 }
281 // Reload
282 $buttons['reload'] = '<a href="' . htmlspecialchars($this->listURL()) . '" title="'
283 . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.reload', TRUE) . '">'
284 . IconUtility::getSpriteIcon('actions-system-refresh') . '</a>';
285 // Shortcut
286 if ($GLOBALS['BE_USER']->mayMakeShortcut()) {
287 $buttons['shortcut'] = $GLOBALS['TBE_TEMPLATE']->makeShortcutIcon(
288 'id, imagemode, pointer, table, search_field, search_levels, showLimit, sortField, sortRev',
289 implode(',', array_keys($this->MOD_MENU)),
290 'web_list'
291 );
292 }
293 // Back
294 if ($this->returnUrl) {
295 $href = htmlspecialchars(GeneralUtility::linkThisUrl($this->returnUrl, array('id' => $this->id)));
296 $buttons['back'] = '<a href="' . $href . '" class="typo3-goBack" title="'
297 . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.goBack', TRUE) . '">'
298 . IconUtility::getSpriteIcon('actions-view-go-back') . '</a>';
299 }
300 }
301 return $buttons;
302 }
303
304 /**
305 * Creates the listing of records from a single table
306 *
307 * @param string $table Table name
308 * @param int $id Page id
309 * @param string $rowList List of fields to show in the listing. Pseudo fields will be added including the record header.
310 * @throws \UnexpectedValueException
311 * @return string HTML table with the listing for the record.
312 */
313 public function getTable($table, $id, $rowList) {
314 // Init
315 $addWhere = '';
316 $titleCol = $GLOBALS['TCA'][$table]['ctrl']['label'];
317 $thumbsCol = $GLOBALS['TCA'][$table]['ctrl']['thumbnail'];
318 $l10nEnabled = $GLOBALS['TCA'][$table]['ctrl']['languageField']
319 && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
320 && !$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable'];
321 $tableCollapsed = (bool)$this->tablesCollapsed[$table];
322 // prepare space icon
323 $this->spaceIcon = '<span class="btn disabled">' . IconUtility::getSpriteIcon('empty-empty') . '</span>';
324 // Cleaning rowlist for duplicates and place the $titleCol as the first column always!
325 $this->fieldArray = array();
326 // title Column
327 // Add title column
328 $this->fieldArray[] = $titleCol;
329 // Control-Panel
330 if (!GeneralUtility::inList($rowList, '_CONTROL_')) {
331 $this->fieldArray[] = '_CONTROL_';
332 }
333 // Clipboard
334 if ($this->showClipboard) {
335 $this->fieldArray[] = '_CLIPBOARD_';
336 }
337 // Ref
338 if (!$this->dontShowClipControlPanels) {
339 $this->fieldArray[] = '_REF_';
340 }
341 // Path
342 if ($this->searchLevels) {
343 $this->fieldArray[] = '_PATH_';
344 }
345 // Localization
346 if ($this->localizationView && $l10nEnabled) {
347 $this->fieldArray[] = '_LOCALIZATION_';
348 $this->fieldArray[] = '_LOCALIZATION_b';
349 $addWhere .= ' AND (
350 ' . $GLOBALS['TCA'][$table]['ctrl']['languageField'] . '<=0
351 OR
352 ' . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] . ' = 0
353 )';
354 }
355 // Cleaning up:
356 $this->fieldArray = array_unique(array_merge($this->fieldArray, GeneralUtility::trimExplode(',', $rowList, TRUE)));
357 if ($this->noControlPanels) {
358 $tempArray = array_flip($this->fieldArray);
359 unset($tempArray['_CONTROL_']);
360 unset($tempArray['_CLIPBOARD_']);
361 $this->fieldArray = array_keys($tempArray);
362 }
363 // Creating the list of fields to include in the SQL query:
364 $selectFields = $this->fieldArray;
365 $selectFields[] = 'uid';
366 $selectFields[] = 'pid';
367 // adding column for thumbnails
368 if ($thumbsCol) {
369 $selectFields[] = $thumbsCol;
370 }
371 if ($table == 'pages') {
372 $selectFields[] = 'module';
373 $selectFields[] = 'extendToSubpages';
374 $selectFields[] = 'nav_hide';
375 $selectFields[] = 'doktype';
376 $selectFields[] = 'shortcut';
377 $selectFields[] = 'shortcut_mode';
378 $selectFields[] = 'mount_pid';
379 }
380 if (is_array($GLOBALS['TCA'][$table]['ctrl']['enablecolumns'])) {
381 $selectFields = array_merge($selectFields, $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']);
382 }
383 if ($GLOBALS['TCA'][$table]['ctrl']['type']) {
384 $selectFields[] = $GLOBALS['TCA'][$table]['ctrl']['type'];
385 }
386 if ($GLOBALS['TCA'][$table]['ctrl']['typeicon_column']) {
387 $selectFields[] = $GLOBALS['TCA'][$table]['ctrl']['typeicon_column'];
388 }
389 if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
390 $selectFields[] = 't3ver_id';
391 $selectFields[] = 't3ver_state';
392 $selectFields[] = 't3ver_wsid';
393 }
394 if ($l10nEnabled) {
395 $selectFields[] = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
396 $selectFields[] = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
397 }
398 if ($GLOBALS['TCA'][$table]['ctrl']['label_alt']) {
399 $selectFields = array_merge(
400 $selectFields,
401 GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$table]['ctrl']['label_alt'], TRUE)
402 );
403 }
404 // Unique list!
405 $selectFields = array_unique($selectFields);
406 $fieldListFields = $this->makeFieldList($table, 1);
407 if (empty($fieldListFields) && $GLOBALS['TYPO3_CONF_VARS']['BE']['debug']) {
408 $message = sprintf($GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:missingTcaColumnsMessage', TRUE), $table, $table);
409 $messageTitle = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:missingTcaColumnsMessageTitle', TRUE);
410 $flashMessage = GeneralUtility::makeInstance(
411 \TYPO3\CMS\Core\Messaging\FlashMessage::class,
412 $message,
413 $messageTitle,
414 \TYPO3\CMS\Core\Messaging\FlashMessage::WARNING,
415 TRUE
416 );
417 /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
418 $flashMessageService = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Messaging\FlashMessageService::class);
419 /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
420 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
421 $defaultFlashMessageQueue->enqueue($flashMessage);
422 }
423 // Making sure that the fields in the field-list ARE in the field-list from TCA!
424 $selectFields = array_intersect($selectFields, $fieldListFields);
425 // Implode it into a list of fields for the SQL-statement.
426 $selFieldList = implode(',', $selectFields);
427 $this->selFieldList = $selFieldList;
428 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['getTable'])) {
429 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['getTable'] as $classData) {
430 $hookObject = GeneralUtility::getUserObj($classData);
431 if (!$hookObject instanceof \TYPO3\CMS\Backend\RecordList\RecordListGetTableHookInterface) {
432 throw new \UnexpectedValueException('$hookObject must implement interface ' . \TYPO3\CMS\Backend\RecordList\RecordListGetTableHookInterface::class, 1195114460);
433 }
434 $hookObject->getDBlistQuery($table, $id, $addWhere, $selFieldList, $this);
435 }
436 }
437 // Create the SQL query for selecting the elements in the listing:
438 // do not do paging when outputting as CSV
439 if ($this->csvOutput) {
440 $this->iLimit = 0;
441 }
442 if ($this->firstElementNumber > 2 && $this->iLimit > 0) {
443 // Get the two previous rows for sorting if displaying page > 1
444 $this->firstElementNumber = $this->firstElementNumber - 2;
445 $this->iLimit = $this->iLimit + 2;
446 // (API function from TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRecordList)
447 $queryParts = $this->makeQueryArray($table, $id, $addWhere, $selFieldList);
448 $this->firstElementNumber = $this->firstElementNumber + 2;
449 $this->iLimit = $this->iLimit - 2;
450 } else {
451 // (API function from TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRecordList)
452 $queryParts = $this->makeQueryArray($table, $id, $addWhere, $selFieldList);
453 }
454
455 // Finding the total amount of records on the page
456 // (API function from TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRecordList)
457 $this->setTotalItems($queryParts);
458
459 // Init:
460 $dbCount = 0;
461 $out = '';
462 $tableHeader = '';
463 $result = NULL;
464 $listOnlyInSingleTableMode = $this->listOnlyInSingleTableMode && !$this->table;
465 // If the count query returned any number of records, we perform the real query,
466 // selecting records.
467 if ($this->totalItems) {
468 // Fetch records only if not in single table mode or if in multi table mode and
469 // not collapsed
470 if ($listOnlyInSingleTableMode || !$this->table && $tableCollapsed) {
471 $dbCount = $this->totalItems;
472 } else {
473 // Set the showLimit to the number of records when outputting as CSV
474 if ($this->csvOutput) {
475 $this->showLimit = $this->totalItems;
476 $this->iLimit = $this->totalItems;
477 }
478 $result = $GLOBALS['TYPO3_DB']->exec_SELECT_queryArray($queryParts);
479 $dbCount = $GLOBALS['TYPO3_DB']->sql_num_rows($result);
480 }
481 }
482 // If any records was selected, render the list:
483 if ($dbCount) {
484 $tableTitle = $GLOBALS['LANG']->sL($GLOBALS['TCA'][$table]['ctrl']['title'], TRUE);
485 if ($tableTitle === '') {
486 $tableTitle = $table;
487 }
488 // Header line is drawn
489 $theData = array();
490 if ($this->disableSingleTableView) {
491 $theData[$titleCol] = '<span class="c-table">' . BackendUtility::wrapInHelp($table, '', $tableTitle)
492 . '</span> (<span class="t3js-table-total-items">' . $this->totalItems . '</span>)';
493 } else {
494 $icon = $this->table
495 ? IconUtility::getSpriteIcon('actions-view-table-collapse', array('title' => $GLOBALS['LANG']->getLL('contractView', TRUE)))
496 : IconUtility::getSpriteIcon('actions-view-table-expand', array('title' => $GLOBALS['LANG']->getLL('expandView', TRUE)));
497 $theData[$titleCol] = $this->linkWrapTable($table, $tableTitle . ' (<span class="t3js-table-total-items">' . $this->totalItems . '</span>) ' . $icon);
498 }
499 if ($listOnlyInSingleTableMode) {
500 $tableHeader .= BackendUtility::wrapInHelp($table, '', $theData[$titleCol]);
501 } else {
502 // Render collapse button if in multi table mode
503 $collapseIcon = '';
504 if (!$this->table) {
505 $href = htmlspecialchars(($this->listURL() . '&collapse[' . $table . ']=' . ($tableCollapsed ? '0' : '1')));
506 $title = $tableCollapsed
507 ? $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.expandTable', TRUE)
508 : $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.collapseTable', TRUE);
509 $icon = $tableCollapsed
510 ? IconUtility::getSpriteIcon('actions-view-list-expand', array('class' => 'collapseIcon'))
511 : IconUtility::getSpriteIcon('actions-view-list-collapse', array('class' => 'collapseIcon'));
512 $collapseIcon = '<a href="' . $href . '" title="' . $title . '" class="pull-right">' . $icon . '</a>';
513 }
514 $tableHeader .= $theData[$titleCol] . $collapseIcon;
515 }
516 // Render table rows only if in multi table view and not collapsed or if in
517 // single table view
518 $iOut = '';
519 if (!$listOnlyInSingleTableMode && (!$tableCollapsed || $this->table)) {
520 // Fixing a order table for sortby tables
521 $this->currentTable = array();
522 $currentIdList = array();
523 $doSort = $GLOBALS['TCA'][$table]['ctrl']['sortby'] && !$this->sortField;
524 $prevUid = 0;
525 $prevPrevUid = 0;
526 // Get first two rows and initialize prevPrevUid and prevUid if on page > 1
527 if ($this->firstElementNumber > 2 && $this->iLimit > 0) {
528 $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($result);
529 $prevPrevUid = -((int)$row['uid']);
530 $row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($result);
531 $prevUid = $row['uid'];
532 }
533 $accRows = array();
534 // Accumulate rows here
535 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($result)) {
536 if (!$this->isRowListingConditionFulfilled($table, $row)) {
537 continue;
538 }
539 // In offline workspace, look for alternative record:
540 BackendUtility::workspaceOL($table, $row, $GLOBALS['BE_USER']->workspace, TRUE);
541 if (is_array($row)) {
542 $accRows[] = $row;
543 $currentIdList[] = $row['uid'];
544 if ($doSort) {
545 if ($prevUid) {
546 $this->currentTable['prev'][$row['uid']] = $prevPrevUid;
547 $this->currentTable['next'][$prevUid] = '-' . $row['uid'];
548 $this->currentTable['prevUid'][$row['uid']] = $prevUid;
549 }
550 $prevPrevUid = isset($this->currentTable['prev'][$row['uid']]) ? -$prevUid : $row['pid'];
551 $prevUid = $row['uid'];
552 }
553 }
554 }
555 $GLOBALS['TYPO3_DB']->sql_free_result($result);
556 $this->totalRowCount = count($accRows);
557 // CSV initiated
558 if ($this->csvOutput) {
559 $this->initCSV();
560 }
561 // Render items:
562 $this->CBnames = array();
563 $this->duplicateStack = array();
564 $this->eCounter = $this->firstElementNumber;
565 $cc = 0;
566 foreach ($accRows as $row) {
567 // Render item row if counter < limit
568 if ($cc < $this->iLimit) {
569 $cc++;
570 $this->translations = FALSE;
571 $iOut .= $this->renderListRow($table, $row, $cc, $titleCol, $thumbsCol);
572 // If localization view is enabled it means that the selected records are
573 // either default or All language and here we will not select translations
574 // which point to the main record:
575 if ($this->localizationView && $l10nEnabled) {
576 // For each available translation, render the record:
577 if (is_array($this->translations)) {
578 foreach ($this->translations as $lRow) {
579 // $lRow isn't always what we want - if record was moved we've to work with the
580 // placeholder records otherwise the list is messed up a bit
581 if ($row['_MOVE_PLH_uid'] && $row['_MOVE_PLH_pid']) {
582 $where = 't3ver_move_id="' . (int)$lRow['uid'] . '" AND pid="' . $row['_MOVE_PLH_pid']
583 . '" AND t3ver_wsid=' . $row['t3ver_wsid'] . BackendUtility::deleteClause($table);
584 $tmpRow = BackendUtility::getRecordRaw($table, $where, $selFieldList);
585 $lRow = is_array($tmpRow) ? $tmpRow : $lRow;
586 }
587 // In offline workspace, look for alternative record:
588 BackendUtility::workspaceOL($table, $lRow, $GLOBALS['BE_USER']->workspace, TRUE);
589 if (is_array($lRow) && $GLOBALS['BE_USER']->checkLanguageAccess($lRow[$GLOBALS['TCA'][$table]['ctrl']['languageField']])) {
590 $currentIdList[] = $lRow['uid'];
591 $iOut .= $this->renderListRow($table, $lRow, $cc, $titleCol, $thumbsCol, 18);
592 }
593 }
594 }
595 }
596 }
597 // Counter of total rows incremented:
598 $this->eCounter++;
599 }
600 // Record navigation is added to the beginning and end of the table if in single
601 // table mode
602 if ($this->table) {
603 $iOut = $this->renderListNavigation('top') . $iOut . $this->renderListNavigation('bottom');
604 } else {
605 // Show that there are more records than shown
606 if ($this->totalItems > $this->itemsLimitPerTable) {
607 $countOnFirstPage = $this->totalItems > $this->itemsLimitSingleTable ? $this->itemsLimitSingleTable : $this->totalItems;
608 $hasMore = $this->totalItems > $this->itemsLimitSingleTable;
609 $colspan = $this->showIcon ? count($this->fieldArray) + 1 : count($this->fieldArray);
610 $iOut .= '<tr><td colspan="' . $colspan . '">
611 <a href="' . htmlspecialchars(($this->listURL() . '&table=' . rawurlencode($table))) . '">'
612 . '<img' . IconUtility::skinImg($this->backPath, 'gfx/pildown.gif', 'width="14" height="14"') . ' alt="" />'
613 . ' <i>[1 - ' . $countOnFirstPage . ($hasMore ? '+' : '') . ']</i></a>
614 </td></tr>';
615 }
616 }
617 // The header row for the table is now created:
618 $out .= $this->renderListHeader($table, $currentIdList);
619 }
620 // The list of records is added after the header:
621 $out .= $iOut;
622 // ... and it is all wrapped in a table:
623 $out = '
624
625
626
627 <!--
628 DB listing of elements: "' . htmlspecialchars($table) . '"
629 -->
630 <div class="panel panel-default">
631 <div class="panel-heading">
632 ' . $tableHeader . '
633 </div>
634 <div class="table-fit">
635 <table data-table="' . htmlspecialchars($table) . '" class="table table-hover' . ($listOnlyInSingleTableMode ? ' typo3-dblist-overview' : '') . '">
636 ' . $out . '
637 </table>
638 </div>
639 </div>
640 ';
641 // Output csv if...
642 // This ends the page with exit.
643 if ($this->csvOutput) {
644 $this->outputCSV($table);
645 }
646 }
647 // Return content:
648 return $out;
649 }
650
651 /**
652 * Check if all row listing conditions are fulfilled.
653 *
654 * This function serves as a dummy method to be overriden in extending classes.
655 *
656 * @param string $table Table name
657 * @param array $row Record
658 * @return bool True, if all conditions are fulfilled.
659 */
660 protected function isRowListingConditionFulfilled($table, $row) {
661 return TRUE;
662 }
663
664 /**
665 * Rendering a single row for the list
666 *
667 * @param string $table Table name
668 * @param array $row Current record
669 * @param int $cc Counter, counting for each time an element is rendered (used for alternating colors)
670 * @param string $titleCol Table field (column) where header value is found
671 * @param string $thumbsCol Table field (column) where (possible) thumbnails can be found
672 * @param int $indent Indent from left.
673 * @return string Table row for the element
674 * @access private
675 * @see getTable()
676 */
677 public function renderListRow($table, $row, $cc, $titleCol, $thumbsCol, $indent = 0) {
678 $iOut = '';
679 $id_orig = NULL;
680 // If in search mode, make sure the preview will show the correct page
681 if (strlen($this->searchString)) {
682 $id_orig = $this->id;
683 $this->id = $row['pid'];
684 }
685 if (is_array($row)) {
686 // Add special classes for first and last row
687 $rowSpecial = '';
688 if ($cc == 1 && $indent == 0) {
689 $rowSpecial .= ' firstcol';
690 }
691 if ($cc == $this->totalRowCount || $cc == $this->iLimit) {
692 $rowSpecial .= ' lastcol';
693 }
694
695 $row_bgColor = ' class="' . $rowSpecial . '"';
696
697 // Overriding with versions background color if any:
698 $row_bgColor = $row['_CSSCLASS'] ? ' class="' . $row['_CSSCLASS'] . '"' : $row_bgColor;
699 // Incr. counter.
700 $this->counter++;
701 // The icon with link
702 $altText = htmlspecialchars(BackendUtility::getRecordIconAltText($row, $table));
703 $iconImg = IconUtility::getSpriteIconForRecord(
704 $table,
705 $row,
706 array('title' => $altText, 'style' => $indent ? ' margin-left: ' . $indent . 'px;' : '')
707 );
708 $theIcon = $this->clickMenuEnabled ? $GLOBALS['SOBE']->doc->wrapClickMenuOnIcon($iconImg, $table, $row['uid']) : $iconImg;
709 // Preparing and getting the data-array
710 $theData = array();
711 $localizationMarkerClass = '';
712 foreach ($this->fieldArray as $fCol) {
713 if ($fCol == $titleCol) {
714 $recTitle = BackendUtility::getRecordTitle($table, $row, FALSE, TRUE);
715 $warning = '';
716 // If the record is edit-locked by another user, we will show a little warning sign:
717 if ($lockInfo = BackendUtility::isRecordLocked($table, $row['uid'])) {
718 $warning = '<a href="#" onclick="alert('
719 . GeneralUtility::quoteJSvalue($lockInfo['msg']) . '); return false;" title="'
720 . htmlspecialchars($lockInfo['msg']) . '">'
721 . IconUtility::getSpriteIcon('status-warning-in-use') . '</a>';
722 }
723 $theData[$fCol] = $warning . $this->linkWrapItems($table, $row['uid'], $recTitle, $row);
724 // Render thumbnails, if:
725 // - a thumbnail column exists
726 // - there is content in it
727 // - the thumbnail column is visible for the current type
728 $type = 0;
729 if (isset($GLOBALS['TCA'][$table]['ctrl']['type'])) {
730 $typeColumn = $GLOBALS['TCA'][$table]['ctrl']['type'];
731 $type = $row[$typeColumn];
732 }
733 // If current type doesn't exist, set it to 0 (or to 1 for historical reasons,
734 // if 0 doesn't exist)
735 if (!isset($GLOBALS['TCA'][$table]['types'][$type])) {
736 $type = isset($GLOBALS['TCA'][$table]['types'][0]) ? 0 : 1;
737 }
738 $visibleColumns = $GLOBALS['TCA'][$table]['types'][$type]['showitem'];
739
740 if ($this->thumbs &&
741 trim($row[$thumbsCol]) &&
742 preg_match('/(^|(.*(;|,)?))' . $thumbsCol . '(((;|,).*)|$)/', $visibleColumns) === 1
743 ) {
744 $theData[$fCol] .= '<br />' . $this->thumbCode($row, $table, $thumbsCol);
745 }
746 if (
747 isset($GLOBALS['TCA'][$table]['ctrl']['languageField'])
748 && $row[$GLOBALS['TCA'][$table]['ctrl']['languageField']] != 0
749 && $row[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] != 0
750 ) {
751 // It's a translated record with a language parent
752 $localizationMarkerClass = ' localization';
753 }
754 } elseif ($fCol == 'pid') {
755 $theData[$fCol] = $row[$fCol];
756 } elseif ($fCol == '_PATH_') {
757 $theData[$fCol] = $this->recPath($row['pid']);
758 } elseif ($fCol == '_REF_') {
759 $theData[$fCol] = $this->createReferenceHtml($table, $row['uid']);
760 } elseif ($fCol == '_CONTROL_') {
761 $theData[$fCol] = $this->makeControl($table, $row);
762 } elseif ($fCol == '_CLIPBOARD_') {
763 $theData[$fCol] = $this->makeClip($table, $row);
764 } elseif ($fCol == '_LOCALIZATION_') {
765 list($lC1, $lC2) = $this->makeLocalizationPanel($table, $row);
766 $theData[$fCol] = $lC1;
767 $theData[$fCol . 'b'] = $lC2;
768 } elseif ($fCol == '_LOCALIZATION_b') {
769 // deliberately empty
770 } else {
771 $tmpProc = BackendUtility::getProcessedValueExtra($table, $fCol, $row[$fCol], 100, $row['uid']);
772 $theData[$fCol] = $this->linkUrlMail(htmlspecialchars($tmpProc), $row[$fCol]);
773 if ($this->csvOutput) {
774 $row[$fCol] = BackendUtility::getProcessedValueExtra($table, $fCol, $row[$fCol], 0, $row['uid']);
775 }
776 }
777 }
778 // Reset the ID if it was overwritten
779 if (strlen($this->searchString)) {
780 $this->id = $id_orig;
781 }
782 // Add row to CSV list:
783 if ($this->csvOutput) {
784 $this->addToCSV($row, $table);
785 }
786 // Add classes to table cells
787 $this->addElement_tdCssClass[$titleCol] = 'col-title' . $localizationMarkerClass;
788 if (!$this->dontShowClipControlPanels) {
789 $this->addElement_tdCssClass['_CONTROL_'] = 'col-control';
790 $this->addElement_tdCssClass['_CLIPBOARD_'] = 'col-clipboard';
791 }
792 $this->addElement_tdCssClass['_PATH_'] = 'col-path';
793 $this->addElement_tdCssClass['_LOCALIZATION_'] = 'col-localizationa';
794 $this->addElement_tdCssClass['_LOCALIZATION_b'] = 'col-localizationb';
795 // Create element in table cells:
796 $theData['uid'] = $row['uid'];
797 $iOut .= $this->addelement(1, $theIcon, $theData, $row_bgColor);
798 // Finally, return table row element:
799 return $iOut;
800 }
801 }
802
803 /**
804 * Gets the number of records referencing the record with the UID $uid in
805 * the table $tableName.
806 *
807 * @param string $tableName
808 * @param int $uid
809 * @return int The number of references to record $uid in table
810 */
811 protected function getReferenceCount($tableName, $uid) {
812 if (!isset($this->referenceCount[$tableName][$uid])) {
813 $where = 'ref_table = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr($tableName, 'sys_refindex')
814 . ' AND ref_uid = ' . $uid . ' AND deleted = 0';
815 $numberOfReferences = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('*', 'sys_refindex', $where);
816 $this->referenceCount[$tableName][$uid] = $numberOfReferences;
817 }
818 return $this->referenceCount[$tableName][$uid];
819 }
820
821 /**
822 * Rendering the header row for a table
823 *
824 * @param string $table Table name
825 * @param array $currentIdList Array of the currently displayed uids of the table
826 * @throws \UnexpectedValueException
827 * @return string Header table row
828 * @access private
829 * @see getTable()
830 */
831 public function renderListHeader($table, $currentIdList) {
832 // Init:
833 $theData = array();
834 $icon = '';
835 // Traverse the fields:
836 foreach ($this->fieldArray as $fCol) {
837 // Calculate users permissions to edit records in the table:
838 $permsEdit = $this->calcPerms & ($table == 'pages' ? 2 : 16);
839 switch ((string)$fCol) {
840 case '_PATH_':
841 // Path
842 $theData[$fCol] = '<i>[' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels._PATH_', TRUE) . ']</i>';
843 break;
844 case '_REF_':
845 // References
846 $theData[$fCol] = '<i>[' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_file_list.xlf:c__REF_', TRUE) . ']</i>';
847 break;
848 case '_LOCALIZATION_':
849 // Path
850 $theData[$fCol] = '<i>[' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels._LOCALIZATION_', TRUE) . ']</i>';
851 break;
852 case '_LOCALIZATION_b':
853 // Path
854 $theData[$fCol] = $GLOBALS['LANG']->getLL('Localize', TRUE);
855 break;
856 case '_CLIPBOARD_':
857 // Clipboard:
858 $cells = array();
859 // If there are elements on the clipboard for this table, then display the
860 // "paste into" icon:
861 $elFromTable = $this->clipObj->elFromTable($table);
862 if (count($elFromTable)) {
863 $href = htmlspecialchars($this->clipObj->pasteUrl($table, $this->id));
864 $onClick = htmlspecialchars('return ' . $this->clipObj->confirmMsg('pages', $this->pageRow, 'into', $elFromTable));
865 $cells['pasteAfter'] = '<a class="btn" href="' . $href . '" onclick="' . $onClick
866 . '" title="' . $GLOBALS['LANG']->getLL('clip_paste', TRUE) . '">'
867 . IconUtility::getSpriteIcon('actions-document-paste-after') . '</a>';
868 }
869 // If the numeric clipboard pads are enabled, display the control icons for that:
870 if ($this->clipObj->current != 'normal') {
871 // The "select" link:
872 $spriteIcon = IconUtility::getSpriteIcon('actions-edit-copy', array('title' => $GLOBALS['LANG']->getLL('clip_selectMarked', TRUE)));
873 $cells['copyMarked'] = $this->linkClipboardHeaderIcon($spriteIcon, $table, 'setCB');
874 // The "edit marked" link:
875 $editIdList = implode(',', $currentIdList);
876 $editIdList = '\'+editList(\'' . $table . '\',\'' . $editIdList . '\')+\'';
877 $params = '&edit[' . $table . '][' . $editIdList . ']=edit&disHelp=1';
878 $onClick = htmlspecialchars(BackendUtility::editOnClick($params, $this->backPath, -1));
879 $cells['edit'] = '<a class="btn" href="#" onclick="' . $onClick . '" title="'
880 . $GLOBALS['LANG']->getLL('clip_editMarked', TRUE) . '">'
881 . IconUtility::getSpriteIcon('actions-document-open') . '</a>';
882 // The "Delete marked" link:
883 $cells['delete'] = $this->linkClipboardHeaderIcon(
884 IconUtility::getSpriteIcon('actions-edit-delete', array('title' => $GLOBALS['LANG']->getLL('clip_deleteMarked', TRUE))),
885 $table,
886 'delete',
887 sprintf($GLOBALS['LANG']->getLL('clip_deleteMarkedWarning'), $GLOBALS['LANG']->sL($GLOBALS['TCA'][$table]['ctrl']['title']))
888 );
889 // The "Select all" link:
890 $onClick = htmlspecialchars(('checkOffCB(\'' . implode(',', $this->CBnames) . '\', this); return false;'));
891 $cells['markAll'] = '<a class="btn" rel="" href="#" onclick="' . $onClick . '" title="'
892 . $GLOBALS['LANG']->getLL('clip_markRecords', TRUE) . '">'
893 . IconUtility::getSpriteIcon('actions-document-select') . '</a>';
894 } else {
895 $cells['empty'] = '';
896 }
897 /**
898 * @hook renderListHeaderActions: Allows to change the clipboard icons of the Web>List table headers
899 * @usage Above each listed table in Web>List a header row is shown.
900 * This hook allows to modify the icons responsible for the clipboard functions
901 * (shown above the clipboard checkboxes when a clipboard other than "Normal" is selected),
902 * or other "Action" functions which perform operations on the listed records.
903 */
904 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['actions'])) {
905 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['actions'] as $classData) {
906 $hookObject = GeneralUtility::getUserObj($classData);
907 if (!$hookObject instanceof \TYPO3\CMS\Recordlist\RecordList\RecordListHookInterface) {
908 throw new \UnexpectedValueException('$hookObject must implement interface ' . \TYPO3\CMS\Recordlist\RecordList\RecordListHookInterface::class, 1195567850);
909 }
910 $cells = $hookObject->renderListHeaderActions($table, $currentIdList, $cells, $this);
911 }
912 }
913 $theData[$fCol] = '<div class="btn-group">' . implode('', $cells) . '</div>';
914 break;
915 case '_CONTROL_':
916 // Control panel:
917 if (!$GLOBALS['TCA'][$table]['ctrl']['readOnly']) {
918 // If new records can be created on this page, add links:
919 if ($this->calcPerms & ($table === 'pages' ? 8 : 16) && $this->showNewRecLink($table)) {
920 $spriteIcon = $table === 'pages'
921 ? IconUtility::getSpriteIcon('actions-page-new')
922 : IconUtility::getSpriteIcon('actions-document-new');
923 if ($table === 'tt_content' && $this->newWizards) {
924 // If mod.web_list.newContentWiz.overrideWithExtension is set, use that extension's create new content wizard instead:
925 $tmpTSc = BackendUtility::getModTSconfig($this->pageinfo['uid'], 'mod.web_list');
926 $tmpTSc = $tmpTSc['properties']['newContentWiz.']['overrideWithExtension'];
927 $wizardPath = ExtensionManagementUtility::isLoaded($tmpTSc)
928 ? ExtensionManagementUtility::extRelPath($tmpTSc) . 'mod1/db_new_content_el.php'
929 : 'sysext/cms/layout/db_new_content_el.php';
930 $newContentWizScriptPath = $this->backPath . $wizardPath;
931 $onClick = htmlspecialchars('return jumpExt(\'' . $newContentWizScriptPath . '?id=' . $this->id . '\');');
932 $icon = '<a class="btn btn-success" href="#" onclick="' . $onClick . '" title="'
933 . $GLOBALS['LANG']->getLL('new', TRUE) . '">' . $spriteIcon . '</a>';
934 } elseif ($table == 'pages' && $this->newWizards) {
935 $href = htmlspecialchars($this->backPath . 'db_new.php?id=' . $this->id
936 . '&pagesOnly=1&returnUrl=' . rawurlencode(GeneralUtility::getIndpEnv('REQUEST_URI')));
937 $icon = '<a class="btn success" href="' . $href . '" title="' . $GLOBALS['LANG']->getLL('new', TRUE) . '">'
938 . $spriteIcon . '</a>';
939 } else {
940 $params = '&edit[' . $table . '][' . $this->id . ']=new';
941 if ($table == 'pages_language_overlay') {
942 $params .= '&overrideVals[pages_language_overlay][doktype]=' . (int)$this->pageRow['doktype'];
943 }
944 $icon = '<a class="btn btn-success" href="#" onclick="' . htmlspecialchars(BackendUtility::editOnClick($params, $this->backPath, -1))
945 . '" title="' . $GLOBALS['LANG']->getLL('new', TRUE) . '">' . $spriteIcon . '</a>';
946 }
947 }
948 // If the table can be edited, add link for editing ALL SHOWN fields for all listed records:
949 if ($permsEdit && $this->table && is_array($currentIdList)) {
950 $editIdList = implode(',', $currentIdList);
951 if ($this->clipNumPane()) {
952 $editIdList = '\'+editList(\'' . $table . '\',\'' . $editIdList . '\')+\'';
953 }
954 $params = '&edit[' . $table . '][' . $editIdList . ']=edit&columnsOnly=' . implode(',', $this->fieldArray) . '&disHelp=1';
955 $icon .= '<a class="btn" href="#" onclick="' . htmlspecialchars(BackendUtility::editOnClick($params, $this->backPath, -1))
956 . '" title="' . $GLOBALS['LANG']->getLL('editShownColumns', TRUE) . '">'
957 . IconUtility::getSpriteIcon('actions-document-open') . '</a>';
958 $icon = '<div class="btn-group">' . $icon . '</div>';
959 }
960 // Add an empty entry, so column count fits again after moving this into $icon
961 $theData[$fCol] = '&nbsp;';
962 }
963 break;
964 default:
965 // Regular fields header:
966 $theData[$fCol] = '';
967
968 // Check if $fCol is really a field and get the label and remove the colons
969 // at the end
970 $sortLabel = BackendUtility::getItemLabel($table, $fCol);
971 if ($sortLabel !== NULL) {
972 $sortLabel = $GLOBALS['LANG']->sL($sortLabel, TRUE);
973 $sortLabel = rtrim(trim($sortLabel), ':');
974 } else {
975 // No TCA field, only output the $fCol variable with square brackets []
976 $sortLabel = htmlspecialchars($fCol);
977 $sortLabel = '<i>[' . rtrim(trim($sortLabel), ':') . ']</i>';
978 }
979
980 if ($this->table && is_array($currentIdList)) {
981 // If the numeric clipboard pads are selected, show duplicate sorting link:
982 if ($this->clipNumPane()) {
983 $theData[$fCol] .= '<a class="btn" href="' . htmlspecialchars($this->listURL('', -1) . '&duplicateField=' . $fCol)
984 . '" title="' . $GLOBALS['LANG']->getLL('clip_duplicates', TRUE) . '">'
985 . IconUtility::getSpriteIcon('actions-document-duplicates-select') . '</a>';
986 }
987 // If the table can be edited, add link for editing THIS field for all
988 // listed records:
989 if (!$GLOBALS['TCA'][$table]['ctrl']['readOnly'] && $permsEdit && $GLOBALS['TCA'][$table]['columns'][$fCol]) {
990 $editIdList = implode(',', $currentIdList);
991 if ($this->clipNumPane()) {
992 $editIdList = '\'+editList(\'' . $table . '\',\'' . $editIdList . '\')+\'';
993 }
994 $params = '&edit[' . $table . '][' . $editIdList . ']=edit&columnsOnly=' . $fCol . '&disHelp=1';
995 $iTitle = sprintf($GLOBALS['LANG']->getLL('editThisColumn'), $sortLabel);
996 $theData[$fCol] .= '<a class="btn" href="#" onclick="' . htmlspecialchars(BackendUtility::editOnClick($params, $this->backPath, -1))
997 . '" title="' . htmlspecialchars($iTitle) . '">'
998 . IconUtility::getSpriteIcon('actions-document-open') . '</a>';
999 }
1000 if(strlen($theData[$fCol]) > 0){
1001 $theData[$fCol] = '<div class="btn-group">' . $theData[$fCol] . '</div> ';
1002 }
1003 }
1004 $theData[$fCol] .= $this->addSortLink($sortLabel, $fCol, $table);
1005 }
1006 }
1007 /**
1008 * @hook renderListHeader: Allows to change the contents of columns/cells of the Web>List table headers
1009 * @usage Above each listed table in Web>List a header row is shown.
1010 * Containing the labels of all shown fields and additional icons to create new records for this
1011 * table or perform special clipboard tasks like mark and copy all listed records to clipboard, etc.
1012 */
1013 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['actions'])) {
1014 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['actions'] as $classData) {
1015 $hookObject = GeneralUtility::getUserObj($classData);
1016 if (!$hookObject instanceof \TYPO3\CMS\Recordlist\RecordList\RecordListHookInterface) {
1017 throw new \UnexpectedValueException('$hookObject must implement interface ' . \TYPO3\CMS\Recordlist\RecordList\RecordListHookInterface::class, 1195567855);
1018 }
1019 $theData = $hookObject->renderListHeader($table, $currentIdList, $theData, $this);
1020 }
1021 }
1022
1023
1024 // Create and return header table row:
1025 return '<thead>' . $this->addelement(1, $icon, $theData, '', '', '', 'th') . '</thead>';
1026 }
1027
1028 /**
1029 * Get pointer for first element on the page
1030 *
1031 * @param int $page Page number starting with 1
1032 * @return int Pointer to first element on the page (starting with 0)
1033 */
1034 protected function getPointerForPage($page) {
1035 return ($page - 1) * $this->iLimit;
1036 }
1037
1038 /**
1039 * Creates a page browser for tables with many records
1040 *
1041 * @param string $renderPart Distinguish between 'top' and 'bottom' part of the navigation (above or below the records)
1042 * @return string Navigation HTML
1043 */
1044 protected function renderListNavigation($renderPart = 'top') {
1045 $totalPages = ceil($this->totalItems / $this->iLimit);
1046 // Show page selector if not all records fit into one page
1047 if ($totalPages <= 1) {
1048 return '';
1049 }
1050 $listURL = $this->listURL('', $this->table);
1051 // 1 = first page
1052 // 0 = first element
1053 $currentPage = floor($this->firstElementNumber / $this->iLimit) + 1;
1054 // Compile first, previous, next, last and refresh buttons
1055 if ($currentPage > 1) {
1056 $labelFirst = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xlf:first');
1057 $labelPrevious = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xlf:previous');
1058 $first = '<li><a href="' . $listURL . '&pointer=' . $this->getPointerForPage(1) . '">'
1059 . IconUtility::getSpriteIcon('actions-view-paging-first', array('title' => $labelFirst)) . '</a></li>';
1060 $previous = '<li><a href="' . $listURL . '&pointer=' . $this->getPointerForPage($currentPage - 1) . '">'
1061 . IconUtility::getSpriteIcon('actions-view-paging-previous', array('title' => $labelPrevious)) . '</a></li>';
1062 } else {
1063 $first = '<li class="disabled"><span>' . IconUtility::getSpriteIcon('actions-view-paging-first') . '</span></li>';
1064 $previous = '<li class="disabled"><span>' . IconUtility::getSpriteIcon('actions-view-paging-previous') . '</span></li>';
1065 }
1066 if ($currentPage < $totalPages) {
1067 $labelNext = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xlf:next');
1068 $labelLast = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xlf:last');
1069 $next = '<li><a href="' . $listURL . '&pointer=' . $this->getPointerForPage($currentPage + 1) . '">'
1070 . IconUtility::getSpriteIcon('actions-view-paging-next', array('title' => $labelNext)) . '</a></li>';
1071 $last = '<li><a href="' . $listURL . '&pointer=' . $this->getPointerForPage($totalPages) . '">'
1072 . IconUtility::getSpriteIcon('actions-view-paging-last', array('title' => $labelLast)) . '</a></li>';
1073 } else {
1074 $next = '<li class="disabled"><span>' . IconUtility::getSpriteIcon('actions-view-paging-next') . '</span></li>';
1075 $last = '<li class="disabled"><span>' . IconUtility::getSpriteIcon('actions-view-paging-last') . '</span></li>';
1076 }
1077 $reload = '<li><a href="#" onclick="document.dblistForm.action=\'' . $listURL
1078 . '&pointer=\'+calculatePointer(document.getElementById(\'jumpPage-' . $renderPart
1079 . '\').value); document.dblistForm.submit(); return true;" title="'
1080 . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_common.xlf:reload', TRUE) . '">'
1081 . IconUtility::getSpriteIcon('actions-system-refresh') . '</a></li>';
1082 if ($renderPart === 'top') {
1083 // Add js to traverse a page select input to a pointer value
1084 $content = '
1085 <script type="text/JavaScript">
1086 /*<![CDATA[*/
1087 function calculatePointer(page) {
1088 if (page > ' . $totalPages . ') {
1089 page = ' . $totalPages . ';
1090 }
1091 if (page < 1) {
1092 page = 1;
1093 }
1094 return (page - 1) * ' . $this->iLimit . ';
1095 }
1096 /*]]>*/
1097 </script>
1098 ';
1099 }
1100 $pageNumberInput = '<span class="paginator-input">
1101 <input type="text" value="' . $currentPage . '" size="3" id="jumpPage-' . $renderPart . '" name="jumpPage-'
1102 . $renderPart . '" onkeyup="if (event.keyCode == Event.KEY_RETURN) { document.dblistForm.action=\'' . $listURL
1103 . '&pointer=\'+calculatePointer(this.value); document.dblistForm.submit(); } return true;" />
1104 </span>';
1105 $pageIndicatorText = sprintf(
1106 $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:pageIndicator'),
1107 $pageNumberInput,
1108 $totalPages
1109 );
1110 $pageIndicator = '<li><span>' . $pageIndicatorText . '</span></li>';
1111 if ($this->totalItems > $this->firstElementNumber + $this->iLimit) {
1112 $lastElementNumber = $this->firstElementNumber + $this->iLimit;
1113 } else {
1114 $lastElementNumber = $this->totalItems;
1115 }
1116 $rangeIndicator = '<li><span>' . sprintf($GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:rangeIndicator'), ($this->firstElementNumber + 1), $lastElementNumber) . '</span></li>';
1117
1118 $titleColumn = $this->fieldArray[0];
1119 $data = array(
1120 $titleColumn => '
1121 <nav>
1122 <ul class="pagination">
1123 ' . $first . '
1124 ' . $previous . '
1125 ' . $rangeIndicator . '
1126 ' . $pageIndicator . '
1127 ' . $next . '
1128 ' . $last . '
1129 ' . $reload . '
1130 </ul>
1131 </nav>
1132 '
1133 );
1134 return $this->addElement(1, '', $data);
1135 }
1136
1137 /*********************************
1138 *
1139 * Rendering of various elements
1140 *
1141 *********************************/
1142
1143 /**
1144 * Creates the control panel for a single record in the listing.
1145 *
1146 * @param string $table The table
1147 * @param array $row The record for which to make the control panel.
1148 * @throws \UnexpectedValueException
1149 * @return string HTML table with the control panel (unless disabled)
1150 */
1151 public function makeControl($table, $row) {
1152 if ($this->dontShowClipControlPanels) {
1153 return '';
1154 }
1155 $rowUid = $row['uid'];
1156 if (ExtensionManagementUtility::isLoaded('version') && isset($row['_ORIG_uid'])) {
1157 $rowUid = $row['_ORIG_uid'];
1158 }
1159 $cells = array();
1160 // If the listed table is 'pages' we have to request the permission settings for each page:
1161 $localCalcPerms = 0;
1162 if ($table == 'pages') {
1163 $localCalcPerms = $GLOBALS['BE_USER']->calcPerms(BackendUtility::getRecord('pages', $row['uid']));
1164 }
1165 // This expresses the edit permissions for this particular element:
1166 $permsEdit = $table == 'pages' && $localCalcPerms & 2 || $table != 'pages' && $this->calcPerms & 16;
1167 // "Show" link (only pages and tt_content elements)
1168 if ($table == 'pages' || $table == 'tt_content') {
1169 $cells['view'] = '<a class="btn" href="#" onclick="'
1170 . htmlspecialchars(
1171 BackendUtility::viewOnClick(
1172 ($table === 'tt_content' ? $this->id : $row['uid']),
1173 $this->backPath,
1174 '',
1175 ($table === 'tt_content' ? '#' . $row['uid'] : '')
1176 )
1177 ) . '" title="' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.showPage', TRUE) . '">'
1178 . IconUtility::getSpriteIcon('actions-document-view') . '</a>';
1179 }
1180 // "Edit" link: ( Only if permissions to edit the page-record of the content of the parent page ($this->id)
1181 if ($permsEdit) {
1182 $params = '&edit[' . $table . '][' . $row['uid'] . ']=edit';
1183 $spriteIcon = $GLOBALS['TCA'][$table]['ctrl']['readOnly']
1184 ? IconUtility::getSpriteIcon('actions-document-open-read-only')
1185 : IconUtility::getSpriteIcon('actions-document-open');
1186 $cells['edit'] = '<a class="btn" href="#" onclick="' . htmlspecialchars(BackendUtility::editOnClick($params, $this->backPath, -1))
1187 . '" title="' . $GLOBALS['LANG']->getLL('edit', TRUE) . '">' . $spriteIcon . '</a>';
1188 }
1189 // "Move" wizard link for pages/tt_content elements:
1190 if ($table == 'tt_content' && $permsEdit || $table == 'pages') {
1191 $onClick = 'return jumpExt(\'' . $this->backPath . 'move_el.php?table=' . $table . '&uid=' . $row['uid'] . '\');';
1192 $linkTitleLL = $GLOBALS['LANG']->getLL('move_' . ($table === 'tt_content' ? 'record' : 'page'), TRUE);
1193 $spriteIcon = $table === 'tt_content'
1194 ? IconUtility::getSpriteIcon('actions-document-move')
1195 : IconUtility::getSpriteIcon('actions-page-move');
1196 $cells['move'] = '<a class="btn" href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . $linkTitleLL . '">' . $spriteIcon . '</a>';
1197 }
1198 // If the extended control panel is enabled OR if we are seeing a single table:
1199 if ($GLOBALS['SOBE']->MOD_SETTINGS['bigControlPanel'] || $this->table) {
1200 // "Info": (All records)
1201 $onClick = 'top.launchView(\'' . $table . '\', \'' . $row['uid'] . '\'); return false;';
1202 $cells['viewBig'] = '<a class="btn" href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . $GLOBALS['LANG']->getLL('showInfo', TRUE) . '">'
1203 . IconUtility::getSpriteIcon('actions-document-info') . '</a>';
1204 // If the table is NOT a read-only table, then show these links:
1205 if (!$GLOBALS['TCA'][$table]['ctrl']['readOnly']) {
1206 // "Revert" link (history/undo)
1207 $moduleUrl = BackendUtility::getModuleUrl('record_history', array('element' => $table . ':' . $row['uid']));
1208 $onClick = 'return jumpExt(' . GeneralUtility::quoteJSvalue($this->backPath . $moduleUrl) . ',\'#latest\');';
1209 $cells['history'] = '<a class="btn" href="#" onclick="' . htmlspecialchars($onClick) . '" title="'
1210 . $GLOBALS['LANG']->getLL('history', TRUE) . '">'
1211 . IconUtility::getSpriteIcon('actions-document-history-open') . '</a>';
1212 // Versioning:
1213 if (ExtensionManagementUtility::isLoaded('version') && !ExtensionManagementUtility::isLoaded('workspaces')) {
1214 $vers = BackendUtility::selectVersionsOfRecord($table, $row['uid'], 'uid', $GLOBALS['BE_USER']->workspace, FALSE, $row);
1215 // If table can be versionized.
1216 if (is_array($vers)) {
1217 $versionIcon = 'no-version';
1218 if (count($vers) > 1) {
1219 $versionIcon = count($vers) - 1;
1220 }
1221 $href = $this->backPath . BackendUtility::getModuleUrl('web_txversionM1', array(
1222 'table' => $table, 'uid' => $row['uid']
1223 ));
1224 $cells['version'] = '<a class="btn" href="' . htmlspecialchars($href) . '" title="'
1225 . $GLOBALS['LANG']->getLL('displayVersions', TRUE) . '">'
1226 . IconUtility::getSpriteIcon(('status-version-' . $versionIcon)) . '</a>';
1227 }
1228 }
1229 // "Edit Perms" link:
1230 if ($table === 'pages' && $GLOBALS['BE_USER']->check('modules', 'system_BeuserTxPermission') && ExtensionManagementUtility::isLoaded('beuser')) {
1231 $href = BackendUtility::getModuleUrl('system_BeuserTxPermission') . '&id=' . $row['uid'] . '&return_id=' . $row['uid'] . '&edit=1';
1232 $cells['perms'] = '<a class="btn" href="' . htmlspecialchars($href) . '" title="'
1233 . $GLOBALS['LANG']->getLL('permissions', TRUE) . '">'
1234 . IconUtility::getSpriteIcon('status-status-locked') . '</a>';
1235 }
1236 // "New record after" link (ONLY if the records in the table are sorted by a "sortby"-row
1237 // or if default values can depend on previous record):
1238 if ($GLOBALS['TCA'][$table]['ctrl']['sortby'] || $GLOBALS['TCA'][$table]['ctrl']['useColumnsForDefaultValues']) {
1239 if ($table !== 'pages' && $this->calcPerms & 16 || $table === 'pages' && $this->calcPerms & 8) {
1240 if ($this->showNewRecLink($table)) {
1241 $params = '&edit[' . $table . '][' . -($row['_MOVE_PLH'] ? $row['_MOVE_PLH_uid'] : $row['uid']) . ']=new';
1242 $cells['new'] = '<a class="btn" href="#" onclick="' . htmlspecialchars(BackendUtility::editOnClick($params, $this->backPath, -1))
1243 . '" title="' . $GLOBALS['LANG']->getLL('new' . ($table == 'pages ' ? 'Page' : 'Record'), TRUE) . '">'
1244 . ($table == 'pages' ? IconUtility::getSpriteIcon('actions-page-new') : IconUtility::getSpriteIcon('actions-document-new')) . '</a>';
1245 }
1246 }
1247 }
1248 // "Up/Down" links
1249 if ($permsEdit && $GLOBALS['TCA'][$table]['ctrl']['sortby'] && !$this->sortField && !$this->searchLevels) {
1250 if (isset($this->currentTable['prev'][$row['uid']])) {
1251 // Up
1252 $params = '&cmd[' . $table . '][' . $row['uid'] . '][move]=' . $this->currentTable['prev'][$row['uid']];
1253 $cells['moveUp'] = '<a class="btn" href="#" onclick="'
1254 . htmlspecialchars('return jumpToUrl(\'' . $GLOBALS['SOBE']->doc->issueCommand($params, -1) . '\');')
1255 . '" title="' . $GLOBALS['LANG']->getLL('moveUp', TRUE) . '">'
1256 . IconUtility::getSpriteIcon('actions-move-up') . '</a>';
1257 } else {
1258 $cells['moveUp'] = $this->spaceIcon;
1259 }
1260 if ($this->currentTable['next'][$row['uid']]) {
1261 // Down
1262 $params = '&cmd[' . $table . '][' . $row['uid'] . '][move]=' . $this->currentTable['next'][$row['uid']];
1263 $cells['moveDown'] = '<a class="btn" href="#" onclick="'
1264 . htmlspecialchars('return jumpToUrl(\'' . $GLOBALS['SOBE']->doc->issueCommand($params, -1) . '\');')
1265 . '" title="' . $GLOBALS['LANG']->getLL('moveDown', TRUE) . '">'
1266 . IconUtility::getSpriteIcon('actions-move-down') . '</a>';
1267 } else {
1268 $cells['moveDown'] = $this->spaceIcon;
1269 }
1270 }
1271 // "Hide/Unhide" links:
1272 $hiddenField = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'];
1273 if (
1274 $permsEdit && $hiddenField && $GLOBALS['TCA'][$table]['columns'][$hiddenField]
1275 && (!$GLOBALS['TCA'][$table]['columns'][$hiddenField]['exclude']
1276 || $GLOBALS['BE_USER']->check('non_exclude_fields', $table . ':' . $hiddenField))
1277 ) {
1278 if ($row[$hiddenField]) {
1279 $params = 'data[' . $table . '][' . $rowUid . '][' . $hiddenField . ']=0';
1280 $cells['hide'] = '<a class="btn t3js-record-hide" data-state="hidden" href="#"'
1281 . ' data-params="' . htmlspecialchars($params) . '"'
1282 . ' title="' . $GLOBALS['LANG']->getLL(('unHide' . ($table == 'pages' ? 'Page' : '')), TRUE) . '">'
1283 . IconUtility::getSpriteIcon('actions-edit-unhide') . '</a>';
1284 } else {
1285 $params = 'data[' . $table . '][' . $rowUid . '][' . $hiddenField . ']=1';
1286 $cells['hide'] = '<a class="btn t3js-record-hide" data-state="visible" href="#"'
1287 . ' data-params="' . htmlspecialchars($params) . '"'
1288 . ' title="' . $GLOBALS['LANG']->getLL(('hide' . ($table == 'pages' ? 'Page' : '')), TRUE) . '">'
1289 . IconUtility::getSpriteIcon('actions-edit-hide') . '</a>';
1290 }
1291 }
1292 // "Delete" link:
1293 if ($table == 'pages' && $localCalcPerms & 4 || $table != 'pages' && $this->calcPerms & 16) {
1294 // Check if the record version is in "deleted" state, because that will switch the action to "restore"
1295 if ($GLOBALS['BE_USER']->workspace > 0 && isset($row['t3ver_state']) && (int)$row['t3ver_state'] === 2) {
1296 $actionName = 'restore';
1297 $refCountMsg = '';
1298 } else {
1299 $actionName = 'delete';
1300 $refCountMsg = BackendUtility::referenceCount(
1301 $table,
1302 $row['uid'],
1303 ' ' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.referencesToRecord'),
1304 $this->getReferenceCount($table, $row['uid'])) . BackendUtility::translationCount($table, $row['uid'],
1305 ' ' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.translationsOfRecord')
1306 );
1307 }
1308
1309 $titleOrig = BackendUtility::getRecordTitle($table, $row, FALSE, TRUE);
1310 $title = GeneralUtility::fixed_lgd_cs($titleOrig, $this->fixedL);
1311 $warningText = $GLOBALS['LANG']->getLL($actionName . 'Warning') . ' "' . $title . '" ' . '[' . $table . ':' . $row['uid'] . ']' . $refCountMsg;
1312
1313 $params = 'cmd[' . $table . '][' . $row['uid'] . '][delete]=1';
1314 $icon = IconUtility::getSpriteIcon('actions-edit-' . $actionName);
1315 $linkTitle = $GLOBALS['LANG']->getLL($actionName, TRUE);
1316 $cells['delete'] = '<a class="btn t3js-record-delete" href="#" '
1317 . ' data-l10parent="' . htmlspecialchars($row['l10n_parent']) . '"'
1318 . ' data-params="' . htmlspecialchars($params) . '" data-title="' . htmlspecialchars($titleOrig) . '"'
1319 . ' data-message="' . htmlspecialchars($warningText) . '" title="' . $linkTitle . '"'
1320 . '>' . $icon . '</a>';
1321 }
1322 // "Levels" links: Moving pages into new levels...
1323 if ($permsEdit && $table == 'pages' && !$this->searchLevels) {
1324 // Up (Paste as the page right after the current parent page)
1325 if ($this->calcPerms & 8) {
1326 $params = '&cmd[' . $table . '][' . $row['uid'] . '][move]=' . -$this->id;
1327 $cells['moveLeft'] = '<a class="btn" href="#" onclick="'
1328 . htmlspecialchars('return jumpToUrl(\'' . $GLOBALS['SOBE']->doc->issueCommand($params, -1) . '\');')
1329 . '" title="' . $GLOBALS['LANG']->getLL('prevLevel', TRUE) . '">'
1330 . IconUtility::getSpriteIcon('actions-move-left') . '</a>';
1331 }
1332 // Down (Paste as subpage to the page right above)
1333 if ($this->currentTable['prevUid'][$row['uid']]) {
1334 $localCalcPerms = $GLOBALS['BE_USER']->calcPerms(BackendUtility::getRecord('pages', $this->currentTable['prevUid'][$row['uid']]));
1335 if ($localCalcPerms & 8) {
1336 $params = '&cmd[' . $table . '][' . $row['uid'] . '][move]=' . $this->currentTable['prevUid'][$row['uid']];
1337 $cells['moveRight'] = '<a class="btn" href="#" onclick="'
1338 . htmlspecialchars('return jumpToUrl(\'' . $GLOBALS['SOBE']->doc->issueCommand($params, -1) . '\');')
1339 . '" title="' . $GLOBALS['LANG']->getLL('nextLevel', TRUE) . '">'
1340 . IconUtility::getSpriteIcon('actions-move-right') . '</a>';
1341 } else {
1342 $cells['moveRight'] = $this->spaceIcon;
1343 }
1344 } else {
1345 $cells['moveRight'] = $this->spaceIcon;
1346 }
1347 }
1348 }
1349 }
1350 /**
1351 * @hook recStatInfoHooks: Allows to insert HTML before record icons on various places
1352 */
1353 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['recStatInfoHooks'])) {
1354 $stat = '';
1355 $_params = array($table, $row['uid']);
1356 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['recStatInfoHooks'] as $_funcRef) {
1357 $stat .= GeneralUtility::callUserFunction($_funcRef, $_params, $this);
1358 }
1359 $cells['stat'] = $stat;
1360 }
1361 /**
1362 * @hook makeControl: Allows to change control icons of records in list-module
1363 * @usage This hook method gets passed the current $cells array as third parameter.
1364 * This array contains values for the icons/actions generated for each record in Web>List.
1365 * Each array entry is accessible by an index-key.
1366 * The order of the icons is depending on the order of those array entries.
1367 */
1368 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['actions'])) {
1369 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['actions'] as $classData) {
1370 $hookObject = GeneralUtility::getUserObj($classData);
1371 if (!$hookObject instanceof \TYPO3\CMS\Recordlist\RecordList\RecordListHookInterface) {
1372 throw new \UnexpectedValueException('$hookObject must implement interface ' . \TYPO3\CMS\Recordlist\RecordList\RecordListHookInterface::class, 1195567840);
1373 }
1374 $cells = $hookObject->makeControl($table, $row, $cells, $this);
1375 }
1376 }
1377 // Compile items into a DIV-element:
1378 return '
1379 <!-- CONTROL PANEL: ' . $table . ':' . $row['uid'] . ' -->
1380 <div class="btn-group" role="group">' . implode('', $cells) . '</div>';
1381 }
1382
1383 /**
1384 * Creates the clipboard panel for a single record in the listing.
1385 *
1386 * @param string $table The table
1387 * @param array $row The record for which to make the clipboard panel.
1388 * @throws \UnexpectedValueException
1389 * @return string HTML table with the clipboard panel (unless disabled)
1390 */
1391 public function makeClip($table, $row) {
1392 // Return blank, if disabled:
1393 if ($this->dontShowClipControlPanels) {
1394 return '';
1395 }
1396 $cells = array();
1397 $cells['pasteAfter'] = ($cells['pasteInto'] = $this->spaceIcon);
1398 //enables to hide the copy, cut and paste icons for localized records - doesn't make much sense to perform these options for them
1399 $isL10nOverlay = $this->localizationView && $table != 'pages_language_overlay' && $row[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] != 0;
1400 // Return blank, if disabled:
1401 // Whether a numeric clipboard pad is active or the normal pad we will see different content of the panel:
1402 // For the "Normal" pad:
1403 if ($this->clipObj->current == 'normal') {
1404 // Show copy/cut icons:
1405 $isSel = (string)$this->clipObj->isSelected($table, $row['uid']);
1406 if ($isL10nOverlay) {
1407 $cells['copy'] = $this->spaceIcon;
1408 $cells['cut'] = $this->spaceIcon;
1409 } else {
1410 $cells['copy'] = '<a class="btn" href="#" onclick="'
1411 . htmlspecialchars('return jumpSelf(\'' . $this->clipObj->selUrlDB($table, $row['uid'], 1, ($isSel == 'copy'), array('returnUrl' => '')) . '\');')
1412 . '" title="' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:cm.copy', TRUE) . '">'
1413 . (!$isSel == 'copy' ? IconUtility::getSpriteIcon('actions-edit-copy') : IconUtility::getSpriteIcon('actions-edit-copy-release')) . '</a>';
1414 $cells['cut'] = '<a class="btn" href="#" onclick="'
1415 . htmlspecialchars('return jumpSelf(\'' . $this->clipObj->selUrlDB($table, $row['uid'], 0, ($isSel == 'cut'), array('returnUrl' => '')) . '\');')
1416 . '" title="' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:cm.cut', TRUE) . '">'
1417 . (!$isSel == 'cut' ? IconUtility::getSpriteIcon('actions-edit-cut') : IconUtility::getSpriteIcon('actions-edit-cut-release')) . '</a>';
1418 }
1419 } else {
1420 // For the numeric clipboard pads (showing checkboxes where one can select elements on/off)
1421 // Setting name of the element in ->CBnames array:
1422 $n = $table . '|' . $row['uid'];
1423 $this->CBnames[] = $n;
1424 // Check if the current element is selected and if so, prepare to set the checkbox as selected:
1425 $checked = $this->clipObj->isSelected($table, $row['uid']) ? ' checked="checked"' : '';
1426 // If the "duplicateField" value is set then select all elements which are duplicates...
1427 if ($this->duplicateField && isset($row[$this->duplicateField])) {
1428 $checked = '';
1429 if (in_array($row[$this->duplicateField], $this->duplicateStack)) {
1430 $checked = ' checked="checked"';
1431 }
1432 $this->duplicateStack[] = $row[$this->duplicateField];
1433 }
1434 // Adding the checkbox to the panel:
1435 $cells['select'] = $isL10nOverlay
1436 ? $this->spaceIcon
1437 : '<div class="btn-checkbox-holder"><input type="hidden" name="CBH[' . $n . ']" value="0" /><input type="checkbox"'
1438 . ' name="CBC[' . $n . ']" value="1" class="smallCheckboxes btn-checkbox"' . $checked . ' /><span class="btn"><span class="t3-icon fa"></span></span></div>';
1439 }
1440 // Now, looking for selected elements from the current table:
1441 $elFromTable = $this->clipObj->elFromTable($table);
1442 if (count($elFromTable) && $GLOBALS['TCA'][$table]['ctrl']['sortby']) {
1443 // IF elements are found and they can be individually ordered, then add a "paste after" icon:
1444 $cells['pasteAfter'] = $isL10nOverlay
1445 ? $this->spaceIcon
1446 : '<a class="btn" href="' . htmlspecialchars($this->clipObj->pasteUrl($table, -$row['uid'])) . '" onclick="'
1447 . htmlspecialchars(('return ' . $this->clipObj->confirmMsg($table, $row, 'after', $elFromTable)))
1448 . '" title="' . $GLOBALS['LANG']->getLL('clip_pasteAfter', TRUE) . '">'
1449 . IconUtility::getSpriteIcon('actions-document-paste-after') . '</a>';
1450 }
1451 // Now, looking for elements in general:
1452 $elFromTable = $this->clipObj->elFromTable('');
1453 if ($table == 'pages' && count($elFromTable)) {
1454 $cells['pasteInto'] = '<a class="btn" href="' . htmlspecialchars($this->clipObj->pasteUrl('', $row['uid']))
1455 . '" onclick="' . htmlspecialchars('return ' . $this->clipObj->confirmMsg($table, $row, 'into', $elFromTable))
1456 . '" title="' . $GLOBALS['LANG']->getLL('clip_pasteInto', TRUE) . '">'
1457 . IconUtility::getSpriteIcon('actions-document-paste-into') . '</a>';
1458 }
1459 /**
1460 * @hook makeClip: Allows to change clip-icons of records in list-module
1461 * @usage This hook method gets passed the current $cells array as third parameter.
1462 * This array contains values for the clipboard icons generated for each record in Web>List.
1463 * Each array entry is accessible by an index-key.
1464 * The order of the icons is depending on the order of those array entries.
1465 */
1466 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['actions'])) {
1467 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['actions'] as $classData) {
1468 $hookObject = GeneralUtility::getUserObj($classData);
1469 if (!$hookObject instanceof \TYPO3\CMS\Recordlist\RecordList\RecordListHookInterface) {
1470 throw new \UnexpectedValueException('$hookObject must implement interface ' . \TYPO3\CMS\Recordlist\RecordList\RecordListHookInterface::class, 1195567845);
1471 }
1472 $cells = $hookObject->makeClip($table, $row, $cells, $this);
1473 }
1474 }
1475 // Compile items into a DIV-element:
1476 return ' <!-- CLIPBOARD PANEL: ' . $table . ':' . $row['uid'] . ' -->
1477 <div class="btn-group" role="group">' . implode('', $cells) . '</div>';
1478 }
1479
1480 /**
1481 * Creates the HTML for a reference count for the record with the UID $uid
1482 * in the table $tableName.
1483 *
1484 * @param string $tableName
1485 * @param int $uid
1486 * @return string HTML of reference a link, will be empty if there are no
1487 */
1488 protected function createReferenceHtml($tableName, $uid) {
1489 $referenceCount = $this->getDatabaseConnection()->exec_SELECTcountRows(
1490 '*',
1491 'sys_refindex',
1492 'ref_table = ' . $this->getDatabaseConnection()->fullQuoteStr($tableName, 'sys_refindex') .
1493 ' AND ref_uid = ' . $uid . ' AND deleted = 0'
1494 );
1495 return $this->generateReferenceToolTip($referenceCount, '\'' . $tableName . '\', \'' . $uid . '\'');
1496 }
1497
1498 /**
1499 * Creates the localization panel
1500 *
1501 * @param string $table The table
1502 * @param array $row The record for which to make the localization panel.
1503 * @return array Array with key 0/1 with content for column 1 and 2
1504 */
1505 public function makeLocalizationPanel($table, $row) {
1506 $out = array(
1507 0 => '',
1508 1 => ''
1509 );
1510 // Reset translations
1511 $this->translations = array();
1512 $translations = $this->translateTools->translationInfo($table, $row['uid'], 0, $row, $this->selFieldList);
1513 // Language title and icon:
1514 $out[0] = $this->languageFlag($row[$GLOBALS['TCA'][$table]['ctrl']['languageField']]);
1515 if (is_array($translations)) {
1516 $this->translations = $translations['translations'];
1517 // Traverse page translations and add icon for each language that does NOT yet exist:
1518 $lNew = '';
1519 foreach ($this->pageOverlays as $lUid_OnPage => $lsysRec) {
1520 if (!isset($translations['translations'][$lUid_OnPage]) && $GLOBALS['BE_USER']->checkLanguageAccess($lUid_OnPage)) {
1521 $url = substr($this->listURL(), strlen($this->backPath));
1522 $href = $GLOBALS['SOBE']->doc->issueCommand('&cmd[' . $table . '][' . $row['uid'] . '][localize]='
1523 . $lUid_OnPage, $url . '&justLocalized=' . rawurlencode($table . ':' . $row['uid'] . ':' . $lUid_OnPage));
1524 $language = BackendUtility::getRecord('sys_language', $lUid_OnPage, 'title');
1525 if ($this->languageIconTitles[$lUid_OnPage]['flagIcon']) {
1526 $lC = IconUtility::getSpriteIcon($this->languageIconTitles[$lUid_OnPage]['flagIcon']);
1527 } else {
1528 $lC = $this->languageIconTitles[$lUid_OnPage]['title'];
1529 }
1530 $lC = '<a href="' . htmlspecialchars($href) . '" title="'
1531 . htmlspecialchars($language['title']) . '">' . $lC . '</a> ';
1532 $lNew .= $lC;
1533 }
1534 }
1535 if ($lNew) {
1536 $out[1] .= $lNew;
1537 }
1538 } elseif ($row['l18n_parent']) {
1539 $out[0] = '&nbsp;&nbsp;&nbsp;&nbsp;' . $out[0];
1540 }
1541 return $out;
1542 }
1543
1544 /**
1545 * Create the selector box for selecting fields to display from a table:
1546 *
1547 * @param string $table Table name
1548 * @param bool $formFields If TRUE, form-fields will be wrapped around the table.
1549 * @return string HTML table with the selector box (name: displayFields['.$table.'][])
1550 */
1551 public function fieldSelectBox($table, $formFields = TRUE) {
1552 // Init:
1553 $formElements = array('', '');
1554 if ($formFields) {
1555 $formElements = array('<form action="' . htmlspecialchars($this->listURL()) . '" method="post">', '</form>');
1556 }
1557 // Load already selected fields, if any:
1558 $setFields = is_array($this->setFields[$table]) ? $this->setFields[$table] : array();
1559 // Request fields from table:
1560 $fields = $this->makeFieldList($table, FALSE, TRUE);
1561 // Add pseudo "control" fields
1562 $fields[] = '_PATH_';
1563 $fields[] = '_REF_';
1564 $fields[] = '_LOCALIZATION_';
1565 $fields[] = '_CONTROL_';
1566 $fields[] = '_CLIPBOARD_';
1567 // Create an option for each field:
1568 $opt = array();
1569 $opt[] = '<option value=""></option>';
1570 foreach ($fields as $fN) {
1571 // Field label
1572 $fL = is_array($GLOBALS['TCA'][$table]['columns'][$fN])
1573 ? rtrim($GLOBALS['LANG']->sL($GLOBALS['TCA'][$table]['columns'][$fN]['label']), ':')
1574 : '[' . $fN . ']';
1575 $opt[] = '
1576 <option value="' . $fN . '"'
1577 . (in_array($fN, $setFields) ? ' selected="selected"' : '') . '>' . htmlspecialchars($fL) . '</option>';
1578 }
1579 // Compile the options into a multiple selector box:
1580 $lMenu = '
1581 <select size="'
1582 . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange(count($fields) + 1, 3, 20)
1583 . '" multiple="multiple" name="displayFields[' . $table . '][]">' . implode('', $opt) . '
1584 </select>
1585 ';
1586 // Table with the field selector::
1587 $content = $formElements[0] . '
1588
1589 <!--
1590 Field selector for extended table view:
1591 -->
1592 <table border="0" cellpadding="0" cellspacing="0" id="typo3-dblist-fieldSelect">
1593 <tr>
1594 <td>' . $lMenu . '</td>
1595 <td><input type="submit" name="search" value="'
1596 . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.setFields', TRUE) . '" /></td>
1597 </tr>
1598 </table>
1599 ' . $formElements[1];
1600 return '<div class="db_list-fieldSelect">' . $content . '</div>';
1601 }
1602
1603 /*********************************
1604 *
1605 * Helper functions
1606 *
1607 *********************************/
1608 /**
1609 * Creates a link around $string. The link contains an onclick action
1610 * which submits the script with some clipboard action.
1611 * Currently, this is used for setting elements / delete elements.
1612 *
1613 * @param string $string The HTML content to link (image/text)
1614 * @param string $table Table name
1615 * @param string $cmd Clipboard command (eg. "setCB" or "delete")
1616 * @param string $warning Warning text, if any ("delete" uses this for confirmation)
1617 * @return string <a> tag wrapped link.
1618 */
1619 public function linkClipboardHeaderIcon($string, $table, $cmd, $warning = '') {
1620 $onClickEvent = 'document.dblistForm.cmd.value=\'' . $cmd . '\';document.dblistForm.cmd_table.value=\''
1621 . $table . '\';document.dblistForm.submit();';
1622 if ($warning) {
1623 $onClickEvent = 'if (confirm(' . GeneralUtility::quoteJSvalue($warning) . ')){' . $onClickEvent . '}';
1624 }
1625 return '<a class="btn" href="#" onclick="' . htmlspecialchars(($onClickEvent . 'return false;')) . '">' . $string . '</a>';
1626 }
1627
1628 /**
1629 * Returns TRUE if a numeric clipboard pad is selected/active
1630 *
1631 * @return bool
1632 */
1633 public function clipNumPane() {
1634 return in_Array('_CLIPBOARD_', $this->fieldArray) && $this->clipObj->current != 'normal';
1635 }
1636
1637 /**
1638 * Creates a sort-by link on the input string ($code).
1639 * It will automatically detect if sorting should be ascending or descending depending on $this->sortRev.
1640 * Also some fields will not be possible to sort (including if single-table-view is disabled).
1641 *
1642 * @param string $code The string to link (text)
1643 * @param string $field The fieldname represented by the title ($code)
1644 * @param string $table Table name
1645 * @return string Linked $code variable
1646 */
1647 public function addSortLink($code, $field, $table) {
1648 // Certain circumstances just return string right away (no links):
1649 if ($field == '_CONTROL_' || $field == '_LOCALIZATION_' || $field == '_CLIPBOARD_' || $field == '_REF_' || $this->disableSingleTableView) {
1650 return $code;
1651 }
1652 // If "_PATH_" (showing record path) is selected, force sorting by pid field (will at least group the records!)
1653 if ($field == '_PATH_') {
1654 $field = 'pid';
1655 }
1656 // Create the sort link:
1657 $sortUrl = $this->listURL('', -1, 'sortField,sortRev,table,firstElementNumber') . '&table=' . $table
1658 . '&sortField=' . $field . '&sortRev=' . ($this->sortRev || $this->sortField != $field ? 0 : 1);
1659 $sortArrow = $this->sortField === $field
1660 ? IconUtility::getSpriteIcon('status-status-sorting-' . ($this->sortRev ? 'desc' : 'asc'))
1661 : '';
1662 // Return linked field:
1663 return '<a href="' . htmlspecialchars($sortUrl) . '">' . $code . $sortArrow . '</a>';
1664 }
1665
1666 /**
1667 * Returns the path for a certain pid
1668 * The result is cached internally for the session, thus you can call
1669 * this function as much as you like without performance problems.
1670 *
1671 * @param int $pid The page id for which to get the path
1672 * @return string The path.
1673 */
1674 public function recPath($pid) {
1675 if (!isset($this->recPath_cache[$pid])) {
1676 $this->recPath_cache[$pid] = BackendUtility::getRecordPath($pid, $this->perms_clause, 20);
1677 }
1678 return $this->recPath_cache[$pid];
1679 }
1680
1681 /**
1682 * Returns TRUE if a link for creating new records should be displayed for $table
1683 *
1684 * @param string $table Table name
1685 * @return bool Returns TRUE if a link for creating new records should be displayed for $table
1686 * @see \TYPO3\CMS\Backend\Controller\NewRecordController::showNewRecLink
1687 */
1688 public function showNewRecLink($table) {
1689 // No deny/allow tables are set:
1690 if (!count($this->allowedNewTables) && !count($this->deniedNewTables)) {
1691 return TRUE;
1692 }
1693 return !in_array($table, $this->deniedNewTables)
1694 && (!count($this->allowedNewTables) || in_array($table, $this->allowedNewTables));
1695 }
1696
1697 /**
1698 * Creates the "&returnUrl" parameter for links - this is used when the script links
1699 * to other scripts and passes its own URL with the link so other scripts can return to the listing again.
1700 * Uses REQUEST_URI as value.
1701 *
1702 * @return string
1703 */
1704 public function makeReturnUrl() {
1705 return '&returnUrl=' . rawurlencode(GeneralUtility::getIndpEnv('REQUEST_URI'));
1706 }
1707
1708 /************************************
1709 *
1710 * CSV related functions
1711 *
1712 ************************************/
1713 /**
1714 * Initializes internal csvLines array with the header of field names
1715 *
1716 * @return void
1717 */
1718 protected function initCSV() {
1719 $this->addHeaderRowToCSV();
1720 }
1721
1722 /**
1723 * Add header line with field names as CSV line
1724 *
1725 * @return void
1726 */
1727 protected function addHeaderRowToCSV() {
1728 // Add header row, control fields will be reduced inside addToCSV()
1729 $this->addToCSV(array_combine($this->fieldArray, $this->fieldArray));
1730 }
1731
1732 /**
1733 * Adds selected columns of one table row as CSV line.
1734 *
1735 * @param array $row Record array, from which the values of fields found in $this->fieldArray will be listed in the CSV output.
1736 * @return void
1737 */
1738 protected function addToCSV(array $row = array()) {
1739 $rowReducedByControlFields = self::removeControlFieldsFromFieldRow($row);
1740 $rowReducedToSelectedColumns = array_intersect_key($rowReducedByControlFields, array_flip($this->fieldArray));
1741 $this->setCsvRow($rowReducedToSelectedColumns);
1742 }
1743
1744 /**
1745 * Remove control fields from row for CSV export
1746 *
1747 * @param array $row fieldNames => fieldValues
1748 * @return array Input array reduces by control fields
1749 */
1750 static protected function removeControlFieldsFromFieldRow(array $row = array()) {
1751 // Possible control fields in a list row
1752 $controlFields = array(
1753 '_PATH_',
1754 '_REF_',
1755 '_CONTROL_',
1756 '_CLIPBOARD_',
1757 '_LOCALIZATION_',
1758 '_LOCALIZATION_b'
1759 );
1760 return array_diff_key($row, array_flip($controlFields));
1761 }
1762
1763 /**
1764 * Adds input row of values to the internal csvLines array as a CSV formatted line
1765 *
1766 * @param array $csvRow Array with values to be listed.
1767 * @return void
1768 */
1769 public function setCsvRow($csvRow) {
1770 $this->csvLines[] = GeneralUtility::csvValues($csvRow);
1771 }
1772
1773 /**
1774 * Compiles the internal csvLines array to a csv-string and outputs it to the browser.
1775 * This function exits!
1776 *
1777 * @param string $prefix Filename prefix:
1778 * @return void EXITS php execution!
1779 */
1780 public function outputCSV($prefix) {
1781 // Setting filename:
1782 $filename = $prefix . '_' . date('dmy-Hi') . '.csv';
1783 // Creating output header:
1784 header('Content-Type: application/octet-stream');
1785 header('Content-Disposition: attachment; filename=' . $filename);
1786 // Cache-Control header is needed here to solve an issue with browser IE and
1787 // versions lower than 9. See for more information: http://support.microsoft.com/kb/323308
1788 header("Cache-Control: ''");
1789 // Printing the content of the CSV lines:
1790 echo implode(CRLF, $this->csvLines);
1791 // Exits:
1792 die;
1793 }
1794
1795 /**
1796 * Returns the database connection
1797 * @return DatabaseConnection
1798 */
1799 protected function getDatabaseConnection() {
1800 return $GLOBALS['TYPO3_DB'];
1801 }
1802
1803 }