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