e426cec5357bd538e7440d352e996364ff4de6b9
[Packages/TYPO3.CMS.git] / typo3 / sysext / recordlist / Classes / Browser / FileBrowser.php
1 <?php
2 namespace TYPO3\CMS\Recordlist\Browser;
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\Tree\View\ElementBrowserFolderTreeView;
18 use TYPO3\CMS\Backend\Utility\BackendUtility;
19 use TYPO3\CMS\Core\Imaging\Icon;
20 use TYPO3\CMS\Core\Resource\Exception;
21 use TYPO3\CMS\Core\Resource\File;
22 use TYPO3\CMS\Core\Resource\FileInterface;
23 use TYPO3\CMS\Core\Resource\FileRepository;
24 use TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter;
25 use TYPO3\CMS\Core\Resource\Folder;
26 use TYPO3\CMS\Core\Resource\ProcessedFile;
27 use TYPO3\CMS\Core\Resource\ResourceFactory;
28 use TYPO3\CMS\Core\Utility\GeneralUtility;
29 use TYPO3\CMS\Recordlist\Tree\View\LinkParameterProviderInterface;
30 use TYPO3\CMS\Recordlist\View\FolderUtilityRenderer;
31
32 /**
33 * Browser for files
34 */
35 class FileBrowser extends AbstractElementBrowser implements ElementBrowserInterface, LinkParameterProviderInterface
36 {
37 /**
38 * When you click a folder name/expand icon to see the content of a certain file folder,
39 * this value will contain the path of the expanded file folder.
40 * If the value is NOT set, then it will be restored from the module session data.
41 * Example value: "/www/htdocs/typo3/32/3dsplm/fileadmin/css/"
42 *
43 * @var string|NULL
44 */
45 protected $expandFolder;
46
47 /**
48 * @var Folder
49 */
50 protected $selectedFolder;
51
52 /**
53 * Holds information about files
54 *
55 * @var mixed[][]
56 */
57 protected $elements = [];
58
59 /**
60 * @var string
61 */
62 protected $searchWord;
63
64 /**
65 * @var FileRepository
66 */
67 protected $fileRepository;
68
69 /**
70 * Loads additional JavaScript
71 */
72 protected function initialize()
73 {
74 parent::initialize();
75 $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Recordlist/BrowseFiles');
76 $this->fileRepository = GeneralUtility::makeInstance(FileRepository::class);
77 }
78
79 /**
80 * Checks additional GET/POST requests
81 */
82 protected function initVariables()
83 {
84 parent::initVariables();
85 $this->expandFolder = GeneralUtility::_GP('expandFolder');
86 $this->searchWord = (string)GeneralUtility::_GP('searchWord');
87 }
88
89 /**
90 * Session data for this class can be set from outside with this method.
91 *
92 * @param mixed[] $data Session data array
93 * @return array[] Session data and boolean which indicates that data needs to be stored in session because it's changed
94 */
95 public function processSessionData($data)
96 {
97 if ($this->expandFolder !== null) {
98 $data['expandFolder'] = $this->expandFolder;
99 $store = true;
100 } else {
101 $this->expandFolder = $data['expandFolder'];
102 $store = false;
103 }
104 return [$data, $store];
105 }
106
107 /**
108 * @return string HTML content
109 */
110 public function render()
111 {
112 $backendUser = $this->getBackendUser();
113
114 // The key number 3 of the bparams contains the "allowed" string. Disallowed is not passed to
115 // the element browser at all but only filtered out in DataHandler afterwards
116 $allowedFileExtensions = GeneralUtility::trimExplode(',', explode('|', $this->bparams)[3], true);
117 if (!empty($allowedFileExtensions) && $allowedFileExtensions[0] !== 'sys_file' && $allowedFileExtensions[0] !== '*') {
118 // Create new filter object
119 $filterObject = GeneralUtility::makeInstance(FileExtensionFilter::class);
120 $filterObject->setAllowedFileExtensions($allowedFileExtensions);
121 // Set file extension filters on all storages
122 $storages = $backendUser->getFileStorages();
123 /** @var $storage \TYPO3\CMS\Core\Resource\ResourceStorage */
124 foreach ($storages as $storage) {
125 $storage->addFileAndFolderNameFilter([$filterObject, 'filterFileList']);
126 }
127 }
128 if ($this->expandFolder) {
129 $fileOrFolderObject = null;
130
131 // Try to fetch the folder the user had open the last time he browsed files
132 // Fallback to the default folder in case the last used folder is not existing
133 try {
134 $fileOrFolderObject = ResourceFactory::getInstance()->retrieveFileOrFolderObject($this->expandFolder);
135 } catch (Exception $accessException) {
136 // We're just catching the exception here, nothing to be done if folder does not exist or is not accessible.
137 } catch (\InvalidArgumentException $driverMissingExecption) {
138 // We're just catching the exception here, nothing to be done if the driver does not exist anymore.
139 }
140
141 if ($fileOrFolderObject instanceof Folder) {
142 // It's a folder
143 $this->selectedFolder = $fileOrFolderObject;
144 } elseif ($fileOrFolderObject instanceof FileInterface) {
145 // It's a file
146 $this->selectedFolder = $fileOrFolderObject->getParentFolder();
147 }
148 }
149 // Or get the user's default upload folder
150 if (!$this->selectedFolder) {
151 try {
152 $this->selectedFolder = $backendUser->getDefaultUploadFolder();
153 } catch (\Exception $e) {
154 // The configured default user folder does not exist
155 }
156 }
157 // Build the file upload and folder creation form
158 $uploadForm = '';
159 $createFolder = '';
160 if ($this->selectedFolder) {
161 $folderUtilityRenderer = GeneralUtility::makeInstance(FolderUtilityRenderer::class, $this);
162 $uploadForm = $folderUtilityRenderer->uploadForm($this->selectedFolder, $allowedFileExtensions);
163 $createFolder = $folderUtilityRenderer->createFolder($this->selectedFolder);
164 }
165
166 // Getting flag for showing/not showing thumbnails:
167 $noThumbs = $backendUser->getTSConfigVal('options.noThumbsInEB');
168 $_MOD_SETTINGS = [];
169 if (!$noThumbs) {
170 // MENU-ITEMS, fetching the setting for thumbnails from File>List module:
171 $_MOD_MENU = ['displayThumbs' => ''];
172 $_MCONF['name'] = 'file_list';
173 $_MOD_SETTINGS = BackendUtility::getModuleData($_MOD_MENU, GeneralUtility::_GP('SET'), $_MCONF['name']);
174 }
175 $noThumbs = $noThumbs ?: !$_MOD_SETTINGS['displayThumbs'];
176 // Create folder tree:
177 /** @var ElementBrowserFolderTreeView $folderTree */
178 $folderTree = GeneralUtility::makeInstance(ElementBrowserFolderTreeView::class);
179 $folderTree->setLinkParameterProvider($this);
180 $tree = $folderTree->getBrowsableTree();
181 if ($this->selectedFolder) {
182 $files = $this->renderFilesInFolder($this->selectedFolder, $allowedFileExtensions, $noThumbs);
183 } else {
184 $files = '';
185 }
186
187 $this->initDocumentTemplate();
188 // Starting content:
189 $content = $this->doc->startPage(htmlspecialchars($this->getLanguageService()->getLL('fileSelector')));
190
191 // Putting the parts together, side by side:
192 $markup = [];
193 $markup[] = '<!-- Wrapper table for folder tree / filelist: -->';
194 $markup[] = '<div class="element-browser">';
195 $markup[] = ' <div class="element-browser-panel element-browser-main">';
196 $markup[] = ' <div class="element-browser-main-sidebar">';
197 $markup[] = ' <div class="element-browser-body">';
198 $markup[] = ' <h3>' . htmlspecialchars($this->getLanguageService()->getLL('folderTree')) . ':</h3>';
199 $markup[] = ' ' . $tree;
200 $markup[] = ' </div>';
201 $markup[] = ' </div>';
202 $markup[] = ' <div class="element-browser-main-content">';
203 $markup[] = ' <div class="element-browser-body">';
204 $markup[] = ' ' . $this->doc->getFlashMessages();
205 $markup[] = ' ' . $files;
206 $markup[] = ' ' . $uploadForm;
207 $markup[] = ' ' . $createFolder;
208 $markup[] = ' </div>';
209 $markup[] = ' </div>';
210 $markup[] = ' </div>';
211 $markup[] = '</div>';
212 $content .= implode('', $markup);
213
214 // Ending page, returning content:
215 $content .= $this->doc->endPage();
216 return $this->doc->insertStylesAndJS($content);
217 }
218
219 /**
220 * For TYPO3 Element Browser: Expand folder of files.
221 *
222 * @param Folder $folder The folder path to expand
223 * @param array $extensionList List of fileextensions to show
224 * @param bool $noThumbs Whether to show thumbnails or not. If set, no thumbnails are shown.
225 * @return string HTML output
226 */
227 public function renderFilesInFolder(Folder $folder, array $extensionList = [], $noThumbs = false)
228 {
229 if (!$folder->checkActionPermission('read')) {
230 return '';
231 }
232 $lang = $this->getLanguageService();
233 $titleLen = (int)$this->getBackendUser()->uc['titleLen'];
234
235 if ($this->searchWord !== '') {
236 $files = $this->fileRepository->searchByName($folder, $this->searchWord);
237 } else {
238 $extensionList = !empty($extensionList) && $extensionList[0] === '*' ? [] : $extensionList;
239 $files = $this->getFilesInFolder($folder, $extensionList);
240 }
241 $filesCount = count($files);
242
243 $lines = [];
244
245 // Create the header of current folder:
246 $folderIcon = $this->iconFactory->getIconForResource($folder, Icon::SIZE_SMALL);
247
248 $lines[] = '
249 <tr>
250 <th class="col-title" nowrap="nowrap">' . $folderIcon . ' ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($folder->getIdentifier(), $titleLen)) . '</th>
251 <th class="col-control" nowrap="nowrap"></th>
252 <th class="col-clipboard" nowrap="nowrap">
253 <a href="#" class="btn btn-default" id="t3js-importSelection" title="' . htmlspecialchars($lang->getLL('importSelection')) . '">' . $this->iconFactory->getIcon('actions-document-import-t3d', Icon::SIZE_SMALL) . '</a>
254 <a href="#" class="btn btn-default" id="t3js-toggleSelection" title="' . htmlspecialchars($lang->getLL('toggleSelection')) . '">' . $this->iconFactory->getIcon('actions-document-select', Icon::SIZE_SMALL) . '</a>
255 </th>
256 <th nowrap="nowrap">&nbsp;</th>
257 </tr>';
258
259 if ($filesCount === 0) {
260 $lines[] = '
261 <tr>
262 <td colspan="4">No files found.</td>
263 </tr>';
264 }
265
266 foreach ($files as $fileObject) {
267 $fileExtension = $fileObject->getExtension();
268 // Thumbnail/size generation:
269 $imgInfo = [];
270 if (!$noThumbs && GeneralUtility::inList(strtolower($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] . ',' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext']), strtolower($fileExtension))) {
271 $processedFile = $fileObject->process(
272 ProcessedFile::CONTEXT_IMAGEPREVIEW,
273 ['width' => 64, 'height' => 64]
274 );
275 $imageUrl = $processedFile->getPublicUrl(true);
276 $imgInfo = [
277 $fileObject->getProperty('width'),
278 $fileObject->getProperty('height')
279 ];
280 $pDim = $imgInfo[0] . 'x' . $imgInfo[1] . ' pixels';
281 $clickIcon = '<img src="' . $imageUrl . '"'
282 . ' width="' . $processedFile->getProperty('width') . '"'
283 . ' height="' . $processedFile->getProperty('height') . '"'
284 . ' hspace="5" vspace="5" border="1" />';
285 } else {
286 $clickIcon = '';
287 $pDim = '';
288 }
289 // Create file icon:
290 $size = ' (' . GeneralUtility::formatSize($fileObject->getSize(), $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:byteSizeUnits')) . ($pDim ? ', ' . $pDim : '') . ')';
291 $icon = '<span title="' . htmlspecialchars($fileObject->getName() . $size) . '">' . $this->iconFactory->getIconForResource($fileObject, Icon::SIZE_SMALL) . '</span>';
292 // Create links for adding the file:
293 $filesIndex = count($this->elements);
294 $this->elements['file_' . $filesIndex] = [
295 'type' => 'file',
296 'table' => 'sys_file',
297 'uid' => $fileObject->getUid(),
298 'fileName' => $fileObject->getName(),
299 'filePath' => $fileObject->getUid(),
300 'fileExt' => $fileExtension,
301 'fileIcon' => $icon
302 ];
303 if ($this->fileIsSelectableInFileList($fileObject, $imgInfo)) {
304 $ATag = '<a href="#" class="btn btn-default" title="' . htmlspecialchars($fileObject->getName()) . '" data-file-index="' . htmlspecialchars($filesIndex) . '" data-close="0">';
305 $ATag .= '<span title="' . htmlspecialchars($lang->getLL('addToList')) . '">' . $this->iconFactory->getIcon('actions-edit-add', Icon::SIZE_SMALL)->render() . '</span>';
306 $ATag_alt = '<a href="#" title="' . htmlspecialchars($fileObject->getName()) . '" data-file-index="' . htmlspecialchars($filesIndex) . '" data-close="1">';
307 $ATag_e = '</a>';
308 $bulkCheckBox = '<label class="btn btn-default btn-checkbox"><input type="checkbox" class="typo3-bulk-item" name="file_' . $filesIndex . '" value="0" /><span class="t3-icon fa"></span></label>';
309 } else {
310 $ATag = '';
311 $ATag_alt = '';
312 $ATag_e = '';
313 $bulkCheckBox = '';
314 }
315 // Create link to showing details about the file in a window:
316 $Ahref = BackendUtility::getModuleUrl('show_item', [
317 'type' => 'file',
318 'table' => '_FILE',
319 'uid' => $fileObject->getCombinedIdentifier(),
320 'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
321 ]);
322
323 // Combine the stuff:
324 $filenameAndIcon = $ATag_alt . $icon . htmlspecialchars(GeneralUtility::fixed_lgd_cs($fileObject->getName(), $titleLen)) . $ATag_e;
325 // Show element:
326 $lines[] = '
327 <tr class="file_list_normal">
328 <td class="col-title" nowrap="nowrap">' . $filenameAndIcon . '&nbsp;</td>
329 <td class="col-control">
330 <div class="btn-group">' . $ATag . $ATag_e . '
331 <a href="' . htmlspecialchars($Ahref) . '" class="btn btn-default" title="' . htmlspecialchars($lang->getLL('info')) . '">' . $this->iconFactory->getIcon('actions-document-info', Icon::SIZE_SMALL) . '</a>
332 </td>
333 <td class="col-clipboard" valign="top">' . $bulkCheckBox . '</td>
334 <td nowrap="nowrap">&nbsp;' . $pDim . '</td>
335 </tr>';
336 if ($pDim) {
337 $lines[] = '
338 <tr>
339 <td class="filelistThumbnail" colspan="4">' . $ATag_alt . $clickIcon . $ATag_e . '</td>
340 </tr>';
341 }
342 }
343
344 $markup = [];
345 $markup[] = '<h3>' . htmlspecialchars($lang->getLL('files')) . ' ' . $filesCount . ':</h3>';
346 $markup[] = GeneralUtility::makeInstance(FolderUtilityRenderer::class, $this)->getFileSearchField($this->searchWord);
347 $markup[] = '<div id="filelist">';
348 $markup[] = ' ' . $this->getBulkSelector($filesCount);
349 $markup[] = ' <!-- Filelisting -->';
350 $markup[] = ' <div class="table-fit">';
351 $markup[] = ' <table class="table table-striped table-hover" id="typo3-filelist">';
352 $markup[] = ' ' . implode('', $lines);
353 $markup[] = ' </table>';
354 $markup[] = ' </div>';
355 $markup[] = ' </div>';
356 $content = implode('', $markup);
357
358 return $content;
359 }
360
361 /**
362 * Get a list of Files in a folder filtered by extension
363 *
364 * @param Folder $folder
365 * @param array $extensionList
366 * @return File[]
367 */
368 protected function getFilesInFolder(Folder $folder, array $extensionList)
369 {
370 if (!empty($extensionList)) {
371 /** @var FileExtensionFilter $filter */
372 $filter = GeneralUtility::makeInstance(FileExtensionFilter::class);
373 $filter->setAllowedFileExtensions($extensionList);
374 $folder->setFileAndFolderNameFilters([[$filter, 'filterFileList']]);
375 }
376 return $folder->getFiles();
377 }
378
379 /**
380 * Get the HTML data required for a bulk selection of files of the TYPO3 Element Browser.
381 *
382 * @param int $filesCount Number of files currently displayed
383 * @return string HTML data required for a bulk selection of files - if $filesCount is 0, nothing is returned
384 */
385 protected function getBulkSelector($filesCount)
386 {
387 if (!$filesCount) {
388 return '';
389 }
390
391 $lang = $this->getLanguageService();
392 $out = '';
393
394 // Getting flag for showing/not showing thumbnails:
395 $noThumbsInEB = $this->getBackendUser()->getTSConfigVal('options.noThumbsInEB');
396 if (!$noThumbsInEB && $this->selectedFolder) {
397 // MENU-ITEMS, fetching the setting for thumbnails from File>List module:
398 $_MOD_MENU = ['displayThumbs' => ''];
399 $_MCONF['name'] = 'file_list';
400 $_MOD_SETTINGS = BackendUtility::getModuleData($_MOD_MENU, GeneralUtility::_GP('SET'), $_MCONF['name']);
401 $addParams = GeneralUtility::implodeArrayForUrl('', $this->getUrlParameters(['identifier' => $this->selectedFolder->getCombinedIdentifier()]));
402 $thumbNailCheck = '<div class="checkbox" style="padding:5px 0 15px 0"><label for="checkDisplayThumbs">'
403 . BackendUtility::getFuncCheck(
404 '',
405 'SET[displayThumbs]',
406 $_MOD_SETTINGS['displayThumbs'],
407 $this->thisScript,
408 $addParams,
409 'id="checkDisplayThumbs"'
410 )
411 . htmlspecialchars($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_file_list.xlf:displayThumbs')) . '</label></div>';
412 $out .= $thumbNailCheck;
413 } else {
414 $out .= '<div style="padding-top: 15px;"></div>';
415 }
416 return $out;
417 }
418
419 /**
420 * Checks if the given file is selectable in the filelist.
421 *
422 * By default all files are selectable. This method may be overwritten in child classes.
423 *
424 * @param FileInterface $file
425 * @param mixed[] $imgInfo Image dimensions from \TYPO3\CMS\Core\Imaging\GraphicalFunctions::getImageDimensions()
426 * @return bool TRUE if file is selectable.
427 */
428 protected function fileIsSelectableInFileList(FileInterface $file, array $imgInfo)
429 {
430 return true;
431 }
432
433 /**
434 * @return string[] Array of body-tag attributes
435 */
436 protected function getBodyTagAttributes()
437 {
438 return [
439 'data-mode' => 'file',
440 'data-elements' => json_encode($this->elements)
441 ];
442 }
443
444 /**
445 * @param array $values Array of values to include into the parameters
446 * @return string[] Array of parameters which have to be added to URLs
447 */
448 public function getUrlParameters(array $values)
449 {
450 return [
451 'mode' => 'file',
452 'expandFolder' => isset($values['identifier']) ? $values['identifier'] : $this->expandFolder,
453 'bparams' => $this->bparams
454 ];
455 }
456
457 /**
458 * @param array $values Values to be checked
459 * @return bool Returns TRUE if the given values match the currently selected item
460 */
461 public function isCurrentlySelectedItem(array $values)
462 {
463 return false;
464 }
465
466 /**
467 * Returns the URL of the current script
468 *
469 * @return string
470 */
471 public function getScriptUrl()
472 {
473 return $this->thisScript;
474 }
475 }