Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 72
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
HTMLTree
0.00% covered (danger)
0.00%
0 / 72
0.00% covered (danger)
0.00%
0 / 7
650
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getTreeList
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
56
 render
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 augmentNodeData
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 getContextualUrl
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 getUrlFromRouteCache
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
6
 getRouteNameFromDataSource
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3/**
4 * Hierarchy Tree HTML Renderer
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova University 2010.
9 * Copyright (C) The National Library of Finland 2023.
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2,
13 * as published by the Free Software Foundation.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23 *
24 * @category VuFind
25 * @package  HierarchyTree_Renderer
26 * @author   Luke O'Sullivan <l.osullivan@swansea.ac.uk>
27 * @author   Ere Maijala <ere.maijala@helsinki.fi>
28 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
29 * @link     https://vufind.org/wiki/development:plugins:hierarchy_components Wiki
30 */
31
32namespace VuFind\Hierarchy\TreeRenderer;
33
34use Laminas\Mvc\Controller\Plugin\Url as UrlPlugin;
35use Laminas\View\Renderer\RendererInterface;
36
37use function in_array;
38
39/**
40 * Hierarchy Tree HTML Renderer
41 *
42 * This is a helper class for producing hierarchy trees.
43 *
44 * @category VuFind
45 * @package  HierarchyTree_Renderer
46 * @author   Luke O'Sullivan <l.osullivan@swansea.ac.uk>
47 * @author   Ere Maijala <ere.maijala@helsinki.fi>
48 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
49 * @link     https://vufind.org/wiki/development:plugins:hierarchy_components Wiki
50 */
51class HTMLTree extends AbstractBase implements \VuFind\I18n\Translator\TranslatorAwareInterface
52{
53    use \VuFind\I18n\Translator\TranslatorAwareTrait;
54
55    /**
56     * Router plugin
57     *
58     * @var UrlPlugin
59     */
60    protected $router = null;
61
62    /**
63     * Whether the collections functionality is enabled
64     *
65     * @var bool
66     */
67    protected $collectionsEnabled;
68
69    /**
70     * View renderer
71     *
72     * @var RendererInterface
73     */
74    protected $viewRenderer;
75
76    /**
77     * Constructor
78     *
79     * @param UrlPlugin         $router             Router plugin for urls
80     * @param bool              $collectionsEnabled Whether the collections functionality is enabled
81     * @param RendererInterface $renderer           View renderer
82     */
83    public function __construct(UrlPlugin $router, bool $collectionsEnabled, RendererInterface $renderer)
84    {
85        $this->router = $router;
86        $this->collectionsEnabled = $collectionsEnabled;
87        $this->viewRenderer = $renderer;
88    }
89
90    /**
91     * Get a list of trees containing the item represented by the stored record
92     * driver.
93     *
94     * @param string $hierarchyID Optional filter: specific hierarchy ID to retrieve
95     *
96     * @return mixed An array of hierarchy IDS if a hierarchy tree exists,
97     * false if it does not
98     */
99    public function getTreeList($hierarchyID = false)
100    {
101        $record = $this->getRecordDriver();
102        $inHierarchies = $record->getHierarchyTopID();
103        $inHierarchiesTitle = $record->getHierarchyTopTitle();
104
105        if ($hierarchyID) {
106            // Specific Hierarchy Supplied
107            if (
108                in_array($hierarchyID, $inHierarchies)
109                && $this->getDataSource()->supports($hierarchyID)
110            ) {
111                return [
112                    $hierarchyID => $this->getHierarchyName(
113                        $hierarchyID,
114                        $inHierarchies,
115                        $inHierarchiesTitle
116                    ),
117                ];
118            }
119        } else {
120            // Return All Hierarchies
121            $hierarchies = [];
122            foreach ($inHierarchies as $i => $hierarchyTopID) {
123                if ($this->getDataSource()->supports($hierarchyTopID)) {
124                    $hierarchies[$hierarchyTopID] = $inHierarchiesTitle[$i] ?? '';
125                }
126            }
127            if (!empty($hierarchies)) {
128                return $hierarchies;
129            }
130        }
131
132        // If we got this far, we couldn't find valid match(es).
133        return false;
134    }
135
136    /**
137     * Render the Hierarchy Tree
138     *
139     * @param string  $context     The context from which the call has been made
140     * @param string  $mode        The mode in which the tree should be generated
141     * @param string  $hierarchyID The hierarchy ID of the tree to fetch (optional)
142     * @param ?string $selectedID  The current record ID (optional)
143     * @param array   $options     Additional options
144     *
145     * @return mixed The desired hierarchy tree output (or false on error)
146     */
147    public function render(
148        string $context,
149        string $mode,
150        string $hierarchyID,
151        ?string $selectedID = null,
152        array $options = []
153    ) {
154        if (empty($context)) {
155            return false;
156        }
157        if ($json = $this->getDataSource()->getJSON($hierarchyID)) {
158            $driver = $this->recordDriver;
159            $nodes = [json_decode($json)];
160            $this->augmentNodeData($nodes, $context, $selectedID);
161            return $this->viewRenderer->render(
162                'hierarchy/tree.phtml',
163                compact('nodes', 'context', 'hierarchyID', 'driver', 'selectedID', 'options')
164            );
165        }
166        return false;
167    }
168
169    /**
170     * Augment all nodes with 'hasSelectedChild' and 'href' for rendering.
171     *
172     * @param array   $nodes      Node list
173     * @param string  $context    Context
174     * @param ?string $selectedID Selected record ID
175     *
176     * @return bool Whether any items are applied (for recursive calls)
177     */
178    protected function augmentNodeData(array $nodes, string $context, ?string $selectedID): bool
179    {
180        $result = false;
181        foreach ($nodes as &$node) {
182            $node->hasSelectedChild = !empty($node->children)
183                && $this->augmentNodeData($node->children, $context, $selectedID);
184            if ($node->id === $selectedID || $node->hasSelectedChild) {
185                $result = true;
186            }
187            $node->href = $this->getContextualUrl($node, $context);
188        }
189        unset($node);
190        return $result;
191    }
192
193    /**
194     * Use the router to build the appropriate URL based on context
195     *
196     * @param object $node    JSON object of a node/top node
197     * @param string $context Record or Collection
198     *
199     * @return string
200     */
201    protected function getContextualUrl($node, $context)
202    {
203        $type = $node->type;
204        if ('collection' === $type && !$this->collectionsEnabled) {
205            $type = 'record';
206        }
207        $url = $this->getUrlFromRouteCache($type, $node->id);
208        return $type === 'collection'
209            ? $url . '#tabnav'
210            : $url;
211    }
212
213    /**
214     * Get the URL for a record and cache it to avoid the relatively slow routing
215     * calls.
216     *
217     * @param string $route Route
218     * @param string $id    Record ID
219     *
220     * @return string URL
221     */
222    protected function getUrlFromRouteCache($route, $id)
223    {
224        static $cache = [];
225        if (!isset($cache[$route])) {
226            $params = [
227                'id' => '__record_id__',
228                'tab' => 'HierarchyTree',
229            ];
230            $options = [
231                'query' => [
232                    'recordID' => '__record_id__',
233                ],
234            ];
235            $cache[$route] = $this->router->fromRoute(
236                $this->getRouteNameFromDataSource($route),
237                $params,
238                $options
239            );
240        }
241        return str_replace('__record_id__', urlencode($id), $cache[$route]);
242    }
243
244    /**
245     * Get route name from data source.
246     *
247     * @param string $route Route
248     *
249     * @return string
250     */
251    protected function getRouteNameFromDataSource($route)
252    {
253        if ($route === 'collection') {
254            return $this->getDataSource()->getCollectionRoute();
255        } elseif ($route === 'record') {
256            return $this->getDataSource()->getRecordRoute();
257        }
258        return $route;
259    }
260}