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