PluginProbe
Filter Everything — WordPress & WooCommerce Filters / 1.9.7
Filter Everything — WordPress & WooCommerce Filters v1.9.7
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
← All changes | src/Entities/PostMetaEntity.php +160 -198 1.3.21.9.7 View file →
@@ -1,20 +1,32 @@
1 1 <?php
2 2
3 3 namespace FilterEverything\Filter;
4 4
5 -if ( ! defined('WPINC') ) {
6 - wp_die();
5 +if ( ! defined('ABSPATH') ) {
6 + exit;
7 7 }
8 8
9 9 class PostMetaEntity implements Entity
10 10 {
11 + use PostMetaTrait;
12 +
11 13 public $items = [];
14 + /**
15 + * Declared explicitly: assigned from the outside in
16 + * EntityManager::prepareEntitiesToDisplay(); dynamic properties are
17 + * deprecated since PHP 8.2.
18 + */
19 + public $items_sort = [];
12 20
21 + public $filter = [];
22 +
13 23 public $entityName = '';
14 24
15 25 public $excludedTerms = [];
16 26
27 + public $isInclude = false;
28 +
17 29 private $new_meta_query = [];
18 30
19 31 /**
20 32 * @todo We need to set parameter PosType to select only that post meta terms, that
@@ -43,11 +55,12 @@
43 55 }
44 56
45 57 }
46 58
47 - public function setExcludedTerms( $excludedTerms )
59 + public function setExcludedTerms( $excludedTerms, $isInclude )
48 60 {
49 61 $this->excludedTerms = $excludedTerms;
62 + $this->isInclude = $isInclude;
50 63 }
51 64
52 65 public function getName()
53 66 {
@@ -66,12 +79,24 @@
66 79 if( ! empty( $this->excludedTerms ) ){
67 80 $exclude = $this->excludedTerms;
68 81 }
69 82
70 - foreach( $terms as $index => $term ){
71 - if( in_array( $term->slug, $exclude ) ){
72 - unset( $terms[$index] );
83 + $exclude_flipped = array_flip( $exclude );
84 +
85 + if( $this->isInclude ){
86 + $included_terms = [];
87 + foreach( $terms as $index => $term ){
88 + if( isset( $exclude_flipped[$term->slug] ) ){
89 + $included_terms[$index] = $term;
90 + }
73 91 }
92 + $terms = $included_terms;
93 + }else{
94 + foreach( $terms as $index => $term ){
95 + if( isset( $exclude_flipped[$term->slug] ) ){
96 + unset( $terms[$index] );
97 + }
98 + }
74 99 }
75 100
76 101 return $terms;
77 102 }
@@ -85,9 +110,10 @@
85 110 * @param int $id term id
86 111 * @return false|object term object of false
87 112 */
88 113 public function getTerm( $slug ){
89 - if( ! $slug ){
114 + // To allow 0 as meta value
115 + if( $slug === '' || $slug === false ){
90 116 return false;
91 117 }
92 118
93 119 foreach ( $this->getTerms() as $term ){
@@ -145,12 +171,45 @@
145 171 foreach( $this->items as $index => $term ){
146 172 foreach( $term->post_types as $post_id => $term_post_type ){
147 173 if( ! in_array($term_post_type, $this->postTypes ) ){
148 174 $position = array_search( $post_id, $term->posts );
149 - unset( $this->items[$index]->posts[$position] );
175 + // To avoid unset $this->items[$index]->posts[0] when $position === false
176 + if( $position !== false ){
177 + unset( $this->items[$index]->posts[$position] );
178 + }
150 179 }
151 180 }
152 181 }
182 +
183 + /**
184 + * Keep only posts that belong to the set universe (all queried posts of
185 + * the set), same as TaxonomyEntity does. Without this the raw meta lists
186 + * (e.g. _stock_status of ALL published products, including those excluded
187 + * from the catalog) leak into wpcFilterJsonData, and the client-side
188 + * recount shows inflated counters compared to the server-rendered ones.
189 + *
190 + * Term posts of "Used for Variations" meta keys legitimately contain
191 + * variation IDs which are absent from the parent-product universe, so a
192 + * post survives when it is in the universe OR in the universe expanded
193 + * to variation IDs.
194 + */
195 + $em = Container::instance()->getEntityManager();
196 + $allWpQueriedPostIds = $em->getAllSetWpQueriedPostIds( $setId );
197 +
198 + if ( ! empty( $allWpQueriedPostIds ) ) {
199 + $universe = array_flip( $allWpQueriedPostIds );
200 + $universe_variations = apply_filters( 'wpc_from_products_to_variations', $universe );
201 +
202 + foreach ( $this->items as $index => $term ) {
203 + $intersected_posts = [];
204 + foreach ( $term->posts as $post_id ) {
205 + if ( isset( $universe[ $post_id ] ) || isset( $universe_variations[ $post_id ] ) ) {
206 + $intersected_posts[] = $post_id;
207 + }
208 + }
209 + $this->items[ $index ]->posts = $intersected_posts;
210 + }
211 + }
153 212 }
154 213
155 214 private function selectTerms(){
156 215 global $wpdb;
@@ -155,74 +214,85 @@
155 214 private function selectTerms(){
156 215 global $wpdb;
157 216
158 217 $e_name = wp_unslash( $this->entityName );
218 + $transient_key = flrt_get_terms_transient_key( 'post_meta_' . $e_name );
219 + $translatable_post_type_exists = false;
159 220
160 - $sql[] = "SELECT {$wpdb->postmeta}.post_id,{$wpdb->postmeta}.meta_value,{$wpdb->posts}.post_type";
161 - $sql[] = "FROM {$wpdb->postmeta}";
162 - $sql[] = "LEFT JOIN {$wpdb->posts} ON ({$wpdb->postmeta}.post_id = {$wpdb->posts}.ID)";
221 + if ( false === ( $result = flrt_get_transient( $transient_key ) ) ) {
163 222
164 - /**
165 - * @todo make it through apply_filter();
166 - */
167 - if( flrt_wpml_active() && defined( 'ICL_LANGUAGE_CODE' ) ){
168 - $sql[] = "LEFT JOIN {$wpdb->prefix}icl_translations AS wpml_translations";
169 - $sql[] = "ON {$wpdb->postmeta}.post_id = wpml_translations.element_id";
223 + $sql[] = "SELECT {$wpdb->postmeta}.post_id,{$wpdb->postmeta}.meta_value,{$wpdb->posts}.post_type";
224 + $sql[] = "FROM {$wpdb->postmeta}";
225 + $sql[] = "LEFT JOIN {$wpdb->posts} ON ({$wpdb->postmeta}.post_id = {$wpdb->posts}.ID)";
170 226
171 - if( ! empty( $this->postTypes ) ){
172 - $sql[] = "AND wpml_translations.element_type IN(";
173 - foreach( $this->postTypes as $type ){
174 - $LANG_IN[] = $wpdb->prepare( "CONCAT('post_', '%s')", $type );
227 + /**
228 + * @todo make it through apply_filter();
229 + */
230 + if ( flrt_wpml_active() && defined( 'ICL_LANGUAGE_CODE' ) ) {
231 + $wpml_settings = get_option( 'icl_sitepress_settings' );
232 +
233 + foreach ( $this->postTypes as $type ) {
234 + if( isset( $wpml_settings['custom_posts_sync_option'][$type] ) ){
235 + if( $wpml_settings['custom_posts_sync_option'][$type] === '1' ){
236 + $translatable_post_type_exists = true;
237 + break;
238 + }
175 239 }
176 - $sql[] = implode(",", $LANG_IN );
177 - $sql[] = ")";
240 + }
241 +
242 + if ( $translatable_post_type_exists ) {
243 + $sql[] = "LEFT JOIN {$wpdb->prefix}icl_translations AS wpml_translations";
244 + $sql[] = "ON {$wpdb->postmeta}.post_id = wpml_translations.element_id";
245 +
246 + if ( ! empty( $this->postTypes ) ) {
247 + $sql[] = "AND wpml_translations.element_type IN(";
248 + foreach ( $this->postTypes as $type ) {
249 +
250 + if( isset( $wpml_settings['custom_posts_sync_option'][$type] ) ) {
251 + if( $wpml_settings['custom_posts_sync_option'][$type] === '1' ) {
252 + $LANG_IN[] = $wpdb->prepare("CONCAT('post_', '%s')", $type);
253 + }
254 + }
255 +
256 + }
257 + $sql[] = implode(",", $LANG_IN);
258 + $sql[] = ")";
259 + }
260 + }
178 261 }
179 - }
180 262
181 - $sql[] = "WHERE {$wpdb->postmeta}.meta_key = %s";
263 + $sql[] = "WHERE {$wpdb->postmeta}.meta_key = %s";
264 + $sql[] = "AND {$wpdb->postmeta}.meta_value IS NOT NULL";
182 265
183 - if( ! empty( $this->postTypes ) ){
184 - $sql[] = "AND {$wpdb->posts}.post_type IN(";
185 - foreach( $this->postTypes as $type ){
186 - $IN[] = $wpdb->prepare( "%s", $type );
187 - }
188 - $sql[] = implode(",", $IN );
189 - $sql[] = ")";
190 - }
266 + /**
267 + * @todo make it through apply_filter();
268 + */
269 + if ( flrt_wpml_active() && defined('ICL_LANGUAGE_CODE') && $translatable_post_type_exists ) {
270 + $sql[] = $wpdb->prepare("AND wpml_translations.language_code = '%s'", ICL_LANGUAGE_CODE);
271 + }
191 272
192 -// if( flrt_is_woocommerce() && in_array( 'product', $this->postTypes ) ){
193 -// // Do not take into account product variations
194 -// $sql[] = "AND {$wpdb->posts}.post_type != 'product_variation'";
195 -// }
273 + /**
274 + * @notice It would be great to make LEFT JOIN posts where post type is post type from the filter SET
275 + * But it seems we can't know post type on this stage of WP loading (in RequestParser).
276 + */
196 277
197 - // Do not take into account variable products if it requires
198 - // We will attach their terms later
199 -// if( $include_variations_later ){
200 -// $sql[] = "AND {$wpdb->posts}.ID NOT IN(";
201 -// $sql[] = "SELECT DISTINCT {$wpdb->posts}.post_parent";
202 -// $sql[] = "FROM {$wpdb->posts}";
203 -// $sql[] = "WHERE {$wpdb->posts}.post_type = 'product_variation' )";
204 -// }
278 + /**
279 + * Filters terms SQL-query and allows to modify it
280 + */
281 + $sql = apply_filters( 'wpc_filter_get_post_meta_terms_sql', $sql, $e_name );
205 282
206 - /**
207 - * @todo make it through apply_filter();
208 - */
209 - if( flrt_wpml_active() && defined( 'ICL_LANGUAGE_CODE' ) ){
210 - $sql[] = $wpdb->prepare("AND wpml_translations.language_code = '%s'", ICL_LANGUAGE_CODE);
211 - }
283 + $sql = implode(' ', $sql );
212 284
213 - $sql[] = "ORDER BY {$wpdb->postmeta}.meta_id ASC";
214 - /**
215 - * @notice It would be great to make LEFT JOIN posts where post type is post type from the filter SET
216 - * But it seems we can't know post type on this stage of WP loading (in RequestParser).
217 - */
285 + $sql = $wpdb->prepare( $sql, $e_name );
218 286
219 - $sql = implode(' ', $sql);
287 + $result = $wpdb->get_results( $sql, ARRAY_A );
220 288
221 - $sql = $wpdb->prepare( $sql, $e_name );
222 - $result = $wpdb->get_results( $sql, ARRAY_A );
289 + $result = $this->convertSelectResult( $result );
223 290
224 - return $this->convertSelectResult( $result );
291 + flrt_set_transient( $transient_key, $result, FLRT_TRANSIENT_PERIOD_HOURS * HOUR_IN_SECONDS );
292 + }
293 +
294 + return $result;
225 295 }
226 296
227 297 private function hasRestrictedSymbols( $str )
228 298 {
@@ -243,8 +313,9 @@
243 313 if( ! is_array( $result ) ){
244 314 return $return;
245 315 }
246 316 $customIndex = 1;
317 +
247 318 // To make standard format for terms array;
248 319 foreach ( $result as $index => $post_meta_row ){
249 320
250 321 if( is_serialized( $post_meta_row['meta_value'] ) ){
@@ -250,13 +321,13 @@
250 321 if( is_serialized( $post_meta_row['meta_value'] ) ){
251 322 $data = maybe_unserialize( $post_meta_row['meta_value'] );
252 323 foreach ( $data as $i => $meta_value ){
253 324 // For multidimensional arrays stored in post meta
254 - if( is_array($meta_value) ){
325 + if( is_array( $meta_value ) ){
255 326 continue;
256 327 }
257 328 $customIndex++;
258 - $slug = sanitize_title($meta_value); //strtolower( $value );
329 + $slug = sanitize_title( $meta_value );
259 330 if( $this->hasRestrictedSymbols( $slug ) ){
260 331 continue;
261 332 }
262 333 $this->addNewTerm( $return, $slug, $post_meta_row, $meta_value, $customIndex );
@@ -282,113 +353,22 @@
282 353 $return[ $slug ]->posts[] = $post_meta_row['post_id'];
283 354 $return[ $slug ]->count++;
284 355 $return[ $slug ]->post_types[$post_meta_row['post_id']] = $post_meta_row['post_type'];
285 356 }else{
286 - $termObject = new \stdClass();
287 - $termObject->slug = $slug;
288 - $termObject->meta_value = $meta_value;
289 - $termObject->name = apply_filters( 'wpc_filter_post_meta_term_name', $meta_value, $this->getName() );
290 - $termObject->term_id = ($index + 1); // To avoid term_id = 0
291 - $termObject->posts = array( $post_meta_row['post_id'] );
292 - $termObject->count = 1;
293 - $termObject->cross_count = 0;
294 - $termObject->post_types[$post_meta_row['post_id']] = $post_meta_row['post_type'];
295 -
296 - $return[ $slug ] = $termObject;
357 + $termObject = new \stdClass();
358 + $termObject->slug = $slug;
359 + $termObject->meta_value = $meta_value;
360 + $termObject->name = apply_filters( 'wpc_filter_post_meta_term_name', $meta_value, $this->getName() );
361 + $termObject->term_id = ($index + 1); // To avoid term_id = 0
362 + $termObject->posts = array( $post_meta_row['post_id'] );
363 + $termObject->count = 1;
364 + $termObject->cross_count = 0;
365 + $termObject->post_types[ $post_meta_row['post_id'] ] = $post_meta_row['post_type'];
366 + $termObject->wp_queried = false;
367 + $return[ $slug ] = $termObject;
297 368 }
298 369 }
299 370
300 - private function isTermInMetaKey( $queried_value, $wp_query ){
301 - $duplicate = [];
302 - $terms = $queried_value['values'];
303 - $meta_key = $wp_query->get('meta_key');
304 - $meta_value = $wp_query->get('meta_value');
305 -
306 - foreach ( $terms as $term ) {
307 - if( $queried_value['e_name'] === $meta_key ){
308 - if( $meta_value === $term ){
309 - $duplicate['post_meta'] = $queried_value['e_name'];
310 - $duplicate['term'] = $term;
311 - return $duplicate;
312 - }
313 - }
314 - }
315 -
316 - return false;
317 - }
318 -
319 - private function isTermInMetaQuery( $queried_value, $wp_query ){
320 - $duplicate = [];
321 - $meta_query = $wp_query->get('meta_query');
322 - $terms = $queried_value['values'];
323 -
324 - if( ! empty( $meta_query ) ){
325 -
326 - foreach ( $meta_query as $query_array ){
327 - if( isset( $query_array['key'] ) && $query_array['key'] === $queried_value['e_name'] ){
328 - if( isset( $query_array['value'] ) && in_array( $query_array['value'], $terms ) ){
329 - $duplicate['post_meta'] = $queried_value['e_name'];
330 - $duplicate['term'] = $query_array['value'];
331 - return $duplicate;
332 - }
333 - }
334 - }
335 - }
336 -
337 - return false;
338 - }
339 -
340 - public function isTermAlreadyInQuery( $queried_value, $wp_query ){
341 - // Is term in Key
342 - if( $duplicate = $this->isTermInMetaKey( $queried_value, $wp_query ) ){
343 - return $duplicate;
344 - }
345 - // Is term in Query
346 - if( $duplicate = $this->isTermInMetaQuery( $queried_value, $wp_query ) ){
347 - return $duplicate;
348 - }
349 -
350 - return false;
351 - }
352 -
353 - private function normalizeMetaQueryArray( $meta_query )
354 - {
355 - $normalized_meta_query = [];
356 -
357 - if( ! is_array( $meta_query ) || ! isset( $meta_query['key'] ) ){
358 - return false;
359 - }
360 - if( isset( $meta_query['value'] ) ){
361 - if( is_array( $meta_query['value'] ) ){
362 - sort( $meta_query['value'] );
363 - $meta_query['value'] = implode( '-', $meta_query['value'] );
364 - $normalized_meta_query['value'] = $meta_query['value'];
365 - }else{
366 - $normalized_meta_query['value'] = $meta_query['value'];
367 - }
368 - }
369 -
370 - $normalized_meta_query['key'] = $meta_query['key'];
371 - if( isset( $meta_query['compare'] ) ){
372 - $normalized_meta_query['compare'] = isset( $meta_query['compare'] ) ? $meta_query['compare'] : '';
373 - }
374 -
375 - return $normalized_meta_query;
376 - }
377 -
378 - private function isTheSameMetaQuery( $meta_query_1, $meta_query_2 ){
379 - $meta_query_1 = $this->normalizeMetaQueryArray($meta_query_1);
380 - $meta_query_2 = $this->normalizeMetaQueryArray($meta_query_2);
381 -
382 - $diff = array_diff( $meta_query_1, $meta_query_2 );
383 -
384 - if ( empty( $diff ) ){
385 - return true;
386 - }
387 -
388 - return false;
389 - }
390 -
391 371 private function addMetaQueryArray( $meta_query_array, $relation = false )
392 372 {
393 373 if( ! isset( $meta_query_array['key'] ) ){
394 374 return false;
@@ -424,46 +404,23 @@
424 404 }
425 405
426 406 }
427 407
428 - private function findNestedIndexForQuery( $meta_query_array )
429 - {
430 - $meta_key = $meta_query_array['key'];
431 -
432 - if( empty( $this->new_meta_query ) ){
433 - return 0;
434 - }
435 -
436 - foreach ( $this->new_meta_query as $i_level_1 => $maybe_meta_query ){
437 - // This subquery already exists
438 - if( isset( $maybe_meta_query[0]['key'] ) && $maybe_meta_query[0]['key'] === $meta_key ){
439 - return $i_level_1;
440 - }
441 - }
442 -
443 - return count( $this->new_meta_query );
444 - }
445 -
446 - private function hasNestedQueries( $meta_query )
447 - {
448 - if( isset( $meta_query[0]['key'] ) ){
449 - return true;
450 - }
451 -
452 - return false;
453 - }
454 -
455 408 private function addMetaKeyToQuery( $wp_query ){
456 409 $args = [];
457 410
458 411 $args['key'] = $wp_query->get( 'meta_key' );
459 412 $args['value'] = $wp_query->get( 'meta_value' );
460 - $args['compare'] = ( $compare = $wp_query->get( 'meta_compare' ) ) ? $compare : 'IN';
461 413
462 - $wp_query->set( 'meta_key', '' );
463 - $wp_query->set( 'meta_value', '' );
414 + // Modified since v 1.6.5 to avoid adding meta_value IN('') condition to SQL query
415 + $args['compare'] = ( $compare = $wp_query->get( 'meta_compare' ) ) ? $compare : '';
464 416
465 - $this->addMetaQueryArray( $args );
417 + if($args['compare'] ){
418 + $wp_query->set( 'meta_key', '' );
419 + $wp_query->set( 'meta_value', '' );
420 +
421 + $this->addMetaQueryArray( $args );
422 + }
466 423 }
467 424
468 425 private function isMetaValueSerialized( $metaKey )
469 426 {
@@ -469,8 +426,9 @@
469 426 {
470 427 global $wpdb;
471 428
472 429 $sql = "SELECT {$wpdb->postmeta}.meta_value FROM {$wpdb->postmeta} WHERE {$wpdb->postmeta}.meta_key = %s";
430 + $sql .= " AND {$wpdb->postmeta}.meta_value != ''";
473 431 $sql .= " LIMIT 0,1";
474 432 $sql = $wpdb->prepare( $sql, $metaKey );
475 433
476 434 $result = $wpdb->get_results( $sql );
@@ -516,8 +474,11 @@
516 474 $serialized = $this->isMetaValueSerialized( $queried_value['e_name'] );
517 475
518 476 // Serialized data stored in meta_value should be matched by regexp
519 477 if( $serialized ){
478 + $replace_from = array( "+", ":", ".", "*", ";", "-" );
479 + $replace_to = array( "\+", "\:", "\.", "\*", "\;", "\-" );
480 +
520 481 // For multiple queries we have to set correct relation
521 482 if( count( $queried_value['values'] ) > 1 ){
522 483 $relation = ( $queried_value['logic'] === 'and' ) ? 'AND' : 'OR';
523 484
@@ -522,12 +483,13 @@
522 483 $relation = ( $queried_value['logic'] === 'and' ) ? 'AND' : 'OR';
523 484
524 485 foreach ( $queried_value['values'] as $slug ) {
525 486 $term = $this->getTerm($slug);
487 + $term_value = str_replace( $replace_from, $replace_to, $term->meta_value );
526 488 $this->addMetaQueryArray(
527 489 array(
528 490 'key' => $queried_value['e_name'],
529 - 'value' => '.*;s:[0-9]+:"'.$term->meta_value.'".*',
491 + 'value' => '.*;s:[0-9]+:"'.$term_value.'".*',
530 492 'compare' => 'REGEXP'
531 493 ),
532 494 $relation
533 495 );
@@ -536,12 +498,13 @@
536 498 }else{
537 499 // Single term selected in filter
538 500 foreach ( $queried_value['values'] as $slug ) {
539 501 $term = $this->getTerm($slug);
502 + $term_value = str_replace( $replace_from, $replace_to, $term->meta_value );
540 503 $this->addMetaQueryArray(
541 504 array(
542 505 'key' => $queried_value['e_name'],
543 - 'value' => '.*;s:[0-9]+:"'.$term->meta_value.'".*',
506 + 'value' => '.*;s:[0-9]+:"'.$term_value.'".*',
544 507 'compare' => 'REGEXP'
545 508 )
546 509 );
547 510 }
@@ -573,9 +536,8 @@
573 536 'compare' => 'IN'
574 537 )
575 538 );
576 539 }
577 -
578 540 }
579 541
580 542 if( count($this->new_meta_query) > 1 ){
581 543 $this->new_meta_query['relation'] = 'AND';