[BUGFIX] Catch some FAL exceptions for missing driver
[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 = array();
58
59 /**
60 * @var string
61 */
62 protected $searchWord;
63
64 /**
65 * @var FileRepository
66 */
67 protected $fileRepository;
68
69 /**
70 * @return void
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 * @return void
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 array($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 TCEMain afterwards
116 $allowedFileExtensions = explode('|', $this->bparams)[3];
117 if (!empty($allowedFileExtensions) && $allowedFileExtensions !== 'sys_file' && $allowedFileExtensions !== '*') {
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(array($filterObject, 'filterFileList'));
126 }
127 }
128 // Create upload/create folder forms, if a path is given
129 if ($this->expandFolder) {
130 $fileOrFolderObject = null;
131
132 // Try to fetch the folder the user had open the last time he browsed files
133 // Fallback to the default folder in case the last used folder is not existing
134 try {
135 $fileOrFolderObject = ResourceFactory::getInstance()->retrieveFileOrFolderObject($this->expandFolder);
136 } catch (Exception $accessException) {
137 // We're just catching the exception here, nothing to be done if folder does not exist or is not accessible.
138 } catch (\InvalidArgumentException $driverMissingExecption) {
139 // We're just catching the exception here, nothing to be done if the driver does not exist anymore.
140 }
141
142 if ($fileOrFolderObject instanceof Folder) {
143 // It's a folder
144 $this->selectedFolder = $fileOrFolderObject;
145 } elseif ($fileOrFolderObject instanceof FileInterface) {
146 // It's a file
147 $this->selectedFolder = $fileOrFolderObject->getParentFolder();
148 }
149 }
150 // Or get the user's default upload folder
151 if (!$this->selectedFolder) {
152 try {
153 $this->selectedFolder = $backendUser->getDefaultUploadFolder();
154 } catch (\Exception $e) {
155 // The configured default user folder does not exist
156 }
157 }
158 // Build the file upload and folder creation form
159 $uploadForm = '';
160 $createFolder = '';
161 if ($this->selectedFolder) {
162 $pArr = explode('|', $this->bparams);
163 $allowedExtensions = isset($pArr[3]) ? GeneralUtility::trimExplode(',', $pArr[3], true) : [];
164 $folderUtilityRenderer = GeneralUtility::makeInstance(FolderUtilityRenderer::class, $this);
165 $uploadForm = $folderUtilityRenderer->uploadForm($this->selectedFolder, $allowedExtensions);
166 $createFolder = $folderUtilityRenderer->createFolder($this->selectedFolder);
167 }
168
169 // Getting flag for showing/not showing thumbnails:
170 $noThumbs = $backendUser->getTSConfigVal('options.noThumbsInEB');
171 $_MOD_SETTINGS = array();
172 if (!$noThumbs) {
173 // MENU-ITEMS, fetching the setting for thumbnails from File>List module:
174 $_MOD_MENU = array('displayThumbs' => '');
175 $_MCONF['name'] = 'file_list';
176 $_MOD_SETTINGS = BackendUtility::getModuleData($_MOD_MENU, GeneralUtility::_GP('SET'), $_MCONF['name']);
177 }
178 $noThumbs = $noThumbs ?: !$_MOD_SETTINGS['displayThumbs'];
179 // Create folder tree:
180 /** @var ElementBrowserFolderTreeView $folderTree */
181 $folderTree = GeneralUtility::makeInstance(ElementBrowserFolderTreeView::class);
182 $folderTree->setLinkParameterProvider($this);
183 $tree = $folderTree->getBrowsableTree();
184 if ($this->selectedFolder) {
185 $files = $this->renderFilesInFolder($this->selectedFolder, $allowedFileExtensions, $noThumbs);
186 } else {
187 $files = '';
188 }
189
190 $this->initDocumentTemplate();
191 // Starting content:
192 $content = $this->doc->startPage('TBE file selector');
193 $content .= $this->doc->getFlashMessages();
194
195 // Insert the upload form on top, if so configured
196 if ($backendUser->getTSConfigVal('options.uploadFieldsInTopOfEB')) {
197 $content .= $uploadForm;
198 }
199 // Putting the parts together, side by side:
200 $content .= '
201
202 <!--
203 Wrapper table for folder tree / filelist:
204 -->
205 <table border="0" cellpadding="0" cellspacing="0" id="typo3-EBfiles">
206 <tr>
207 <td class="c-wCell" valign="top"><h3>' . $this->getLanguageService()->getLL('folderTree', true) . ':</h3>' . $tree . '</td>
208 <td class="c-wCell" valign="top">' . $files . '</td>
209 </tr>
210 </table>
211 ';
212 // Adding create folder + upload forms if applicable:
213 if (!$backendUser->getTSConfigVal('options.uploadFieldsInTopOfEB')) {
214 $content .= $uploadForm;
215 }
216 $content .= $createFolder;
217 // Add some space
218 $content .= '<br /><br />';
219 // Ending page, returning content:
220 $content .= $this->doc->endPage();
221 return $this->doc->insertStylesAndJS($content);
222 }
223
224 /**
225 * For TYPO3 Element Browser: Expand folder of files.
226 *
227 * @param Folder $folder The folder path to expand
228 * @param string $extensionList List of fileextensions to show
229 * @param bool $noThumbs Whether to show thumbnails or not. If set, no thumbnails are shown.
230 * @return string HTML output
231 */
232 public function renderFilesInFolder(Folder $folder, $extensionList = '', $noThumbs = false)
233 {
234 if (!$folder->checkActionPermission('read')) {
235 return '';
236 }
237 $lang = $this->getLanguageService();
238 $titleLen = (int)$this->getBackendUser()->uc['titleLen'];
239
240 if ($this->searchWord !== '') {
241 $files = $this->fileRepository->searchByName($folder, $this->searchWord);
242 } else {
243 $extensionList = $extensionList === '*' ? '' : $extensionList;
244 $files = $this->getFilesInFolder($folder, $extensionList);
245 }
246 $filesCount = count($files);
247
248 $lines = array();
249
250 // Create the header of current folder:
251 $folderIcon = $this->iconFactory->getIconForResource($folder, Icon::SIZE_SMALL);
252 $lines[] = '<tr class="t3-row-header"><td colspan="4">'
253 . $folderIcon . htmlspecialchars(GeneralUtility::fixed_lgd_cs($folder->getIdentifier(), $titleLen))
254 . '</td></tr>';
255
256 if ($filesCount === 0) {
257 $lines[] = '
258 <tr class="file_list_normal">
259 <td colspan="4">No files found.</td>
260 </tr>';
261 }
262
263 foreach ($files as $fileObject) {
264 $fileExtension = $fileObject->getExtension();
265 // Thumbnail/size generation:
266 $imgInfo = array();
267 if (!$noThumbs && GeneralUtility::inList(strtolower($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] . ',' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['mediafile_ext']), strtolower($fileExtension))) {
268 $processedFile = $fileObject->process(
269 ProcessedFile::CONTEXT_IMAGEPREVIEW,
270 array('width' => 64, 'height' => 64)
271 );
272 $imageUrl = $processedFile->getPublicUrl(true);
273 $imgInfo = array(
274 $fileObject->getProperty('width'),
275 $fileObject->getProperty('height')
276 );
277 $pDim = $imgInfo[0] . 'x' . $imgInfo[1] . ' pixels';
278 $clickIcon = '<img src="' . $imageUrl . '"'
279 . ' width="' . $processedFile->getProperty('width') . '"'
280 . ' height="' . $processedFile->getProperty('height') . '"'
281 . ' hspace="5" vspace="5" border="1" />';
282 } else {
283 $clickIcon = '';
284 $pDim = '';
285 }
286 // Create file icon:
287 $size = ' (' . GeneralUtility::formatSize($fileObject->getSize()) . 'bytes' . ($pDim ? ', ' . $pDim : '') . ')';
288 $icon = '<span title="' . htmlspecialchars($fileObject->getName() . $size) . '">' . $this->iconFactory->getIconForResource($fileObject, Icon::SIZE_SMALL) . '</span>';
289 // Create links for adding the file:
290 $filesIndex = count($this->elements);
291 $this->elements['file_' . $filesIndex] = array(
292 'type' => 'file',
293 'table' => 'sys_file',
294 'uid' => $fileObject->getUid(),
295 'fileName' => $fileObject->getName(),
296 'filePath' => $fileObject->getUid(),
297 'fileExt' => $fileExtension,
298 'fileIcon' => $icon
299 );
300 if ($this->fileIsSelectableInFileList($fileObject, $imgInfo)) {
301 $ATag = '<a href="#" title="' . htmlspecialchars($fileObject->getName()) . '" data-file-index="' . htmlspecialchars($filesIndex) . '" data-close="0">';
302 $ATag_alt = '<a href="#" title="' . htmlspecialchars($fileObject->getName()) . '" data-file-index="' . htmlspecialchars($filesIndex) . '" data-close="1">';
303 $ATag_e = '</a>';
304 $bulkCheckBox = '<input type="checkbox" class="typo3-bulk-item" name="file_' . $filesIndex . '" value="0" /> ';
305 } else {
306 $ATag = '';
307 $ATag_alt = '';
308 $ATag_e = '';
309 $bulkCheckBox = '';
310 }
311 // Create link to showing details about the file in a window:
312 $Ahref = BackendUtility::getModuleUrl('show_item', array(
313 'type' => 'file',
314 'table' => '_FILE',
315 'uid' => $fileObject->getCombinedIdentifier(),
316 'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
317 ));
318
319 // Combine the stuff:
320 $filenameAndIcon = $bulkCheckBox . $ATag_alt . $icon . htmlspecialchars(GeneralUtility::fixed_lgd_cs($fileObject->getName(), $titleLen)) . $ATag_e;
321 // Show element:
322 $lines[] = '
323 <tr class="file_list_normal">
324 <td nowrap="nowrap">' . $filenameAndIcon . '&nbsp;</td>
325 <td>' . $ATag . '<span title="' . $lang->getLL('addToList', true) . '">' . $this->iconFactory->getIcon('actions-edit-add', Icon::SIZE_SMALL)->render() . '</span>' . $ATag_e . '</td>
326 <td nowrap="nowrap"><a href="' . htmlspecialchars($Ahref) . '" title="' . $lang->getLL('info', true) . '">' . $this->iconFactory->getIcon('actions-document-info', Icon::SIZE_SMALL) . $lang->getLL('info', true) . '</a></td>
327 <td nowrap="nowrap">&nbsp;' . $pDim . '</td>
328 </tr>';
329 if ($pDim) {
330 $lines[] = '
331 <tr>
332 <td class="filelistThumbnail" colspan="4">' . $ATag_alt . $clickIcon . $ATag_e . '</td>
333 </tr>';
334 }
335 }
336
337 $out = '<h3>' . $lang->getLL('files', true) . ' ' . $filesCount . ':</h3>';
338 $out .= $this->getFileSearchField();
339 $out .= '<div id="filelist">';
340 $out .= $this->getBulkSelector($filesCount);
341
342 // Wrap all the rows in table tags:
343 $out .= '
344
345 <!--
346 Filelisting
347 -->
348 <table cellpadding="0" cellspacing="0" id="typo3-filelist">
349 ' . implode('', $lines) . '
350 </table>';
351 // Return accumulated content for filelisting:
352 $out .= '</div>';
353 return $out;
354 }
355
356 /**
357 * Get a list of Files in a folder filtered by extension
358 *
359 * @param Folder $folder
360 * @param string $extensionList
361 * @return File[]
362 */
363 protected function getFilesInFolder(Folder $folder, $extensionList)
364 {
365 if ($extensionList !== '') {
366 /** @var FileExtensionFilter $filter */
367 $filter = GeneralUtility::makeInstance(FileExtensionFilter::class);
368 $filter->setAllowedFileExtensions($extensionList);
369 $folder->setFileAndFolderNameFilters(array(array($filter, 'filterFileList')));
370 }
371 return $folder->getFiles();
372 }
373
374 /**
375 * Get the HTML data required for a bulk selection of files of the TYPO3 Element Browser.
376 *
377 * @param int $filesCount Number of files currently displayed
378 * @return string HTML data required for a bulk selection of files - if $filesCount is 0, nothing is returned
379 */
380 protected function getBulkSelector($filesCount)
381 {
382 if (!$filesCount) {
383 return '';
384 }
385
386 $lang = $this->getLanguageService();
387 $labelToggleSelection = $lang->sL('LLL:EXT:lang/locallang_browse_links.xlf:toggleSelection', true);
388 $labelImportSelection = $lang->sL('LLL:EXT:lang/locallang_browse_links.xlf:importSelection', true);
389
390 $out = '<div style="padding-top:10px;">' . '<a href="#" id="t3js-importSelection" title="' . $labelImportSelection . '">'
391 . $this->iconFactory->getIcon('actions-document-import-t3d', Icon::SIZE_SMALL)
392 . $labelImportSelection . '</a>&nbsp;&nbsp;&nbsp;'
393 . '<a href="#" id="t3js-toggleSelection" title="' . $labelToggleSelection . '">'
394 . $this->iconFactory->getIcon('actions-document-select', Icon::SIZE_SMALL)
395 . $labelToggleSelection . '</a>' . '</div>';
396
397 // Getting flag for showing/not showing thumbnails:
398 $noThumbsInEB = $this->getBackendUser()->getTSConfigVal('options.noThumbsInEB');
399 if (!$noThumbsInEB && $this->selectedFolder) {
400 // MENU-ITEMS, fetching the setting for thumbnails from File>List module:
401 $_MOD_MENU = array('displayThumbs' => '');
402 $_MCONF['name'] = 'file_list';
403 $_MOD_SETTINGS = BackendUtility::getModuleData($_MOD_MENU, GeneralUtility::_GP('SET'), $_MCONF['name']);
404 $addParams = GeneralUtility::implodeArrayForUrl('', $this->getUrlParameters(['identifier' => $this->selectedFolder->getCombinedIdentifier()]));
405 $thumbNailCheck = '<div class="checkbox" style="padding:5px 0 15px 0"><label for="checkDisplayThumbs">'
406 . BackendUtility::getFuncCheck(
407 '',
408 'SET[displayThumbs]',
409 $_MOD_SETTINGS['displayThumbs'],
410 $this->thisScript,
411 $addParams,
412 'id="checkDisplayThumbs"'
413 )
414 . $lang->sL('LLL:EXT:lang/locallang_mod_file_list.xlf:displayThumbs', true) . '</label></div>';
415 $out .= $thumbNailCheck;
416 } else {
417 $out .= '<div style="padding-top: 15px;"></div>';
418 }
419 return $out;
420 }
421
422 /**
423 * Get the HTML data required for the file search field of the TYPO3 Element Browser.
424 *
425 * @return string HTML data required for the search field in the file list of the Element Browser
426 */
427 protected function getFileSearchField()
428 {
429 $action = $this->getScriptUrl() . GeneralUtility::implodeArrayForUrl('', $this->getUrlParameters([]));
430 $out = '
431 <form method="post" action="' . htmlspecialchars($action) . '" style="padding-bottom: 15px;">
432 <div class="input-group">
433 <input class="form-control" type="text" name="searchWord" value="' . htmlspecialchars($this->searchWord) . '">
434 <span class="input-group-btn">
435 <button class="btn btn-default" type="submit">' . $this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:search', true) . '</button>
436 </span>
437 </div>
438 </form>';
439 return $out;
440 }
441
442 /**
443 * Checks if the given file is selectable in the filelist.
444 *
445 * By default all files are selectable. This method may be overwritten in child classes.
446 *
447 * @param FileInterface $file
448 * @param mixed[] $imgInfo Image dimensions from \TYPO3\CMS\Core\Imaging\GraphicalFunctions::getImageDimensions()
449 * @return bool TRUE if file is selectable.
450 */
451 protected function fileIsSelectableInFileList(FileInterface $file, array $imgInfo)
452 {
453 return true;
454 }
455
456 /**
457 * @return string[] Array of body-tag attributes
458 */
459 protected function getBodyTagAttributes()
460 {
461 return [
462 'data-mode' => 'file',
463 'data-elements' => json_encode($this->elements)
464 ];
465 }
466
467 /**
468 * @param array $values Array of values to include into the parameters
469 * @return string[] Array of parameters which have to be added to URLs
470 */
471 public function getUrlParameters(array $values)
472 {
473 return [
474 'mode' => 'file',
475 'expandFolder' => isset($values['identifier']) ? $values['identifier'] : $this->expandFolder,
476 'bparams' => $this->bparams
477 ];
478 }
479
480 /**
481 * @param array $values Values to be checked
482 * @return bool Returns TRUE if the given values match the currently selected item
483 */
484 public function isCurrentlySelectedItem(array $values)
485 {
486 return false;
487 }
488
489 /**
490 * Returns the URL of the current script
491 *
492 * @return string
493 */
494 public function getScriptUrl()
495 {
496 return $this->thisScript;
497 }
498 }