1<?php
2declare(strict_types = 1);
3namespace TYPO3\CMS\Recordlist\LinkHandler;
4
5/*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18use Psr\Http\Message\ServerRequestInterface;
19use TYPO3\CMS\Backend\Utility\BackendUtility;
20use TYPO3\CMS\Core\LinkHandling\LinkService;
21use TYPO3\CMS\Core\Page\PageRenderer;
22use TYPO3\CMS\Core\Utility\GeneralUtility;
23use TYPO3\CMS\Fluid\View\StandaloneView;
24use TYPO3\CMS\Recordlist\Browser\RecordBrowser;
25use TYPO3\CMS\Recordlist\Controller\AbstractLinkBrowserController;
26use TYPO3\CMS\Recordlist\Tree\View\LinkParameterProviderInterface;
27use TYPO3\CMS\Recordlist\Tree\View\RecordBrowserPageTreeView;
28
29/**
30 * Link handler for arbitrary database records
31 * @internal This class is a specific LinkHandler implementation and is not part of the TYPO3's Core API.
32 */
33class RecordLinkHandler extends AbstractLinkHandler implements LinkHandlerInterface, LinkParameterProviderInterface
34{
35    /**
36     * Configuration key in TSconfig TCEMAIN.linkHandler.record
37     *
38     * @var string
39     */
40    protected $identifier;
41
42    /**
43     * Specific TSconfig for the current instance (corresponds to TCEMAIN.linkHandler.record.identifier.configuration)
44     *
45     * @var array
46     */
47    protected $configuration = [];
48
49    /**
50     * Parts of the current link
51     *
52     * @var array
53     */
54    protected $linkParts = [];
55
56    /**
57     * @var int
58     */
59    protected $expandPage = 0;
60
61    /**
62     * Initializes the handler.
63     *
64     * @param AbstractLinkBrowserController $linkBrowser
65     * @param string $identifier
66     * @param array $configuration Page TSconfig
67     */
68    public function initialize(AbstractLinkBrowserController $linkBrowser, $identifier, array $configuration)
69    {
70        parent::initialize($linkBrowser, $identifier, $configuration);
71        $this->identifier = $identifier;
72        $this->configuration = $configuration;
73    }
74
75    /**
76     * Checks if this is the right handler for the given link.
77     *
78     * Also stores information locally about currently linked record.
79     *
80     * @param array $linkParts Link parts as returned from TypoLinkCodecService
81     * @return bool
82     */
83    public function canHandleLink(array $linkParts): bool
84    {
85        if (!$linkParts['url'] || !isset($linkParts['url']['identifier']) || $linkParts['url']['identifier'] !== $this->identifier) {
86            return false;
87        }
88
89        $data = $linkParts['url'];
90
91        // Get the related record
92        $table = $this->configuration['table'];
93        $record = BackendUtility::getRecord($table, $data['uid']);
94        if ($record === null) {
95            $linkParts['title'] = $this->getLanguageService()->getLL('recordNotFound');
96        } else {
97            $linkParts['tableName'] = $this->getLanguageService()->sL($GLOBALS['TCA'][$table]['ctrl']['title']);
98            $linkParts['pid'] = (int)$record['pid'];
99            $linkParts['title'] = $linkParts['title'] ?: BackendUtility::getRecordTitle($table, $record);
100        }
101        $linkParts['url']['type'] = $linkParts['type'];
102        $this->linkParts = $linkParts;
103
104        return true;
105    }
106
107    /**
108     * Formats information for the current record for HTML output.
109     *
110     * @return string
111     */
112    public function formatCurrentUrl(): string
113    {
114        return sprintf(
115            '%s: %s [uid: %d]',
116            $this->linkParts['tableName'],
117            $this->linkParts['title'],
118            $this->linkParts['url']['uid']
119        );
120    }
121
122    /**
123     * Renders the link handler.
124     *
125     * @param ServerRequestInterface $request
126     * @return string
127     */
128    public function render(ServerRequestInterface $request): string
129    {
130        // Declare JS module
131        GeneralUtility::makeInstance(PageRenderer::class)->loadRequireJsModule('TYPO3/CMS/Recordlist/RecordLinkHandler');
132
133        // Define the current page
134        if (isset($request->getQueryParams()['expandPage'])) {
135            $this->expandPage = (int)$request->getQueryParams()['expandPage'];
136        } elseif (isset($this->configuration['storagePid'])) {
137            $this->expandPage = (int)$this->configuration['storagePid'];
138        } elseif (isset($this->linkParts['pid'])) {
139            $this->expandPage = (int)$this->linkParts['pid'];
140        }
141        $this->setTemporaryDbMounts();
142
143        $databaseBrowser = GeneralUtility::makeInstance(RecordBrowser::class);
144
145        $recordList = $databaseBrowser->displayRecordsForPage(
146            $this->expandPage,
147            $this->configuration['table'],
148            $this->getUrlParameters([])
149        );
150
151        $path = GeneralUtility::getFileAbsFileName('EXT:recordlist/Resources/Private/Templates/LinkBrowser/Record.html');
152        $view = GeneralUtility::makeInstance(StandaloneView::class);
153        $view->setTemplatePathAndFilename($path);
154        $view->assignMultiple([
155            'tree' => $this->configuration['hidePageTree'] ? '' : $this->renderPageTree(),
156            'recordList' => $recordList,
157        ]);
158
159        return $view->render();
160    }
161
162    /**
163     * Renders the page tree.
164     *
165     * @return string
166     */
167    protected function renderPageTree(): string
168    {
169        $userTsConfig = $this->getBackendUser()->getTSConfig();
170
171        /** @var RecordBrowserPageTreeView $pageTree */
172        $pageTree = GeneralUtility::makeInstance(RecordBrowserPageTreeView::class);
173        $pageTree->setLinkParameterProvider($this);
174        $pageTree->ext_showPageId = (bool)($userTsConfig['options.']['pageTree.']['showPageIdWithTitle'] ?? false);
175        $pageTree->ext_showNavTitle = (bool)($userTsConfig['options.']['pageTree.']['showNavTitle'] ?? false);
176        $pageTree->ext_showPathAboveMounts = (bool)($userTsConfig['options.']['pageTree.']['showPathAboveMounts'] ?? false);
177        $pageTree->addField('nav_title');
178
179        // Load the mount points, if any
180        // NOTE: mount points actually override the page tree
181        if (!empty($this->configuration['pageTreeMountPoints'])) {
182            $pageTree->MOUNTS = GeneralUtility::intExplode(',', $this->configuration['pageTreeMountPoints'], true);
183        }
184
185        return $pageTree->getBrowsableTree();
186    }
187
188    /**
189     * Returns attributes for the body tag.
190     *
191     * @return string[] Array of body-tag attributes
192     */
193    public function getBodyTagAttributes(): array
194    {
195        $attributes = [
196            'data-identifier' => 't3://record?identifier=' . $this->identifier . '&uid=',
197        ];
198        if (!empty($this->linkParts)) {
199            $attributes['data-current-link'] = GeneralUtility::makeInstance(LinkService::class)->asString($this->linkParts['url']);
200        }
201
202        return $attributes;
203    }
204
205    /**
206     * Returns all parameters needed to build a URL with all the necessary information.
207     *
208     * @param array $values Array of values to include into the parameters or which might influence the parameters
209     * @return string[] Array of parameters which have to be added to URLs
210     */
211    public function getUrlParameters(array $values): array
212    {
213        $pid = isset($values['pid']) ? (int)$values['pid'] : $this->expandPage;
214        $parameters = [
215            'expandPage' => $pid,
216        ];
217
218        return array_merge(
219            $this->linkBrowser->getUrlParameters($values),
220            ['P' => $this->linkBrowser->getParameters()],
221            $parameters
222        );
223    }
224
225    /**
226     * Checks if the submitted page matches the current page.
227     *
228     * @param array $values Values to be checked
229     * @return bool Returns TRUE if the given values match the currently selected item
230     */
231    public function isCurrentlySelectedItem(array $values): bool
232    {
233        return !empty($this->linkParts) && (int)$this->linkParts['pid'] === (int)$values['pid'];
234    }
235
236    /**
237     * Returns the URL of the current script
238     *
239     * @return string
240     */
241    public function getScriptUrl(): string
242    {
243        return $this->linkBrowser->getScriptUrl();
244    }
245}
246