Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.38% covered (success)
95.38%
62 / 65
50.00% covered (danger)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
FacetFormatter
95.38% covered (success)
95.38%
62 / 65
50.00% covered (danger)
50.00%
2 / 4
30
0.00% covered (danger)
0.00%
0 / 1
 buildFacetFilters
81.82% covered (warning)
81.82%
9 / 11
0.00% covered (danger)
0.00%
0 / 1
5.15
 matchFacetItem
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
5
 buildFacetValues
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
9
 format
96.30% covered (success)
96.30%
26 / 27
0.00% covered (danger)
0.00%
0 / 1
11
1<?php
2
3/**
4 * Facet formatter for API responses
5 *
6 * PHP version 8
7 *
8 * Copyright (C) The National Library of Finland 2015-2016.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2,
12 * as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.    See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22 *
23 * @category VuFind
24 * @package  API_Formatter
25 * @author   Ere Maijala <ere.maijala@helsinki.fi>
26 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
27 * @link     https://vufind.org/wiki/development:plugins:controllers Wiki
28 */
29
30namespace VuFindApi\Formatter;
31
32use VuFind\Search\Base\Results;
33
34use function in_array;
35
36/**
37 * Facet formatter for API responses
38 *
39 * @category VuFind
40 * @package  API_Formatter
41 * @author   Ere Maijala <ere.maijala@helsinki.fi>
42 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
43 * @link     https://vufind.org/wiki/development:plugins:controllers Wiki
44 */
45class FacetFormatter extends BaseFormatter
46{
47    /**
48     * Build an array of facet filters from the request params
49     *
50     * @param array $request Request params
51     *
52     * @return array
53     */
54    protected function buildFacetFilters($request)
55    {
56        $facetFilters = [];
57        if (isset($request['facetFilter'])) {
58            foreach ($request['facetFilter'] as $filter) {
59                [$facetField, $regex] = explode(':', $filter, 2);
60                $regex = trim($regex);
61                if (str_starts_with($regex, '"')) {
62                    $regex = substr($regex, 1);
63                }
64                if (str_ends_with($regex, '"')) {
65                    $regex = substr($regex, 0, -1);
66                }
67                $facetFilters[$facetField][] = $regex;
68            }
69        }
70        return $facetFilters;
71    }
72
73    /**
74     * Match a facet item with the filters.
75     *
76     * @param array $facet   Facet
77     * @param array $filters Facet filters
78     *
79     * @return boolean
80     */
81    protected function matchFacetItem($facet, $filters)
82    {
83        $discard = true;
84        array_walk_recursive(
85            $facet,
86            function ($item, $key) use (&$discard, $filters) {
87                if ($discard && $key == 'value') {
88                    foreach ($filters as $filter) {
89                        $pattern = '/' . addcslashes($filter, '/') . '/';
90                        if (preg_match($pattern, $item) === 1) {
91                            $discard = false;
92                            break;
93                        }
94                    }
95                }
96            }
97        );
98        return !$discard;
99    }
100
101    /**
102     * Recursive function to create a facet value list for a single facet
103     *
104     * @param array $list    Facet items
105     * @param array $filters Facet filters
106     *
107     * @return array
108     */
109    protected function buildFacetValues($list, $filters = false)
110    {
111        $result = [];
112        $fields = [
113            'value', 'displayText', 'count',
114            'children', 'href', 'isApplied',
115        ];
116        foreach ($list as $value) {
117            $resultValue = [];
118            if ($filters && !$this->matchFacetItem($value, $filters)) {
119                continue;
120            }
121
122            foreach ($value as $key => $item) {
123                if (!in_array($key, $fields)) {
124                    continue;
125                }
126                if ($key == 'children') {
127                    if (!empty($item)) {
128                        $resultValue[$key]
129                            = $this->buildFacetValues(
130                                $item,
131                                $filters
132                            );
133                    }
134                } else {
135                    if ($key == 'displayText') {
136                        $key = 'translated';
137                    }
138                    $resultValue[$key] = $item;
139                }
140            }
141            $result[] = $resultValue;
142        }
143        return $result;
144    }
145
146    /**
147     * Create the result facet list
148     *
149     * @param array   $request               Request parameters
150     * @param Results $results               Search results
151     * @param array   $hierarchicalFacetData Hierarchical facet data
152     *
153     * @return array
154     */
155    public function format($request, Results $results, $hierarchicalFacetData)
156    {
157        if ($results->getResultTotal() <= 0 || empty($request['facet'])) {
158            return [];
159        }
160
161        $filters = $this->buildFacetFilters($request);
162        $facets = $results->getFacetList();
163
164        // Format hierarchical facets, if any
165        if ($hierarchicalFacetData) {
166            foreach ($hierarchicalFacetData as $facet => $data) {
167                $facets[$facet]['list'] = $data;
168            }
169        }
170
171        // Add "missing" fields to non-hierarchical facets to make them similar
172        // to hierarchical facets for easier consumption.
173        $urlHelper = $results->getUrlQuery();
174        foreach ($facets as $facetKey => &$facetItems) {
175            if (isset($hierarchicalFacetData[$facetKey])) {
176                continue;
177            }
178
179            foreach ($facetItems['list'] as &$item) {
180                $href = !$item['isApplied']
181                    ? $urlHelper->addFacet(
182                        $facetKey,
183                        $item['value'],
184                        $item['operator']
185                    )->getParams(false) : $urlHelper->getParams(false);
186                $item['href'] = $href;
187            }
188        }
189        $this->filterArrayValues($facets);
190
191        $result = [];
192        foreach ($facets as $facetName => $facetData) {
193            $result[$facetName] = $this->buildFacetValues(
194                $facetData['list'],
195                !empty($filters[$facetName]) ? $filters[$facetName] : false
196            );
197        }
198        return $result;
199    }
200}