PluginProbe
Filter Everything — WordPress & WooCommerce Filters / 1.6.1
Filter Everything — WordPress & WooCommerce Filters v1.6.1
1.9.7 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 All 52 releases
filter-everything / src / RequestParser.php

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

436 lines 14.2 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 = strtolower( trim( $request, '/' ) );
160 }
161
162 /**
163 * @return array
164 */
165 private function getPathSegments(){
166 if( $this->request ){
167 return explode('/', $this->request );
168 }
169 return [];
170 }
171
172 public function cleanUpRequestPathFromFilterSegments( $request_path ){
173 // Otherwise it will be URL encoded with uppercase characters
174 // But tax term slugs always stored lowercase in WordPress DB
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 if( $this->isSlugInQuery( $slug ) ){
219 $filter_entity = $em->getFilterBySlug( $slug, array('entity', 'e_name', 'slug', 'in_path') );
220 $filter_entity['values'] = $this->extractValuesFromQuery( $slug );
221 $filter_entity['founded_in_path'] = 'no';
222 $this->queryVars['queried_values'][$slug] = $filter_entity;
223 }
224 }
225
226 unset($em, $fse);
227 }
228
229 private function checkSlugInSegmentForCleaningNativePath( $segment ){
230 $em = Container::instance()->getEntityManager();
231 foreach( $em->getConfiguredPathSlugs() as $key => $slug ){
232 if( mb_strpos( $segment, $slug . $this->separator ) === 0 ){
233 return $slug;
234 }
235 }
236 return false;
237 }
238
239 private function getSlugFromSegment( $segment ){
240 $em = Container::instance()->getEntityManager();
241 foreach( $em->getConfiguredPathSlugs() as $key => $slug ){
242 if( mb_strpos( $segment, $slug . $this->separator ) === 0 ){
243 return $slug;
244 }
245 }
246 return false;
247 }
248
249 private function cutParamsFromSegment( $segment, $slug ){
250 return mb_substr( $segment, mb_strlen( $slug . $this->separator ) );
251 }
252
253 private function checkValuesOrder( $segmentParams, $sep ){
254 $fse = Container::instance()->getFilterService();
255 $terms = explode( $sep, $segmentParams );
256 $terms = $fse->sortTerms($terms);
257 $sortedParams = implode( $sep, $terms );
258
259 if( $segmentParams !== $sortedParams ){
260 return false;
261 }
262
263 return true;
264 }
265
266 /**
267 * @param string $segmentParams specially formatted sting like two#or#or-or-three#and
268 * @param array $filters filters arrays with logic value
269 */
270 private function extractLogicSeparator( $segmentParams, $filters ){
271 $fse = Container::instance()->getFilterService();
272 foreach( $filters as $filter ){
273 $logicSeparator = $fse->getLogicSeparator( $filter ); // -or- | -and-
274 if( mb_strpos( $segmentParams, $logicSeparator ) !== false ){
275 $this->queryVars['wpc_logic_separators'][$filter['slug']] = $filter['logic'];
276 return $logicSeparator;
277 }
278 }
279
280 return false;
281 }
282
283 private function extractQueriedValuesFromQueryString( $filterParams, $slug ){
284 // $filterParams = accessories;tshirts
285 $em = Container::instance()->getEntityManager();
286 $allEntityTerms = $em->getEntityAllTermsSlugs( $slug );
287 $queriedValues = $em->safeExplodeFilterValues( $filterParams, $slug, FLRT_QUERY_TERMS_SEPARATOR );
288 $queriedValues = $em->safeImplodeFilterValues( $queriedValues, FLRT_QUERY_TERMS_SEPARATOR );
289
290 $allEntityTerms_flipped = array_flip( $allEntityTerms );
291 foreach ( $queriedValues as $k => $value ) {
292 // if ( ! in_array($value, $allEntityTerms) ) {
293 if ( ! isset( $allEntityTerms_flipped[$value] ) ) {
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 $filters = $em->getAllFiltersBySlug( $slug, array( 'logic', 'slug' ) );
321
322 $segmentParams = $em->safeExplodeFilterValues( $segmentParams, $slug, $this->separator, false );
323 $logicSeparator = $this->extractLogicSeparator( $segmentParams, $filters );
324
325 // $segmentParams = two#or#or-or-three#and
326 // $valueSeparator = '-or-'
327 if( $logicSeparator ) {
328
329 $queriedValues = explode( $logicSeparator, $segmentParams );
330
331 if ( ! $this->checkValuesOrder($segmentParams, $logicSeparator) ) {
332 /**
333 * @feature maybe redirect to URL with correct order of values
334 */
335 $this->set_404('Invalid params order');
336 }
337 }else{
338 $queriedValues[0] = $segmentParams;
339 }
340
341 $queriedValues = $em->safeImplodeFilterValues( $queriedValues, $this->separator );
342
343 $allEntityTerms_flipped = array_flip( $allEntityTerms );
344 foreach ( $queriedValues as $k => $value ) {
345 if ( ! isset( $allEntityTerms_flipped[$value] ) ) {
346 unset( $queriedValues[$k] );
347 $this->set_404( 'Term does not exist - ' . $value );
348 }
349 }
350
351 // Check duplicates
352 if( flrt_array_contains_duplicate( $queriedValues ) ){
353 $this->set_404('Param duplicates');
354 $queriedValues = array_unique($queriedValues);
355 }
356
357 unset($em);
358
359 return $queriedValues;
360 }
361
362 private function validateSegmentsOrder( $template = [] ){
363 $fse = Container::instance()->getFilterService();
364
365 if( ! $template ){
366 $template = $fse->getFiltersOrder();
367 }
368
369 $to_compare = $this->queryVars['segments_order'];
370
371 if( flrt_array_contains_duplicate( $to_compare ) ){
372 return false;
373 }
374
375 if( ! is_array( $template ) || ! is_array( $to_compare ) ){
376 return false;
377 }
378
379 $new_template = array_intersect( $template, $to_compare );
380
381 if( empty( $new_template ) ){
382 return false;
383 }
384
385 $new_template = array_values( $new_template );
386 $already_compared = [];
387 $i = 0;
388
389 foreach ( $to_compare as $index => $value ) {
390 if( in_array( $value, $already_compared ) ){
391 $existing_index = array_search( $value, $already_compared );
392 $existing_index++;
393 if( isset( $already_compared[$existing_index] ) ){
394 return false;
395 }
396 }
397
398 if( $value === $new_template[$i] ){
399 $i++;
400 $already_compared[] = $value;
401 }
402 }
403
404 unset( $fse );
405
406 return ( $new_template === $already_compared );
407
408 }
409
410 private function validateQueryVars(){
411 // Check for segments duplicates
412 $maybe_duplicates = [];
413 $fse = Container::instance()->getFilterService();
414
415 if( ! empty( $this->queryVars['queried_values'] ) ){
416 foreach( $this->queryVars['queried_values'] as $filter ){
417 $maybe_duplicates[] = $fse->getEntityFullName( $filter['entity'], $filter['e_name'] );
418 }
419
420 if( flrt_array_contains_duplicate( $maybe_duplicates ) ){
421 $this->set_404( 'Segment duplicates' );
422 }
423 }
424
425 // Check segments order
426 if( ! empty( $this->queryVars['segments_order'] ) ) {
427 if (!$this->validateSegmentsOrder()) {
428 $this->set_404('Invalid segments order');
429 }
430 }
431
432 unset( $fse );
433 // Check something other
434 // If max < than min maybe should be an error
435 }
436 }