[BUGFIX] ElementBrowser does not highlight selected file in file list view
[Packages/TYPO3.CMS.git] / typo3 / sysext / recordlist / Classes / LinkHandler / FileLinkHandler.php
1 <?php
2 namespace TYPO3\CMS\Recordlist\LinkHandler;
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 Psr\Http\Message\ServerRequestInterface;
18 use TYPO3\CMS\Backend\Tree\View\ElementBrowserFolderTreeView;
19 use TYPO3\CMS\Core\Imaging\Icon;
20 use TYPO3\CMS\Core\Page\PageRenderer;
21 use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
22 use TYPO3\CMS\Core\Resource\File;
23 use TYPO3\CMS\Core\Resource\FileInterface;
24 use TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter;
25 use TYPO3\CMS\Core\Resource\Folder;
26 use TYPO3\CMS\Core\Resource\ResourceFactory;
27 use TYPO3\CMS\Core\Resource\ResourceInterface;
28 use TYPO3\CMS\Core\Utility\GeneralUtility;
29 use TYPO3\CMS\Core\Utility\StringUtility;
30 use TYPO3\CMS\Recordlist\Tree\View\LinkParameterProviderInterface;
31 use TYPO3\CMS\Recordlist\View\FolderUtilityRenderer;
32
33 /**
34 * Link handler for files
35 */
36 class FileLinkHandler extends AbstractLinkHandler implements LinkHandlerInterface, LinkParameterProviderInterface
37 {
38 /**
39 * Parts of the current link
40 *
41 * @var array
42 */
43 protected $linkParts = [];
44
45 /**
46 * @var string
47 */
48 protected $expectedClass = File::class;
49
50 /**
51 * @var string
52 */
53 protected $mode = 'file';
54
55 /**
56 * @var string
57 */
58 protected $expandFolder;
59
60 /**
61 * @var string
62 */
63 protected $additionalFolderClass = '';
64
65 /**
66 * Checks if this is the handler for the given link
67 *
68 * The handler may store this information locally for later usage.
69 *
70 * @param array $linkParts Link parts as returned from TypoLinkCodecService
71 *
72 * @return bool
73 */
74 public function canHandleLink(array $linkParts)
75 {
76 if (!$linkParts['url']) {
77 return false;
78 }
79 $url = rawurldecode($linkParts['url']);
80
81 if (StringUtility::beginsWith($url, 'file:') && !StringUtility::beginsWith($url, 'file://')) {
82 $rel = substr($url, 5);
83 try {
84 // resolve FAL-api "file:UID-of-sys_file-record" and "file:combined-identifier"
85 $fileOrFolderObject = ResourceFactory::getInstance()->retrieveFileOrFolderObject($rel);
86 if (is_a($fileOrFolderObject, $this->expectedClass)) {
87 $this->linkParts = $linkParts;
88 $this->linkParts['url'] = $rel;
89 $this->linkParts['name'] = $fileOrFolderObject->getName();
90
91 return true;
92 }
93 } catch (FileDoesNotExistException $e) {
94 }
95 }
96 return false;
97 }
98
99 /**
100 * Format the current link for HTML output
101 *
102 * @return string
103 */
104 public function formatCurrentUrl()
105 {
106 return $this->linkParts['name'];
107 }
108
109 /**
110 * Render the link handler
111 *
112 * @param ServerRequestInterface $request
113 *
114 * @return string
115 */
116 public function render(ServerRequestInterface $request)
117 {
118 GeneralUtility::makeInstance(PageRenderer::class)->loadRequireJsModule('TYPO3/CMS/Recordlist/FileLinkHandler');
119
120 $this->expandFolder = isset($request->getQueryParams()['expandFolder']) ? $request->getQueryParams()['expandFolder'] : null;
121 if (!empty($this->linkParts) && !isset($this->expandFolder)) {
122 $this->expandFolder = $this->linkParts['url'];
123 }
124
125 /** @var ElementBrowserFolderTreeView $folderTree */
126 $folderTree = GeneralUtility::makeInstance(ElementBrowserFolderTreeView::class);
127 $folderTree->setLinkParameterProvider($this);
128 $tree = $folderTree->getBrowsableTree();
129
130 // Create upload/create folder forms, if a path is given
131 $selectedFolder = false;
132 if ($this->expandFolder) {
133 $fileOrFolderObject = null;
134 try {
135 $fileOrFolderObject = ResourceFactory::getInstance()->retrieveFileOrFolderObject($this->expandFolder);
136 } catch (\Exception $e) {
137 // No path is selected
138 }
139
140 if ($fileOrFolderObject instanceof Folder) {
141 // It's a folder
142 $selectedFolder = $fileOrFolderObject;
143 } elseif ($fileOrFolderObject instanceof FileInterface) {
144 // It's a file
145 try {
146 $selectedFolder = $fileOrFolderObject->getParentFolder();
147 } catch (\Exception $e) {
148 // Accessing the parent folder failed for some reason. e.g. permissions
149 }
150 }
151 }
152
153 $backendUser = $this->getBackendUser();
154 // If no folder is selected, get the user's default upload folder
155 if (!$selectedFolder) {
156 try {
157 $selectedFolder = $backendUser->getDefaultUploadFolder();
158 } catch (\Exception $e) {
159 // The configured default user folder does not exist
160 }
161 }
162 // Build the file upload and folder creation form
163 $uploadForm = '';
164 $createFolder = '';
165 $content = '';
166 if ($selectedFolder) {
167 $folderUtilityRenderer = GeneralUtility::makeInstance(FolderUtilityRenderer::class, $this);
168 $uploadForm = $this->mode === 'file' ? $folderUtilityRenderer->uploadForm($selectedFolder, []) : '';
169 $createFolder = $folderUtilityRenderer->createFolder($selectedFolder);
170 }
171 // Insert the upload form on top, if so configured
172 if ($backendUser->getTSConfigVal('options.uploadFieldsInTopOfEB')) {
173 $content .= $uploadForm;
174 }
175
176 // Render the filelist if there is a folder selected
177 $files = '';
178 if ($selectedFolder) {
179 $parameters = $this->linkBrowser->getUrlParameters();
180 $allowedExtensions = isset($parameters['allowedExtensions']) ? $parameters['allowedExtensions'] : '';
181 $files = $this->expandFolder($selectedFolder, $allowedExtensions);
182 }
183 // Create folder tree:
184 $content .= '
185 <!--
186 Wrapper table for folder tree / file/folder list:
187 -->
188 <table border="0" cellpadding="0" cellspacing="0" id="typo3-linkFiles">
189 <tr>
190 <td class="c-wCell" valign="top"><h3>' . $this->getLanguageService()->getLL('folderTree') . ':</h3>' . $tree . '</td>
191 <td class="c-wCell" valign="top">' . $files . '</td>
192 </tr>
193 </table>
194 ';
195 // Adding create folder + upload form if applicable
196 if (!$backendUser->getTSConfigVal('options.uploadFieldsInTopOfEB')) {
197 $content .= $uploadForm;
198 }
199 $content .= '<br />' . $createFolder . '<br />';
200 return $content;
201 }
202
203 /**
204 * For RTE: This displays all files from folder. No thumbnails shown
205 *
206 * @param Folder $folder The folder path to expand
207 * @param string $extensionList List of file extensions to show
208 * @return string HTML output
209 */
210 public function expandFolder(Folder $folder, $extensionList = '')
211 {
212 if (!$folder->checkActionPermission('read')) {
213 return '';
214 }
215 $out = '<h3>' . htmlspecialchars($this->getTitle()) . ':</h3>';
216
217 // Create header element; The folder from which files are listed.
218 $titleLen = (int)$this->getBackendUser()->uc['titleLen'];
219 $folderIcon = $this->iconFactory->getIconForResource($folder, Icon::SIZE_SMALL);
220 $folderIcon .= htmlspecialchars(GeneralUtility::fixed_lgd_cs($folder->getIdentifier(), $titleLen));
221
222 $currentIdentifier = !empty($this->linkParts) ? $this->linkParts['url'] : '';
223 $selected = $currentIdentifier === $folder->getCombinedIdentifier() ? $this->additionalFolderClass : '';
224 $out .= '
225 <span class="' . $selected . '" title="' . htmlspecialchars($folder->getIdentifier()) . '">
226 ' . $folderIcon . '
227 </span>
228 ';
229 // Get files from the folder:
230 $folderContent = $this->getFolderContent($folder, $extensionList);
231 if (!empty($folderContent)) {
232 $out .= '<ul class="list-tree">';
233 foreach ($folderContent as $fileOrFolderObject) {
234 list($fileIdentifier, $icon) = $this->renderItem($fileOrFolderObject);
235 $selected = (int)$currentIdentifier === $fileIdentifier ? ' class="active"' : '';
236 $out .=
237 '<li' . $selected . '>
238 <span class="list-tree-group">
239 <a href="#" class="t3js-fileLink list-tree-group" title="' . htmlspecialchars($fileOrFolderObject->getName()) . '" data-file="file:' . htmlspecialchars($fileIdentifier) . '">
240 <span class="list-tree-icon">' . $icon . '</span>
241 <span class="list-tree-title">' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($fileOrFolderObject->getName(), $titleLen)) . '</span>
242 </a>
243 </span>
244 </li>';
245 }
246 $out .= '</ul>';
247 }
248 return $out;
249 }
250
251 /**
252 * @return string
253 */
254 protected function getTitle()
255 {
256 return $this->getLanguageService()->getLL('files');
257 }
258
259 /**
260 * @param Folder $folder
261 * @param string $extensionList
262 *
263 * @return FileInterface[]
264 */
265 protected function getFolderContent(Folder $folder, $extensionList)
266 {
267 if ($extensionList !== '') {
268 /** @var FileExtensionFilter $filter */
269 $filter = GeneralUtility::makeInstance(FileExtensionFilter::class);
270 $filter->setAllowedFileExtensions($extensionList);
271 $folder->setFileAndFolderNameFilters([[$filter, 'filterFileList']]);
272 }
273 return $folder->getFiles();
274 }
275
276 /**
277 * Renders a single item displayed in the current folder
278 *
279 * @param ResourceInterface $fileOrFolderObject
280 *
281 * @return array
282 * @throws \InvalidArgumentException
283 */
284 protected function renderItem(ResourceInterface $fileOrFolderObject)
285 {
286 if (!$fileOrFolderObject instanceof File) {
287 throw new \InvalidArgumentException('Expected File object, got "' . get_class($fileOrFolderObject) . '" object.', 1443651368);
288 }
289 $fileIdentifier = $fileOrFolderObject->getUid();
290 // Get size and icon:
291 $size = ' (' . GeneralUtility::formatSize($fileOrFolderObject->getSize()) . 'bytes)';
292 $icon = '<span title="' . htmlspecialchars($fileOrFolderObject->getName() . $size) . '">'
293 . $this->iconFactory->getIconForResource($fileOrFolderObject, Icon::SIZE_SMALL)
294 . '</span>';
295 return [$fileIdentifier, $icon];
296 }
297
298 /**
299 * @return string[] Array of body-tag attributes
300 */
301 public function getBodyTagAttributes()
302 {
303 return [
304 'data-current-link' => empty($this->linkParts) ? '' : 'file:' . $this->linkParts['url']
305 ];
306 }
307
308 /**
309 * @param array $values Array of values to include into the parameters or which might influence the parameters
310 *
311 * @return string[] Array of parameters which have to be added to URLs
312 */
313 public function getUrlParameters(array $values)
314 {
315 $parameters = [
316 'expandFolder' => isset($values['identifier']) ? $values['identifier'] : (string)$this->expandFolder
317 ];
318 return array_merge($this->linkBrowser->getUrlParameters($values), $parameters);
319 }
320
321 /**
322 * @param array $values Values to be checked
323 *
324 * @return bool Returns TRUE if the given values match the currently selected item
325 */
326 public function isCurrentlySelectedItem(array $values)
327 {
328 return false;
329 }
330
331 /**
332 * Returns the URL of the current script
333 *
334 * @return string
335 */
336 public function getScriptUrl()
337 {
338 return $this->linkBrowser->getScriptUrl();
339 }
340 }