]> BookStack Code Mirror - bookstack/blob - app/Api/ListingResponseBuilder.php
Merge branch 'api-endpoint-users' into users_api
[bookstack] / app / Api / ListingResponseBuilder.php
1 <?php
2
3 namespace BookStack\Api;
4
5 use Illuminate\Database\Eloquent\Builder;
6 use Illuminate\Database\Eloquent\Collection;
7 use Illuminate\Http\Request;
8
9 class ListingResponseBuilder
10 {
11     protected $query;
12     protected $request;
13     protected $fields;
14     protected $hiddenFields;
15
16     protected $filterOperators = [
17         'eq'   => '=',
18         'ne'   => '!=',
19         'gt'   => '>',
20         'lt'   => '<',
21         'gte'  => '>=',
22         'lte'  => '<=',
23         'like' => 'like',
24     ];
25
26     /**
27      * ListingResponseBuilder constructor.
28      */
29     public function __construct(Builder $query, Request $request, array $fields, array $hiddenFields )
30     {
31         $this->query = $query;
32         $this->request = $request;
33         $this->fields = $fields;
34         $this->hiddenFields = $hiddenFields;
35     }
36
37     /**
38      * Get the response from this builder.
39      */
40     public function toResponse()
41     {
42         $filteredQuery = $this->filterQuery($this->query);
43
44         $total = $filteredQuery->count();
45         $data = $this->fetchData($filteredQuery);
46         $data = $data->makeVisible($this->hiddenFields);
47
48         return response()->json([
49             'data'  => $data,
50             'total' => $total,
51         ]);
52     }
53
54     /**
55      * Fetch the data to return in the response.
56      */
57     protected function fetchData(Builder $query): Collection
58     {
59         $query = $this->countAndOffsetQuery($query);
60         $query = $this->sortQuery($query);
61
62         return $query->get($this->fields);
63     }
64
65     /**
66      * Apply any filtering operations found in the request.
67      */
68     protected function filterQuery(Builder $query): Builder
69     {
70         $query = clone $query;
71         $requestFilters = $this->request->get('filter', []);
72         if (!is_array($requestFilters)) {
73             return $query;
74         }
75
76         $queryFilters = collect($requestFilters)->map(function ($value, $key) {
77             return $this->requestFilterToQueryFilter($key, $value);
78         })->filter(function ($value) {
79             return !is_null($value);
80         })->values()->toArray();
81
82         return $query->where($queryFilters);
83     }
84
85     /**
86      * Convert a request filter query key/value pair into a [field, op, value] where condition.
87      */
88     protected function requestFilterToQueryFilter($fieldKey, $value): ?array
89     {
90         $splitKey = explode(':', $fieldKey);
91         $field = $splitKey[0];
92         $filterOperator = $splitKey[1] ?? 'eq';
93
94         if (!in_array($field, $this->fields)) {
95             return null;
96         }
97
98         if (!in_array($filterOperator, array_keys($this->filterOperators))) {
99             $filterOperator = 'eq';
100         }
101
102         $queryOperator = $this->filterOperators[$filterOperator];
103
104         return [$field, $queryOperator, $value];
105     }
106
107     /**
108      * Apply sorting operations to the query from given parameters
109      * otherwise falling back to the first given field, ascending.
110      */
111     protected function sortQuery(Builder $query): Builder
112     {
113         $query = clone $query;
114         $defaultSortName = $this->fields[0];
115         $direction = 'asc';
116
117         $sort = $this->request->get('sort', '');
118         if (strpos($sort, '-') === 0) {
119             $direction = 'desc';
120         }
121
122         $sortName = ltrim($sort, '+- ');
123         if (!in_array($sortName, $this->fields)) {
124             $sortName = $defaultSortName;
125         }
126
127         return $query->orderBy($sortName, $direction);
128     }
129
130     /**
131      * Apply count and offset for paging, based on params from the request while falling
132      * back to system defined default, taking the max limit into account.
133      */
134     protected function countAndOffsetQuery(Builder $query): Builder
135     {
136         $query = clone $query;
137         $offset = max(0, $this->request->get('offset', 0));
138         $maxCount = config('api.max_item_count');
139         $count = $this->request->get('count', config('api.default_item_count'));
140         $count = max(min($maxCount, $count), 1);
141
142         return $query->skip($offset)->take($count);
143     }
144 }