Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
35.92% |
37 / 103 |
|
30.77% |
4 / 13 |
CRAP | |
0.00% |
0 / 1 |
RecordLinker | |
35.92% |
37 / 103 |
|
30.77% |
4 / 13 |
250.27 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
__invoke | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
related | |
0.00% |
0 / 27 |
|
0.00% |
0 / 1 |
42 | |||
getActionUrl | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
3 | |||
getRequestUrl | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
20 | |||
getTabUrl | |
94.74% |
18 / 19 |
|
0.00% |
0 / 1 |
3.00 | |||
getUrl | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getBreadcrumbHtml | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
getChildRecordSearchUrl | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
getVersionsSearchUrl | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
6 | |||
getSearchActionForSource | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getVersionsActionForSource | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getRecordUrlParams | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
4.13 |
1 | <?php |
2 | |
3 | /** |
4 | * Record linker view helper |
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 View_Helpers |
26 | * @author Demian Katz <demian.katz@villanova.edu> |
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 Wiki |
30 | */ |
31 | |
32 | namespace VuFind\View\Helper\Root; |
33 | |
34 | use VuFind\RecordDriver\AbstractBase as AbstractRecord; |
35 | |
36 | use function is_array; |
37 | use function is_string; |
38 | |
39 | /** |
40 | * Record linker view helper |
41 | * |
42 | * @category VuFind |
43 | * @package View_Helpers |
44 | * @author Demian Katz <demian.katz@villanova.edu> |
45 | * @author Ere Maijala <ere.maijala@helsinki.fi> |
46 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
47 | * @link https://vufind.org/wiki/development Wiki |
48 | */ |
49 | class RecordLinker extends \Laminas\View\Helper\AbstractHelper |
50 | { |
51 | /** |
52 | * Record router |
53 | * |
54 | * @var \VuFind\Record\Router |
55 | */ |
56 | protected $router; |
57 | |
58 | /** |
59 | * Search results (optional) |
60 | * |
61 | * @var \VuFind\Search\Base\Results |
62 | */ |
63 | protected $results = null; |
64 | |
65 | /** |
66 | * Cached record URLs |
67 | * |
68 | * @var array |
69 | */ |
70 | protected $cachedDriverUrls = []; |
71 | |
72 | /** |
73 | * Constructor |
74 | * |
75 | * @param \VuFind\Record\Router $router Record router |
76 | */ |
77 | public function __construct(\VuFind\Record\Router $router) |
78 | { |
79 | $this->router = $router; |
80 | } |
81 | |
82 | /** |
83 | * Store an optional results object and return this object so that the |
84 | * appropriate link can be rendered. |
85 | * |
86 | * @param ?\VuFind\Search\Base\Results $results Results object. |
87 | * |
88 | * @return RecordLinker |
89 | */ |
90 | public function __invoke($results = null) |
91 | { |
92 | $this->results = $results; |
93 | return $this; |
94 | } |
95 | |
96 | /** |
97 | * Given an array representing a related record (which may be a bib ID or OCLC |
98 | * number), this helper renders a URL linking to that record. |
99 | * |
100 | * @param array $link Link information from record model |
101 | * @param string $source Source ID for backend being used to retrieve records |
102 | * |
103 | * @return string URL derived from link information |
104 | */ |
105 | public function related($link, $source = DEFAULT_SEARCH_BACKEND) |
106 | { |
107 | $urlHelper = $this->getView()->plugin('url'); |
108 | $baseUrl = $urlHelper($this->getSearchActionForSource($source)); |
109 | switch ($link['type']) { |
110 | case 'bib': |
111 | return $baseUrl |
112 | . '?lookfor=' . urlencode($link['value']) |
113 | . '&type=id&jumpto=1'; |
114 | case 'dlc': |
115 | return $baseUrl |
116 | . '?lookfor=' . urlencode('"' . $link['value'] . '"') |
117 | . '&type=lccn&jumpto=1'; |
118 | case 'isn': |
119 | return $baseUrl |
120 | . '?join=AND&bool0[]=AND&lookfor0[]=%22' |
121 | . urlencode($link['value']) |
122 | . '%22&type0[]=isn&bool1[]=NOT&lookfor1[]=%22' |
123 | . urlencode($link['exclude']) |
124 | . '%22&type1[]=id&sort=title&view=list'; |
125 | case 'oclc': |
126 | return $baseUrl |
127 | . '?lookfor=' . urlencode($link['value']) |
128 | . '&type=oclc_num&jumpto=1'; |
129 | case 'title': |
130 | return $baseUrl |
131 | . '?lookfor=' . urlencode($link['value']) |
132 | . '&type=title'; |
133 | } |
134 | throw new \Exception('Unexpected link type: ' . $link['type']); |
135 | } |
136 | |
137 | /** |
138 | * Given a record driver, get a URL for that record. |
139 | * |
140 | * @param AbstractRecord|string $driver Record driver representing record |
141 | * to link to, or source|id pipe-delimited string |
142 | * @param string $action Record action to access |
143 | * @param array $query Optional query parameters |
144 | * @param string $anchor Optional anchor |
145 | * @param array $options Record URL parameter options (optional) |
146 | * |
147 | * @return string |
148 | */ |
149 | public function getActionUrl( |
150 | $driver, |
151 | $action, |
152 | $query = [], |
153 | $anchor = '', |
154 | $options = [] |
155 | ) { |
156 | // Build the URL: |
157 | $urlHelper = $this->getView()->plugin('url'); |
158 | $details = $this->router->getActionRouteDetails($driver, $action); |
159 | return $urlHelper( |
160 | $details['route'], |
161 | $details['params'] ?: [], |
162 | [ |
163 | 'query' => $this->getRecordUrlParams($options) + $query, |
164 | 'fragment' => $anchor ? ltrim($anchor, '#') : '', |
165 | 'normalize_path' => false, // required to keep slashes encoded |
166 | ] |
167 | ); |
168 | } |
169 | |
170 | /** |
171 | * Given a string or array of parts, build a request (e.g. hold) URL. |
172 | * |
173 | * @param string|array $url URL to process |
174 | * @param bool $includeAnchor Should we include an anchor? |
175 | * |
176 | * @return string |
177 | */ |
178 | public function getRequestUrl($url, $includeAnchor = true) |
179 | { |
180 | if (is_array($url)) { |
181 | // Assemble URL string from array parts: |
182 | $source = $url['source'] ?? DEFAULT_SEARCH_BACKEND; |
183 | parse_str($url['query'] ?? '', $query); |
184 | $finalUrl = $this->getActionUrl( |
185 | "{$source}|" . $url['record'], |
186 | $url['action'], |
187 | $query, |
188 | $includeAnchor ? ($url['anchor'] ?? '') : '' |
189 | ); |
190 | } else { |
191 | // If URL is already a string but we don't want anchors, strip |
192 | // the anchor now: |
193 | if (!$includeAnchor) { |
194 | [$finalUrl] = explode('#', $url); |
195 | } else { |
196 | $finalUrl = $url; |
197 | } |
198 | } |
199 | return $finalUrl; |
200 | } |
201 | |
202 | /** |
203 | * Given a record driver, get a URL for that record. |
204 | * |
205 | * @param AbstractRecord|string $driver Record driver representing record to |
206 | * link to, or source|id pipe-delimited string |
207 | * @param ?string $tab Optional record tab to access |
208 | * @param array $query Optional query params |
209 | * @param array $options Any additional options: |
210 | * - excludeSearchId (default: false) |
211 | * |
212 | * @return string |
213 | */ |
214 | public function getTabUrl($driver, $tab = null, $query = [], $options = []) |
215 | { |
216 | $driverId = is_string($driver) |
217 | ? $driver |
218 | : ($driver->getSourceIdentifier() . '|' . $driver->getUniqueID()); |
219 | $cacheKey = md5( |
220 | $driverId . '|' . ($tab ?? '-') . '|' . var_export($query, true) |
221 | . var_export($options, true) |
222 | ); |
223 | if (!isset($this->cachedDriverUrls[$cacheKey])) { |
224 | // Build the URL: |
225 | $urlHelper = $this->getView()->plugin('url'); |
226 | $details = $this->router->getTabRouteDetails($driver, $tab, $query); |
227 | $this->cachedDriverUrls[$cacheKey] = $urlHelper( |
228 | $details['route'], |
229 | $details['params'], |
230 | array_merge_recursive( |
231 | $details['options'] ?? [], |
232 | ['query' => $this->getRecordUrlParams($options)] |
233 | ) |
234 | ); |
235 | } |
236 | return $this->cachedDriverUrls[$cacheKey]; |
237 | } |
238 | |
239 | /** |
240 | * Get the default URL for a record. |
241 | * |
242 | * @param AbstractRecord|string $driver Record driver representing record to |
243 | * link to, or source|id pipe-delimited string |
244 | * @param array $options Any additional options: |
245 | * - excludeSearchId (default: false) |
246 | * |
247 | * @return string |
248 | */ |
249 | public function getUrl($driver, $options = []) |
250 | { |
251 | return $this->getTabUrl($driver, null, [], $options); |
252 | } |
253 | |
254 | /** |
255 | * Given a record driver, generate HTML to link to the record from breadcrumbs. |
256 | * |
257 | * @param AbstractRecord $driver Record to link to. |
258 | * |
259 | * @return string |
260 | */ |
261 | public function getBreadcrumbHtml($driver) |
262 | { |
263 | $truncateHelper = $this->getView()->plugin('truncate'); |
264 | $escapeHelper = $this->getView()->plugin('escapeHtml'); |
265 | return '<a href="' . $this->getUrl($driver) . '">' . |
266 | $escapeHelper($truncateHelper($driver->getBreadcrumb(), 30)) |
267 | . '</a>'; |
268 | } |
269 | |
270 | /** |
271 | * Given a record driver, generate a URL to fetch all child records for it. |
272 | * |
273 | * @param AbstractRecord $driver Host Record. |
274 | * |
275 | * @return string |
276 | */ |
277 | public function getChildRecordSearchUrl($driver) |
278 | { |
279 | $urlHelper = $this->getView()->plugin('url'); |
280 | $route = $this->getSearchActionForSource($driver->getSourceIdentifier()); |
281 | return $urlHelper($route) |
282 | . '?lookfor=' |
283 | . urlencode(addcslashes($driver->getUniqueID(), '"')) |
284 | . '&type=ParentID'; |
285 | } |
286 | |
287 | /** |
288 | * Return search URL for all versions |
289 | * |
290 | * @param AbstractRecord $driver Record driver |
291 | * |
292 | * @return string |
293 | */ |
294 | public function getVersionsSearchUrl($driver) |
295 | { |
296 | $route = $this->getVersionsActionForSource($driver->getSourceIdentifier()); |
297 | if (false === $route) { |
298 | return ''; |
299 | } |
300 | |
301 | $urlParams = [ |
302 | 'id' => $driver->getUniqueID(), |
303 | 'search' => 'versions', |
304 | ]; |
305 | |
306 | $urlHelper = $this->getView()->plugin('url'); |
307 | return $urlHelper($route, [], ['query' => $urlParams]); |
308 | } |
309 | |
310 | /** |
311 | * Given a record source ID, return the route name for searching its backend. |
312 | * |
313 | * @param string $source Record source identifier. |
314 | * |
315 | * @return string |
316 | */ |
317 | protected function getSearchActionForSource($source) |
318 | { |
319 | $optionsHelper = $this->getView()->plugin('searchOptions'); |
320 | return $optionsHelper($source)->getSearchAction(); |
321 | } |
322 | |
323 | /** |
324 | * Given a record source ID, return the route name for version search with its |
325 | * backend. |
326 | * |
327 | * @param string $source Record source identifier. |
328 | * |
329 | * @return string|bool |
330 | */ |
331 | protected function getVersionsActionForSource($source) |
332 | { |
333 | $optionsHelper = $this->getView()->plugin('searchOptions'); |
334 | return $optionsHelper($source)->getVersionsAction(); |
335 | } |
336 | |
337 | /** |
338 | * Get query parameters for a record URL |
339 | * |
340 | * @param array $options Any additional options: |
341 | * - excludeSearchId (default: false) |
342 | * |
343 | * @return array |
344 | */ |
345 | protected function getRecordUrlParams(array $options = []): array |
346 | { |
347 | if (!empty($options['excludeSearchId'])) { |
348 | return []; |
349 | } |
350 | $sid = ($this->results ? $this->results->getSearchId() : null) |
351 | ?? $this->getView()->plugin('searchMemory')->getLastSearchId(); |
352 | return $sid ? compact('sid') : []; |
353 | } |
354 | } |