4
4
use Cake \Collection \Collection ;
5
5
use Cake \Controller \Component ;
6
6
use Cake \Core \Configure ;
7
+ use Cake \Network \Exception \BadRequestException ;
7
8
use Cake \ORM \Query ;
8
9
use Cake \ORM \Table ;
9
10
use Cake \ORM \TableRegistry ;
@@ -77,104 +78,135 @@ private function _draw()
77
78
* Process query data of ajax request regarding order
78
79
* Alters $options if delegateOrder is set
79
80
* In this case, the model needs to handle the 'customOrder' option.
80
- * @param $options: Query options from the request
81
+ * @param $options: Query options
82
+ * @param $columns: Column definitions
81
83
*/
82
- private function _order (array &$ options )
84
+ private function _order (array &$ options, array & $ columns )
83
85
{
84
86
if (empty ($ this ->request ->query ['order ' ]))
85
87
return ;
86
88
87
- // -- add custom order
88
89
$ order = $ this ->config ('order ' );
89
- foreach ($ this ->request ->query ['order ' ] as $ item ) {
90
- $ order [$ this ->request ->query ['columns ' ][$ item ['column ' ]]['name ' ]] = $ item ['dir ' ];
90
+
91
+ /* extract custom ordering from request */
92
+ foreach ($ this ->request ->query ['order ' ] as $ item ) {
93
+ if (empty ($ columns ))
94
+ throw new \InvalidArgumentException ('Column ordering requested, but no column definitions provided. ' );
95
+
96
+ $ dir = strtoupper ($ item ['dir ' ]);
97
+ if (!in_array ($ dir , ['ASC ' , 'DESC ' ]))
98
+ throw new BadRequestException ('Malformed order direction. ' );
99
+
100
+ $ c = $ columns [$ item ['column ' ]] ?? null ;
101
+ if (!$ c || !($ c ['orderable ' ] ?? true )) // orderable is true by default
102
+ throw new BadRequestException ('Illegal column ordering. ' );
103
+
104
+ if (empty ($ c ['field ' ]))
105
+ throw new \InvalidArgumentException ('Column description misses field name. ' );
106
+
107
+ $ order [$ c ['field ' ]] = $ dir ;
91
108
}
109
+
110
+ /* apply ordering */
92
111
if (!empty ($ options ['delegateOrder ' ])) {
93
112
$ options ['customOrder ' ] = $ order ;
94
113
} else {
95
114
$ this ->config ('order ' , $ order );
96
115
}
97
116
98
- // -- remove default ordering as we have a custom one
117
+ /* remove default ordering in favor of our custom one */
99
118
unset($ options ['order ' ]);
100
119
}
101
120
102
121
/**
103
122
* Process query data of ajax request regarding filtering
104
123
* Alters $options if delegateSearch is set
105
124
* In this case, the model needs to handle the 'globalSearch' option.
106
- * @param $options: Query options from the request
107
- * @return: returns true if additional filtering takes place
125
+ * @param $options: Query options
126
+ * @param $columns: Column definitions
127
+ * @return: true if additional filtering takes place
108
128
*/
109
- private function _filter (array &$ options ) : bool
129
+ private function _filter (array &$ options, array & $ columns ) : bool
110
130
{
111
- // -- add limit
131
+ /* add limit and offset */
112
132
if (!empty ($ this ->request ->query ['length ' ])) {
113
133
$ this ->config ('length ' , $ this ->request ->query ['length ' ]);
114
134
}
115
-
116
- // -- add offset
117
135
if (!empty ($ this ->request ->query ['start ' ])) {
118
136
$ this ->config ('start ' , (int )$ this ->request ->query ['start ' ]);
119
137
}
120
138
121
- // -- don't support any search if columns data missing
122
- if (empty ($ this ->request ->query ['columns ' ]))
123
- return false ;
124
-
125
- // -- check table search field
126
139
$ haveFilters = false ;
140
+ $ delegateSearch = !empty ($ options ['delegateSearch ' ]);
141
+
142
+ /* add global filter (general search field) */
127
143
$ globalSearch = $ this ->request ->query ['search ' ]['value ' ] ?? false ;
144
+ if ($ globalSearch ) {
145
+ if (empty ($ columns ))
146
+ throw new \InvalidArgumentException ('Filtering requested, but no column definitions provided. ' );
128
147
129
- // -- if delegate option set, defer search to the model and bail out
130
- if (!empty ($ options ['delegateSearch ' ])) {
131
- if ($ globalSearch ) {
148
+ if ($ delegateSearch ) {
132
149
$ options ['globalSearch ' ] = $ globalSearch ;
133
150
$ haveFilters = true ;
134
- }
135
- foreach ($ this ->request ->query ['columns ' ] as $ column ) {
136
- $ localSearch = $ column ['search ' ]['value ' ];
137
- if (!empty ($ localSearch )) {
138
- $ options ['localSearch ' ][$ column ['name ' ]] = $ localSearch ;
151
+ } else {
152
+ foreach ($ columns as $ c ) {
153
+ if (!($ c ['searchable ' ] ?? true )) // searchable is true by default
154
+ continue ;
155
+
156
+ if (empty ($ c ['field ' ]))
157
+ throw new \InvalidArgumentException ('Column description misses field name. ' );
158
+
159
+ $ this ->_addCondition ($ c ['field ' ], $ globalSearch , 'or ' );
139
160
$ haveFilters = true ;
140
161
}
141
162
}
142
- return $ haveFilters ;
143
163
}
144
164
145
- // -- add conditions for both table-wide and column search fields
146
- foreach ($ this ->request ->query ['columns ' ] as $ column ) {
147
- if ($ globalSearch && $ column ['searchable ' ] == 'true ' ) {
148
- $ this ->_addCondition ($ column ['name ' ], $ globalSearch , 'or ' );
149
- $ haveFilters = true ;
150
- }
151
- $ localSearch = $ column ['search ' ]['value ' ];
165
+ /* add local filters (column search fields) */
166
+ foreach ($ this ->request ->query ['columns ' ] ?? [] as $ index => $ column ) {
167
+ $ localSearch = $ column ['search ' ]['value ' ] ?? null ;
152
168
if (!empty ($ localSearch )) {
153
- $ this ->_addCondition ($ column ['name ' ], $ column ['search ' ]['value ' ]);
169
+ if (empty ($ columns ))
170
+ throw new \InvalidArgumentException ('Filtering requested, but no column definitions provided. ' );
171
+
172
+ $ c = $ columns [$ index ] ?? null ;
173
+ if (!$ c || !($ c ['searchable ' ] ?? true )) // searchable is true by default
174
+ throw new BadRequestException ('Illegal filter request. ' );
175
+
176
+ if (empty ($ c ['field ' ]))
177
+ throw new \InvalidArgumentException ('Column description misses field name. ' );
178
+
179
+ if ($ delegateSearch ) {
180
+ $ options ['localSearch ' ][$ c ['field ' ]] = $ localSearch ;
181
+ } else {
182
+ $ this ->_addCondition ($ c ['field ' ], $ localSearch );
183
+ }
154
184
$ haveFilters = true ;
155
185
}
156
186
}
187
+
157
188
return $ haveFilters ;
158
189
}
159
190
160
191
/**
161
192
* Find data
162
193
*
163
- * @param $tableName
164
- * @param $finder
165
- * @param array $options
166
- * @return array|\Cake\ORM\Query
194
+ * @param $tableName: ORM table name
195
+ * @param $finder: Finder name (as in Table::find())
196
+ * @param array $options: Finder options (as in Table::find())
197
+ * @param array $columns: Column definitions needed for filter/order operations
198
+ * @return Query to be evaluated (Query::count() may have already been called)
167
199
*/
168
- public function find ($ tableName , $ finder = 'all ' , array $ options = [])
200
+ public function find (string $ tableName , string $ finder = 'all ' , array $ options = [], array $ columns = []) : Query
169
201
{
170
- $ delegateSearch = ! empty ( $ options ['delegateSearch ' ]) ;
202
+ $ delegateSearch = $ options ['delegateSearch ' ] ?? false ;
171
203
172
204
// -- get table object
173
205
$ this ->_table = TableRegistry::get ($ tableName );
174
206
175
207
// -- process draw & ordering options
176
208
$ this ->_draw ();
177
- $ this ->_order ($ options );
209
+ $ this ->_order ($ options, $ columns );
178
210
179
211
// -- call table's finder w/o filters
180
212
$ data = $ this ->_table ->find ($ finder , $ options );
@@ -183,10 +215,10 @@ public function find($tableName, $finder = 'all', array $options = [])
183
215
$ this ->_viewVars ['recordsTotal ' ] = $ data ->count ();
184
216
185
217
// -- process filter options
186
- $ filters = $ this ->_filter ($ options );
218
+ $ haveFilters = $ this ->_filter ($ options, $ columns );
187
219
188
220
// -- apply filters
189
- if ($ filters ) {
221
+ if ($ haveFilters ) {
190
222
if ($ delegateSearch ) {
191
223
// call finder again to process filters (provided in $options)
192
224
$ data = $ this ->_table ->find ($ finder , $ options );
0 commit comments