1 <?php
2
3 namespace Docolight\Support;
4
5 use Exception;
6 use CDataProvider;
7 use Docolight\Support\Fluent;
8 use Docolight\Support\Collection;
9
10 /**
11 * A Data Provider with `Docolight\Support\Collection` as it's data source.
12 *
13 * @author Krisan Alfa Timur <krisanalfa@docotel.co.id>
14 */
15 class CollectionDataProvider extends CDataProvider
16 {
17 /**
18 * Collection file
19 *
20 * @var \Docolight\Support\Collection
21 */
22 protected $collection = null;
23
24 /**
25 * Free text field
26 *
27 * @var array
28 */
29 protected $freeTextField = array();
30
31 /**
32 * Class construction.
33 *
34 * @param \Docolight\Support\Collection $collection
35 * @param array $freeTextField
36 */
37 public function __construct(Collection $collection, array $freeTextField = array())
38 {
39 // This contains a list of field that can perform such a free text search
40 $this->freeTextField = $freeTextField;
41
42 $first = $collection->first();
43
44 if (! $first instanceof Fluent) {
45 throw new Exception("The items in Collection must be an instance of \Docolight\Support\Fluent.");
46 }
47
48 // Let's create a bare sort
49 $sort = $this->getSort();
50
51 // Set a list of attributes that available to be sorted
52 $sort->attributes = array_keys($first->attributes());
53
54 // Set the sort object
55 $this->setSort($sort);
56
57 $collectionClassName = get_class($collection);
58
59 // Is there any filter from request?
60 $filter = request($collectionClassName, array());
61
62 // In Firefox, backslash (\) from Namespace is converted to underscore (_)
63 $additionalFilter = request(str_replace('\\', '_', $collectionClassName), array());
64
65 // Let's merge them together
66 $filter = array_filter(array_merge($filter, $additionalFilter));
67
68 // Make a collection, filter it first if we got a filter request
69 $this->collection = (! empty($filter)) ? $this->filterCollection($collection, $filter) : $collection;
70
71 $sortRequest = request('sort');
72
73 // This is it, a sort request
74 if ($sortRequest !== null) {
75 // Get the field name we want to sort
76 $fieldToSort = head(explode('.', $sortRequest));
77
78 // Determine whether request is a ascending sort or descending one
79 $isDescending = (trimtolower(last(explode('.', $sortRequest))) === 'desc');
80
81 // Assign new collection instance
82 $this->collection = $this->collection->sortBy($fieldToSort, SORT_REGULAR, $isDescending);
83 }
84 }
85
86 /**
87 * Get collection
88 *
89 * @return \Docolight\Support\Collection
90 */
91 public function getCollection()
92 {
93 return $this->collection;
94 }
95
96 /**
97 * Fetches the data from the persistent data storage.
98 *
99 * @return array list of data items
100 */
101 protected function fetchData()
102 {
103 // Get our pagination object
104 $pagination = $this->getPagination();
105
106 // Default pagination current page
107 $currentPage = (int) request($pagination->pageVar, 1);
108
109 // Return an offset array
110 return $this->collection->forPage($currentPage, $pagination->getPageSize())->all();
111 }
112
113 /**
114 * Calculates the total number of data items.
115 *
116 * @return int the total number of data items.
117 */
118 protected function calculateTotalItemCount()
119 {
120 // Count all
121 $total = $this->collection->count();
122
123 // Make a pagination
124 $pagination = $this->getPagination();
125
126 // Set total item in pagination
127 $pagination->setItemCount($total);
128
129 // Set a new pagination instance
130 $this->setPagination($pagination);
131
132 return $total;
133 }
134
135 /**
136 * Fetches the data item keys from the persistent data storage.
137 *
138 * @return array list of data item keys.
139 */
140 protected function fetchKeys()
141 {
142 return ($this->collection->first() instanceof Fluent) ? array_keys($this->collection->first()->attributes()) : array();
143 }
144
145 /**
146 * Filter collection by criteria
147 *
148 * @param \Docolight\Support\Collection $collection
149 * @param array $filter
150 *
151 * @return \Docolight\Support\Collection
152 */
153 protected function filterCollection(Collection $collection, array $filter)
154 {
155 // Get free text field value
156 $freeTextField = $this->freeTextField;
157
158 // Perform filter using a callback, see \Docolight\Support\Collection::filter method
159 $collection = $collection->filter(function ($item) use ($filter, $freeTextField) {
160 // Checking items, it should be an instance of Fluent
161 if (! $item instanceof Fluent) {
162 throw new Exception("The items in Collection must be an instance of \Docolight\Support\Fluent.");
163 }
164
165 // We should separate between normal field and free text field
166 $attributes = array_except($item->attributes(), $freeTextField);
167
168 // We compare this filter with the attributes (attributes without freetext field value)
169 $withoutFreeText = array_except($filter, $freeTextField);
170
171 // We will compare this value to attributes if user performs free text search
172 $freeTextCriteria = array_only($filter, $freeTextField);
173
174 // The basic search, we compare the attributes that doesn't have ability to perform free text search
175 $basicSearch = (empty($withoutFreeText)) ? true : !empty(array_intersect_assoc($withoutFreeText, $attributes));
176
177 // Some variable to deterimine the result of free text search
178 $freeTextSearchMode = false;
179 $matchesCounter = 0;
180
181 // Free text search performed
182 if (!empty($freeTextCriteria)) {
183 $freeTextSearchMode = true;
184
185 // Loop for each filter, but only for the free text field
186 foreach ($freeTextCriteria as $itemField => $expectation) {
187 // If the value is contains our expectation, let's add the matches counter
188 if (str_contains(mb_strtolower($item->{$itemField}), trimtolower($expectation))) {
189 $matchesCounter++;
190 }
191 }
192 }
193
194 // Here we check, if free text search has been performed, the matches counter should be the same size with
195 // free text criteria. Means, all criteria should be match in the item, we also check basic search to maintain
196 // document validity.
197 return ($freeTextSearchMode) ? (($matchesCounter === count($freeTextCriteria)) and $basicSearch) : $basicSearch;
198 });
199
200 // Set the filter to the collection, so it'll be shown in filter text field
201 $collection->setFilters($filter);
202
203 return $collection;
204 }
205 }
206