b74baa919bb103a95fd4843b66025386831c014a
[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 return true;
91 }
92 } catch (FileDoesNotExistException $e) {
93 }
94 }
95 return false;
96 }
97
98 /**
99 * Format the current link for HTML output
100 *
101 * @return string
102 */
103 public function formatCurrentUrl()
104 {
105 return $this->linkParts['name'];
106 }
107
108 /**
109 * Render the link handler
110 *
111 * @param ServerRequestInterface $request
112 *
113 * @return string
114 */
115 public function render(ServerRequestInterface $request)
116 {
117 GeneralUtility::makeInstance(PageRenderer::class)->loadRequireJsModule('TYPO3/CMS/Recordlist/FileLinkHandler');
118
119 $this->expandFolder = isset($request->getQueryParams()['expandFolder']) ? $request->getQueryParams()['expandFolder'] : null;
120 if (!empty($this->linkParts) && !isset($this->expandFolder)) {
121 $this->expandFolder = $this->linkParts['url'];
122 }
123
124 /** @var ElementBrowserFolderTreeView $folderTree */
125 $folderTree = GeneralUtility::makeInstance(ElementBrowserFolderTreeView::class);
126 $folderTree->setLinkParameterProvider($this);
127 $tree = $folderTree->getBrowsableTree();
128
129 // Create upload/create folder forms, if a path is given
130 $selectedFolder = false;
131 if ($this->expandFolder) {
132 $fileOrFolderObject = null;
133 try {
134 $fileOrFolderObject = ResourceFactory::getInstance()->retrieveFileOrFolderObject($this->expandFolder);
135 } catch (\Exception $e) {
136 // No path is selected
137 }
138
139 if ($fileOrFolderObject instanceof Folder) {
140 // It's a folder
141 $selectedFolder = $fileOrFolderObject;
142 } elseif ($fileOrFolderObject instanceof FileInterface) {
143 // It's a file
144 try {
145 $selectedFolder = $fileOrFolderObject->getParentFolder();
146 } catch (\Exception $e) {
147 // Accessing the parent folder failed for some reason. e.g. permissions
148 }
149 }
150 }
151
152 $backendUser = $this->getBackendUser();
153 // If no folder is selected, get the user's default upload folder
154 if (!$selectedFolder) {
155 try {
156 $selectedFolder = $backendUser->getDefaultUploadFolder();
157 } catch (\Exception $e) {
158 // The configured default user folder does not exist
159 }
160 }
161 // Build the file upload and folder creation form
162 $uploadForm = '';
163 $createFolder = '';
164 $content = '';
165 if ($selectedFolder) {
166 $folderUtilityRenderer = GeneralUtility::makeInstance(FolderUtilityRenderer::class, $this);
167 $uploadForm = $this->mode === 'file' ? $folderUtilityRenderer->uploadForm($selectedFolder, []) : '';
168 $createFolder = $folderUtilityRenderer->createFolder($selectedFolder);
169 }
170 // Insert the upload form on top, if so configured
171 if ($backendUser->getTSConfigVal('options.uploadFieldsInTopOfEB')) {
172 $content .= $uploadForm;
173 }
174
175 // Render the filelist if there is a folder selected
176 $files = '';
177 if ($selectedFolder) {
178 $parameters = $this->linkBrowser->getUrlParameters();
179 $allowedExtensions = isset($parameters['allowedExtensions']) ? $parameters['allowedExtensions'] : '';
180 $files = $this->expandFolder($selectedFolder, $allowedExtensions);
181 }
182 // Create folder tree:
183 $content .= '
184 <!--
185 Wrapper table for folder tree / file/folder list:
186 -->
187 <table border="0" cellpadding="0" cellspacing="0" id="typo3-linkFiles">
188 <tr>
189 <td class="c-wCell" valign="top"><h3>' . $this->getLanguageService()->getLL('folderTree') . ':</h3>' . $tree . '</td>
190 <td class="c-wCell" valign="top">' . $files . '</td>
191 </tr>
192 </table>
193 ';
194 // Adding create folder + upload form if applicable
195 if (!$backendUser->getTSConfigVal('options.uploadFieldsInTopOfEB')) {
196 $content .= $uploadForm;
197 }
198 $content .= '<br />' . $createFolder . '<br />';
199 return $content;
200 }
201
202 /**
203 * For RTE: This displays all files from folder. No thumbnails shown
204 *
205 * @param Folder $folder The folder path to expand
206 * @param string $extensionList List of file extensions to show
207 * @return string HTML output
208 */
209 public function expandFolder(Folder $folder, $extensionList = '')
210 {
211 if (!$folder->checkActionPermission('read')) {
212 return '';
213 }
214 $out = '<h3>' . htmlspecialchars($this->getTitle()) . ':</h3>';
215
216 // Create header element; The folder from which files are listed.
217 $titleLen = (int)$this->getBackendUser()->uc['titleLen'];
218 $folderIcon = $this->iconFactory->getIconForResource($folder, Icon::SIZE_SMALL);
219 $folderIcon .= htmlspecialchars(GeneralUtility::fixed_lgd_cs($folder->getIdentifier(), $titleLen));
220
221 $currentIdentifier = !empty($this->linkParts) ? $this->linkParts['url'] : '';
222 $selected = $currentIdentifier === $folder->getCombinedIdentifier() ? $this->additionalFolderClass : '';
223 $out .= '
224 <span class="' . $selected . '" title="' . htmlspecialchars($folder->getIdentifier()) . '">
225 ' . $folderIcon . '
226 </span>
227 ';
228 // Get files from the folder:
229 $folderContent = $this->getFolderContent($folder, $extensionList);
230 if (!empty($folderContent)) {
231 $out .= '<ul class="list-tree">';
232 foreach ($folderContent as $fileOrFolderObject) {
233 list($fileIdentifier, $icon) = $this->renderItem($fileOrFolderObject);
234 $selected = $currentIdentifier === $fileIdentifier ? ' class="active"' : '';
235 $out .=
236 '<li' . $selected . '>
237 <span class="list-tree-group">
238 <a href="#" class="t3js-fileLink list-tree-group" title="' . htmlspecialchars($fileOrFolderObject->getName()) . '" data-file="file:' . htmlspecialchars($fileIdentifier) . '">
239 <span class="list-tree-icon">' . $icon . '</span>
240 <span class="list-tree-title">' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($fileOrFolderObject->getName(), $titleLen)) . '</span>
241 </a>
242 </span>
243 </li>';
244 }
245 $out .= '</ul>';
246 }
247 return $out;
248 }
249
250 /**
251 * @return string
252 */
253 protected function getTitle()
254 {
255 return $this->getLanguageService()->getLL('files');
256 }
257
258 /**
259 * @param Folder $folder
260 * @param string $extensionList
261 *
262 * @return FileInterface[]
263 */
264 protected function getFolderContent(Folder $folder, $extensionList)
265 {
266 if ($extensionList !== '') {
267 /** @var FileExtensionFilter $filter */
268 $filter = GeneralUtility::makeInstance(FileExtensionFilter::class);
269 $filter->setAllowedFileExtensions($extensionList);
270 $folder->setFileAndFolderNameFilters([[$filter, 'filterFileList']]);
271 }
272 return $folder->getFiles();
273 }
274
275 /**
276 * Renders a single item displayed in the current folder
277 *
278 * @param ResourceInterface $fileOrFolderObject
279 *
280 * @return array
281 * @throws \InvalidArgumentException
282 */
283 protected function renderItem(ResourceInterface $fileOrFolderObject)
284 {
285 if (!$fileOrFolderObject instanceof File) {
286 throw new \InvalidArgumentException('Expected File object, got "' . get_class($fileOrFolderObject) . '" object.', 1443651368);
287 }
288 $fileIdentifier = $fileOrFolderObject->getUid();
289 // Get size and icon:
290 $size = ' (' . GeneralUtility::formatSize($fileOrFolderObject->getSize()) . 'bytes)';
291 $icon = '<span title="' . htmlspecialchars($fileOrFolderObject->getName() . $size) . '">'
292 . $this->iconFactory->getIconForResource($fileOrFolderObject, Icon::SIZE_SMALL)
293 . '</span>';
294 return [$fileIdentifier, $icon];
295 }
296
297 /**
298 * @return string[] Array of body-tag attributes
299 */
300 public function getBodyTagAttributes()
301 {
302 return [
303 'data-current-link' => empty($this->linkParts) ? '' : 'file:' . $this->linkParts['url']
304 ];
305 }
306
307 /**
308 * @param array $values Array of values to include into the parameters or which might influence the parameters
309 *
310 * @return string[] Array of parameters which have to be added to URLs
311 */
312 public function getUrlParameters(array $values)
313 {
314 $parameters = [
315 'expandFolder' => isset($values['identifier']) ? $values['identifier'] : (string)$this->expandFolder
316 ];
317 return array_merge($this->linkBrowser->getUrlParameters($values), $parameters);
318 }
319
320 /**
321 * @param array $values Values to be checked
322 *
323 * @return bool Returns TRUE if the given values match the currently selected item
324 */
325 public function isCurrentlySelectedItem(array $values)
326 {
327 return false;
328 }
329
330 /**
331 * Returns the URL of the current script
332 *
333 * @return string
334 */
335 public function getScriptUrl()
336 {
337 return $this->linkBrowser->getScriptUrl();
338 }
339 }