PluginProbe
Filter Everything — WordPress & WooCommerce Filters / 1.4.9
Filter Everything — WordPress & WooCommerce Filters v1.4.9
1.9.6 1.9.5 1.9.4 1.9.3 1.9.2.2 1.9.2.1 trunk 1.2.1 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 1.4.1 1.4.4 1.4.5 1.4.8 1.4.9 1.5.0 1.5.1 1.6.0 1.6.1 1.6.2 1.6.3 All 51 releases
filter-everything / src / RequestParser.php

RequestParser.php in Filter Everything — WordPress & WooCommerce Filters 1.4.9, at src/RequestParser.php

438 lines 14.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FilterEverything\Filter;
4
5 if ( ! defined('WPINC') ) {
6 wp_die();
7 }
8
9 class RequestParser
10 {
11 private $request;
12
13 private $queryVars = [];
14
15 private $separator;
16
17 public function __construct( $request )
18 {
19 $this->setRequest( $request );
20 $this->separator = FLRT_PREFIX_SEPARATOR;
21 }
22
23 private function initQueryVars(){
24 // Setup default $queryVars
25 $this->queryVars = array(
26 'queried_values' => [],
27 'segments_order' => [],
28 'wpc_logic_separators' => [],
29 'non_filter_segments' => [],
30 'error' => '',
31 );
32 }
33
34 public function getQueryVars(){
35 $this->initQueryVars();
36 $this->parseRequest();
37 $this->validateQueryVars();
38 return $this->queryVars;
39 }
40
41 private function isSlugInRequest( $slug ){
42 return ( $this->isSlugInQuery( $slug ) || $this->isSlugInPath( $slug ) );
43 }
44
45 public function detectFilterRequest(){
46 $em = Container::instance()->getEntityManager();
47 foreach ( $em->getGlobalConfiguredSlugs() as $entity_slug ){
48 if( $this->isSlugInRequest( $entity_slug ) ){
49 return true;
50 }
51 }
52 return false;
53 }
54
55 private function isSlugInPath( $entity_slug ){
56 if( mb_strpos( '/' . $this->request, '/' . $entity_slug . $this->separator ) !== false ){
57 return true;
58 }
59 return false;
60 }
61
62 private function isSlugInQuery( $entity_slug ){
63 if(
64 ( $this->queryStringParam( 'max_' . $entity_slug ) !== false )
65 ||
66 ( $this->queryStringParam( 'min_' . $entity_slug ) !== false )
67 ||
68 ( $this->queryStringParam( $entity_slug ) !== false )
69 ){
70 return true;
71 }
72 return false;
73 }
74
75 /**
76 * Result always must be checked for !== false because 0 may be returned
77 * @param $key [max_{slug}|min_{slug}|{slug}]
78 * @return false|mixed
79 */
80 public function queryStringParam( $key )
81 {
82 $container = Container::instance();
83 $em = $container->getEntityManager();
84 $get = $container->getTheGet();
85 $post = $container->getThePost();
86
87 if( isset( $post['flrt_ajax_link'] ) ){
88 $parts = parse_url( $post['flrt_ajax_link'] );
89
90 if( isset( $parts['query'] ) ){
91 parse_str( $parts['query'], $output );
92 if( isset( $output[$key] ) ){
93 return $this->urlEncodeGetValues( $output[$key] );
94 }
95 }
96 }
97
98 if( isset( $get[$key] ) ){
99 return $this->urlEncodeGetValues( $get[$key] );
100 }
101
102 return false;
103 }
104
105 private function urlEncodeGetValues( $values )
106 {
107 $queriedValues = explode( FLRT_QUERY_TERMS_SEPARATOR, $values );
108 $queriedValues = array_map( 'urlencode', $queriedValues );
109 $queriedValues = array_map( 'mb_strtolower', $queriedValues );
110
111 return implode(FLRT_QUERY_TERMS_SEPARATOR, $queriedValues);
112 }
113
114 private function extractValuesFromQuery( $slug ){
115 $em = Container::instance()->getEntityManager();
116 $filter = $em->getFilterBySlug( $slug, array( 'entity' ) );
117
118 $values = [];
119 // Matches numbers and decimal separator
120 $regexp = '/^([\-]?\d+(?:[\.\,]\d{1,})?)$/';
121
122 if( ( $this->queryStringParam( 'max_' . $slug ) !== false ) && $filter['entity'] === 'post_meta_num' ){
123 preg_match($regexp, $this->queryStringParam( 'max_' . $slug ), $output);
124 $values['max'] = isset( $output[1] ) ? $output[1] : false;
125 }
126
127 if( ( $this->queryStringParam( 'min_' . $slug ) !== false ) && $filter['entity'] === 'post_meta_num' ){
128 preg_match($regexp, $this->queryStringParam( 'min_' . $slug ), $output);
129 $values['min'] = isset( $output[1] ) ? $output[1] : false;
130 }
131
132 if( ( $this->queryStringParam( $slug ) !== false ) ){
133 if( $filter['entity'] !== 'post_meta_num' ){
134 $params = $this->queryStringParam( $slug );
135 $values = $this->extractQueriedValuesFromQueryString( $params, $slug );
136 }else{
137 $values[] = $this->queryStringParam( $slug );
138 }
139
140 }
141
142 unset($em);
143
144 return $values;
145 }
146
147 private function set_404( $message = '' ){
148 $this->queryVars['error'] = '404';
149 if( $message && FLRT_PLUGIN_DEBUG ){
150 echo esc_html( $message );
151 }
152 }
153
154 public function getRequest(){
155 return $this->request;
156 }
157
158 public function setRequest( $request ){
159 // $this->request = urldecode( strtolower( trim( $request, '/' ) ) );
160 $this->request = strtolower( trim( $request, '/' ) );
161 }
162
163 /**
164 * @return array
165 */
166 private function getPathSegments(){
167 if( $this->request ){
168 return explode('/', $this->request );
169 }
170 return [];
171 }
172
173 public function cleanUpRequestPathFromFilterSegments( $request_path ){
174 // Otherwise it will be URL encoded and uppercase
175 $request_path = strtolower($request_path);
176
177 foreach( $this->getPathSegments() as $segment ){
178 if( $this->checkSlugInSegmentForCleaningNativePath( $segment ) ){
179 /**
180 *@improvement Maybe remove query_args also
181 */
182 $request_path = str_replace('/' . $segment, '', $request_path );
183 }
184 }
185
186 return $request_path;
187 }
188
189 public function parseRequest(){
190 /**
191 * @bug this method fires twice.
192 */
193 $pathSegments = $this->getPathSegments();
194 $em = Container::instance()->getEntityManager();
195 $fse = Container::instance()->getFilterService();
196 // Path values
197 foreach( $pathSegments as $segment ){
198
199 if( $slug = $this->getSlugFromSegment( $segment ) ){
200 $segmentParams = $this->cutParamsFromSegment( $segment, $slug );
201 // List of entity, e_name, slug should be unique for all filters
202 $filter_entity = $em->getFilterBySlug( $slug, array('entity', 'e_name', 'slug', 'in_path') );
203
204 $filter_entity['values'] = $this->extractQueriedValuesFromSegment( $segmentParams, $slug );
205 $filter_entity['founded_in_path'] = 'yes';
206 $this->queryVars['queried_values'][$slug] = $filter_entity;
207
208 $order_element = $fse->getEntityFullName( $filter_entity['entity'], $filter_entity['e_name'] );
209
210 $this->queryVars['segments_order'][] = $order_element;
211 } else {
212 $this->queryVars['non_filter_segments'][] = $segment;
213 }
214 }
215
216 // Query string values
217 foreach( $em->getConfiguredQuerySlugs() as $slug ){
218
219 if( $this->isSlugInQuery( $slug ) ){
220 $filter_entity = $em->getFilterBySlug( $slug, array('entity', 'e_name', 'slug', 'in_path') );
221 $filter_entity['values'] = $this->extractValuesFromQuery( $slug );
222 $filter_entity['founded_in_path'] = 'no';
223 $this->queryVars['queried_values'][$slug] = $filter_entity;
224 }
225 }
226
227 unset($em, $fse);
228 }
229
230 private function checkSlugInSegmentForCleaningNativePath( $segment ){
231 $em = Container::instance()->getEntityManager();
232 foreach( $em->getConfiguredPathSlugs() as $key => $slug ){
233 if( mb_strpos( $segment, $slug . $this->separator ) === 0 ){
234 return $slug;
235 }
236 }
237 return false;
238 }
239
240 private function getSlugFromSegment( $segment ){
241 $em = Container::instance()->getEntityManager();
242 foreach( $em->getConfiguredPathSlugs() as $key => $slug ){
243 if( mb_strpos( $segment, $slug . $this->separator ) === 0 ){
244 return $slug;
245 }
246 }
247 return false;
248 }
249
250 private function cutParamsFromSegment( $segment, $slug ){
251 return mb_substr( $segment, mb_strlen( $slug . $this->separator ) );
252 }
253
254 private function checkValuesOrder( $segmentParams, $sep ){
255 $fse = Container::instance()->getFilterService();
256 $terms = explode( $sep, $segmentParams );
257 $terms = $fse->sortTerms($terms);
258 $sortedParams = implode( $sep, $terms );
259
260 if( $segmentParams !== $sortedParams ){
261 return false;
262 }
263
264 return true;
265 }
266
267 /**
268 * @param string $segmentParams specially formatted sting like two#or#or-or-three#and
269 * @param array $filters filters arrays with logic value
270 */
271 private function extractLogicSeparator( $segmentParams, $filters ){
272 $fse = Container::instance()->getFilterService();
273 foreach( $filters as $filter ){
274 $logicSeparator = $fse->getLogicSeparator( $filter ); // -or- | -and-
275 if( mb_strpos( $segmentParams, $logicSeparator ) !== false ){
276 $this->queryVars['wpc_logic_separators'][$filter['slug']] = $filter['logic'];
277 return $logicSeparator;
278 }
279 }
280
281 return false;
282 }
283
284 private function extractQueriedValuesFromQueryString( $filterParams, $slug ){
285 // $filterParams = accessories;tshirts
286 $em = Container::instance()->getEntityManager();
287 $allEntityTerms = $em->getEntityAllTermsSlugs( $slug );
288 $queriedValues = $em->safeExplodeFilterValues( $filterParams, $slug, FLRT_QUERY_TERMS_SEPARATOR );
289 $queriedValues = $em->safeImplodeFilterValues( $queriedValues, FLRT_QUERY_TERMS_SEPARATOR );
290
291 foreach ( $queriedValues as $k => $value ) {
292
293 if ( ! in_array($value, $allEntityTerms) ) {
294 unset( $queriedValues[$k] );
295 $this->set_404( 'Term does not exist - ' . $value );
296 }
297 }
298
299 // Check duplicates
300 if( flrt_array_contains_duplicate( $queriedValues ) ){
301 $this->set_404('Param duplicates');
302 $queriedValues = array_unique($queriedValues);
303 }
304
305 unset( $em );
306
307 return $queriedValues;
308 }
309
310 /**
311 * @param string $segmentParams
312 * @param string $slug
313 * @return false|array
314 */
315 private function extractQueriedValuesFromSegment( $segmentParams, $slug ){
316
317 $em = Container::instance()->getEntityManager();
318 $allEntityTerms = $em->getEntityAllTermsSlugs( $slug );
319
320 // TEMPORARY
321 //$allEntityTerms = array_map( 'urldecode', $allEntityTerms );
322
323 $filters = $em->getAllFiltersBySlug( $slug, array( 'logic', 'slug' ) );
324
325 $segmentParams = $em->safeExplodeFilterValues( $segmentParams, $slug, $this->separator, false );
326 $logicSeparator = $this->extractLogicSeparator( $segmentParams, $filters );
327
328 // $segmentParams = two#or#or-or-three#and
329 // $valueSeparator = '-or-'
330 if( $logicSeparator ) {
331
332 $queriedValues = explode( $logicSeparator, $segmentParams );
333
334 if ( ! $this->checkValuesOrder($segmentParams, $logicSeparator) ) {
335 /**
336 * @feature maybe redirect to URL with correct order of values
337 */
338 $this->set_404('Invalid params order');
339 }
340 }else{
341 $queriedValues[0] = $segmentParams;
342 }
343
344 $queriedValues = $em->safeImplodeFilterValues( $queriedValues, $this->separator );
345
346 foreach ( $queriedValues as $k => $value ) {
347 if ( ! in_array( $value, $allEntityTerms) ) {
348 unset( $queriedValues[$k] );
349 $this->set_404( 'Term does not exist - ' . $value );
350 }
351 }
352
353 // Check duplicates
354 if( flrt_array_contains_duplicate( $queriedValues ) ){
355 $this->set_404('Param duplicates');
356 $queriedValues = array_unique($queriedValues);
357 }
358
359 unset($em);
360
361 return $queriedValues;
362 }
363
364 private function validateSegmentsOrder( $template = [] ){
365 $fse = Container::instance()->getFilterService();
366
367 if( ! $template ){
368 $template = $fse->getFiltersOrder();
369 }
370
371 $to_compare = $this->queryVars['segments_order'];
372
373 if( flrt_array_contains_duplicate( $to_compare ) ){
374 return false;
375 }
376
377 if( ! is_array( $template ) || ! is_array( $to_compare ) ){
378 return false;
379 }
380
381 $new_template = array_intersect( $template, $to_compare );
382
383 if( empty( $new_template ) ){
384 return false;
385 }
386
387 $new_template = array_values( $new_template );
388 $already_compared = [];
389 $i = 0;
390
391 foreach ( $to_compare as $index => $value ) {
392 if( in_array( $value, $already_compared ) ){
393 $existing_index = array_search( $value, $already_compared );
394 $existing_index++;
395 if( isset( $already_compared[$existing_index] ) ){
396 return false;
397 }
398 }
399
400 if( $value === $new_template[$i] ){
401 $i++;
402 $already_compared[] = $value;
403 }
404 }
405
406 unset( $fse );
407
408 return ( $new_template === $already_compared );
409
410 }
411
412 private function validateQueryVars(){
413 // Check for segments duplicates
414 $maybe_duplicates = [];
415 $fse = Container::instance()->getFilterService();
416
417 if( ! empty( $this->queryVars['queried_values'] ) ){
418 foreach( $this->queryVars['queried_values'] as $filter ){
419 $maybe_duplicates[] = $fse->getEntityFullName( $filter['entity'], $filter['e_name'] );
420 }
421
422 if( flrt_array_contains_duplicate( $maybe_duplicates ) ){
423 $this->set_404( 'Segment duplicates' );
424 }
425 }
426
427 // Check segments order
428 if( ! empty( $this->queryVars['segments_order'] ) ) {
429 if (!$this->validateSegmentsOrder()) {
430 $this->set_404('Invalid segments order');
431 }
432 }
433
434 unset( $fse );
435 // Check something other
436 // If max < than min maybe should be an error
437 }
438 }