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 +171 -199 1.2.41.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
@@ -34,13 +46,21 @@
34 46
35 47 public function setPostTypes( $postTypes )
36 48 {
37 49 $this->postTypes = $postTypes;
50 +
51 + if( flrt_is_woocommerce()
52 + && in_array( 'product', $this->postTypes )
53 + && ! in_array( 'product_variation', $this->postTypes ) ){
54 + $this->postTypes[] = 'product_variation';
55 + }
56 +
38 57 }
39 58
40 - public function setExcludedTerms( $excludedTerms )
59 + public function setExcludedTerms( $excludedTerms, $isInclude )
41 60 {
42 61 $this->excludedTerms = $excludedTerms;
62 + $this->isInclude = $isInclude;
43 63 }
44 64
45 65 public function getName()
46 66 {
@@ -59,12 +79,24 @@
59 79 if( ! empty( $this->excludedTerms ) ){
60 80 $exclude = $this->excludedTerms;
61 81 }
62 82
63 - foreach( $terms as $index => $term ){
64 - if( in_array( $term->slug, $exclude ) ){
65 - 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 + }
66 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 + }
67 99 }
68 100
69 101 return $terms;
70 102 }
@@ -78,9 +110,10 @@
78 110 * @param int $id term id
79 111 * @return false|object term object of false
80 112 */
81 113 public function getTerm( $slug ){
82 - if( ! $slug ){
114 + // To allow 0 as meta value
115 + if( $slug === '' || $slug === false ){
83 116 return false;
84 117 }
85 118
86 119 foreach ( $this->getTerms() as $term ){
@@ -133,86 +166,133 @@
133 166 }
134 167
135 168 function populateTermsWithPostIds( $setId, $post_type )
136 169 {
137 - $queried_post_type = reset($this->postTypes );
138 -
170 + // Correct term posts if they were selected without specified postTypes
139 171 foreach( $this->items as $index => $term ){
140 172 foreach( $term->post_types as $post_id => $term_post_type ){
141 - if( $term_post_type !== $queried_post_type ){
173 + if( ! in_array($term_post_type, $this->postTypes ) ){
142 174 $position = array_search( $post_id, $term->posts );
143 - 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 + }
144 179 }
145 180 }
146 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 + }
147 212 }
148 213
149 214 private function selectTerms(){
150 215 global $wpdb;
151 216
152 - $e_name = wp_unslash( $this->entityName );
153 - $include_variations = apply_filters('wpc_terms_include_variations', false, $this);
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;
154 220
155 - $sql[] = "SELECT {$wpdb->postmeta}.post_id,{$wpdb->postmeta}.meta_value,{$wpdb->posts}.post_type";
156 - $sql[] = "FROM {$wpdb->postmeta}";
157 - $sql[] = "LEFT JOIN {$wpdb->posts} ON ({$wpdb->postmeta}.post_id = {$wpdb->posts}.ID)";
221 + if ( false === ( $result = flrt_get_transient( $transient_key ) ) ) {
158 222
159 - /**
160 - * @todo make it through apply_filter();
161 - */
162 - if( flrt_wpml_active() && defined( 'ICL_LANGUAGE_CODE' ) ){
163 - $sql[] = "LEFT JOIN {$wpdb->prefix}icl_translations AS wpml_translations";
164 - $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)";
165 226
166 - if( ! empty( $this->postTypes ) ){
167 - $sql[] = "AND wpml_translations.element_type IN(";
168 - foreach( $this->postTypes as $type ){
169 - $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 + }
170 239 }
171 - $sql[] = implode(",", $LANG_IN );
172 - $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 + }
173 261 }
174 - }
175 262
176 - $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";
177 265
178 - if( ! empty( $this->postTypes ) ){
179 - $sql[] = "AND {$wpdb->posts}.post_type IN(";
180 - foreach( $this->postTypes as $type ){
181 - $IN[] = $wpdb->prepare( "%s", $type );
182 - }
183 - $sql[] = implode(",", $IN );
184 - $sql[] = ")";
185 - }
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 + }
186 272
187 - // Do not take into account variations if it requires
188 - if( $include_variations ){
189 - $sql[] = "AND {$wpdb->posts}.ID NOT IN(";
190 - $sql[] = "SELECT DISTINCT {$wpdb->posts}.post_parent";
191 - $sql[] = "FROM {$wpdb->posts}";
192 - $sql[] = "WHERE {$wpdb->posts}.post_type = 'product_variation' )";
193 - }
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 + */
194 277
195 - /**
196 - * @todo make it through apply_filter();
197 - */
198 - if( flrt_wpml_active() && defined( 'ICL_LANGUAGE_CODE' ) ){
199 - $sql[] = $wpdb->prepare("AND wpml_translations.language_code = '%s'", ICL_LANGUAGE_CODE);
200 - }
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 );
201 282
202 - $sql[] = "ORDER BY {$wpdb->postmeta}.meta_id ASC";
203 - /**
204 - * @notice It would be great to make LEFT JOIN posts where post type is post type from the filter SET
205 - * But it seems we can't know post type on this stage of WP loading (in RequestParser).
206 - */
283 + $sql = implode(' ', $sql );
207 284
208 - $sql = implode(' ', $sql);
285 + $sql = $wpdb->prepare( $sql, $e_name );
209 286
210 - $sql = $wpdb->prepare( $sql, $e_name );
211 - $result = $wpdb->get_results( $sql, ARRAY_A );
212 - $result = apply_filters( 'wpc_term_post_meta_terms', $result, $include_variations, $this );
287 + $result = $wpdb->get_results( $sql, ARRAY_A );
213 288
214 - return $this->convertSelectResult( $result );
289 + $result = $this->convertSelectResult( $result );
290 +
291 + flrt_set_transient( $transient_key, $result, FLRT_TRANSIENT_PERIOD_HOURS * HOUR_IN_SECONDS );
292 + }
293 +
294 + return $result;
215 295 }
216 296
217 297 private function hasRestrictedSymbols( $str )
218 298 {
@@ -233,8 +313,9 @@
233 313 if( ! is_array( $result ) ){
234 314 return $return;
235 315 }
236 316 $customIndex = 1;
317 +
237 318 // To make standard format for terms array;
238 319 foreach ( $result as $index => $post_meta_row ){
239 320
240 321 if( is_serialized( $post_meta_row['meta_value'] ) ){
@@ -240,13 +321,13 @@
240 321 if( is_serialized( $post_meta_row['meta_value'] ) ){
241 322 $data = maybe_unserialize( $post_meta_row['meta_value'] );
242 323 foreach ( $data as $i => $meta_value ){
243 324 // For multidimensional arrays stored in post meta
244 - if( is_array($meta_value) ){
325 + if( is_array( $meta_value ) ){
245 326 continue;
246 327 }
247 328 $customIndex++;
248 - $slug = sanitize_title($meta_value); //strtolower( $value );
329 + $slug = sanitize_title( $meta_value );
249 330 if( $this->hasRestrictedSymbols( $slug ) ){
250 331 continue;
251 332 }
252 333 $this->addNewTerm( $return, $slug, $post_meta_row, $meta_value, $customIndex );
@@ -272,113 +353,22 @@
272 353 $return[ $slug ]->posts[] = $post_meta_row['post_id'];
273 354 $return[ $slug ]->count++;
274 355 $return[ $slug ]->post_types[$post_meta_row['post_id']] = $post_meta_row['post_type'];
275 356 }else{
276 - $termObject = new \stdClass();
277 - $termObject->slug = $slug;
278 - $termObject->meta_value = $meta_value;
279 - $termObject->name = apply_filters( 'wpc_filter_post_meta_term_name', $meta_value, $this->getName() );
280 - $termObject->term_id = ($index + 1); // To avoid term_id = 0
281 - $termObject->posts = array( $post_meta_row['post_id'] );
282 - $termObject->count = 1;
283 - $termObject->cross_count = 0;
284 - $termObject->post_types[$post_meta_row['post_id']] = $post_meta_row['post_type'];
285 -
286 - $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;
287 368 }
288 369 }
289 370
290 - private function isTermInMetaKey( $queried_value, $wp_query ){
291 - $duplicate = [];
292 - $terms = $queried_value['values'];
293 - $meta_key = $wp_query->get('meta_key');
294 - $meta_value = $wp_query->get('meta_value');
295 -
296 - foreach ( $terms as $term ) {
297 - if( $queried_value['e_name'] === $meta_key ){
298 - if( $meta_value === $term ){
299 - $duplicate['post_meta'] = $queried_value['e_name'];
300 - $duplicate['term'] = $term;
301 - return $duplicate;
302 - }
303 - }
304 - }
305 -
306 - return false;
307 - }
308 -
309 - private function isTermInMetaQuery( $queried_value, $wp_query ){
310 - $duplicate = [];
311 - $meta_query = $wp_query->get('meta_query');
312 - $terms = $queried_value['values'];
313 -
314 - if( ! empty( $meta_query ) ){
315 -
316 - foreach ( $meta_query as $query_array ){
317 - if( isset( $query_array['key'] ) && $query_array['key'] === $queried_value['e_name'] ){
318 - if( isset( $query_array['value'] ) && in_array( $query_array['value'], $terms ) ){
319 - $duplicate['post_meta'] = $queried_value['e_name'];
320 - $duplicate['term'] = $query_array['value'];
321 - return $duplicate;
322 - }
323 - }
324 - }
325 - }
326 -
327 - return false;
328 - }
329 -
330 - public function isTermAlreadyInQuery( $queried_value, $wp_query ){
331 - // Is term in Key
332 - if( $duplicate = $this->isTermInMetaKey( $queried_value, $wp_query ) ){
333 - return $duplicate;
334 - }
335 - // Is term in Query
336 - if( $duplicate = $this->isTermInMetaQuery( $queried_value, $wp_query ) ){
337 - return $duplicate;
338 - }
339 -
340 - return false;
341 - }
342 -
343 - private function normalizeMetaQueryArray( $meta_query )
344 - {
345 - $normalized_meta_query = [];
346 -
347 - if( ! is_array( $meta_query ) || ! isset( $meta_query['key'] ) ){
348 - return false;
349 - }
350 - if( isset( $meta_query['value'] ) ){
351 - if( is_array( $meta_query['value'] ) ){
352 - sort( $meta_query['value'] );
353 - $meta_query['value'] = implode( '-', $meta_query['value'] );
354 - $normalized_meta_query['value'] = $meta_query['value'];
355 - }else{
356 - $normalized_meta_query['value'] = $meta_query['value'];
357 - }
358 - }
359 -
360 - $normalized_meta_query['key'] = $meta_query['key'];
361 - if( isset( $meta_query['compare'] ) ){
362 - $normalized_meta_query['compare'] = isset( $meta_query['compare'] ) ? $meta_query['compare'] : '';
363 - }
364 -
365 - return $normalized_meta_query;
366 - }
367 -
368 - private function isTheSameMetaQuery( $meta_query_1, $meta_query_2 ){
369 - $meta_query_1 = $this->normalizeMetaQueryArray($meta_query_1);
370 - $meta_query_2 = $this->normalizeMetaQueryArray($meta_query_2);
371 -
372 - $diff = array_diff( $meta_query_1, $meta_query_2 );
373 -
374 - if ( empty( $diff ) ){
375 - return true;
376 - }
377 -
378 - return false;
379 - }
380 -
381 371 private function addMetaQueryArray( $meta_query_array, $relation = false )
382 372 {
383 373 if( ! isset( $meta_query_array['key'] ) ){
384 374 return false;
@@ -414,46 +404,23 @@
414 404 }
415 405
416 406 }
417 407
418 - private function findNestedIndexForQuery( $meta_query_array )
419 - {
420 - $meta_key = $meta_query_array['key'];
421 -
422 - if( empty( $this->new_meta_query ) ){
423 - return 0;
424 - }
425 -
426 - foreach ( $this->new_meta_query as $i_level_1 => $maybe_meta_query ){
427 - // This subquery already exists
428 - if( isset( $maybe_meta_query[0]['key'] ) && $maybe_meta_query[0]['key'] === $meta_key ){
429 - return $i_level_1;
430 - }
431 - }
432 -
433 - return count( $this->new_meta_query );
434 - }
435 -
436 - private function hasNestedQueries( $meta_query )
437 - {
438 - if( isset( $meta_query[0]['key'] ) ){
439 - return true;
440 - }
441 -
442 - return false;
443 - }
444 -
445 408 private function addMetaKeyToQuery( $wp_query ){
446 409 $args = [];
447 410
448 411 $args['key'] = $wp_query->get( 'meta_key' );
449 412 $args['value'] = $wp_query->get( 'meta_value' );
450 - $args['compare'] = ( $compare = $wp_query->get( 'meta_compare' ) ) ? $compare : 'IN';
451 413
452 - $wp_query->set( 'meta_key', '' );
453 - $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 : '';
454 416
455 - $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 + }
456 423 }
457 424
458 425 private function isMetaValueSerialized( $metaKey )
459 426 {
@@ -459,8 +426,9 @@
459 426 {
460 427 global $wpdb;
461 428
462 429 $sql = "SELECT {$wpdb->postmeta}.meta_value FROM {$wpdb->postmeta} WHERE {$wpdb->postmeta}.meta_key = %s";
430 + $sql .= " AND {$wpdb->postmeta}.meta_value != ''";
463 431 $sql .= " LIMIT 0,1";
464 432 $sql = $wpdb->prepare( $sql, $metaKey );
465 433
466 434 $result = $wpdb->get_results( $sql );
@@ -506,8 +474,11 @@
506 474 $serialized = $this->isMetaValueSerialized( $queried_value['e_name'] );
507 475
508 476 // Serialized data stored in meta_value should be matched by regexp
509 477 if( $serialized ){
478 + $replace_from = array( "+", ":", ".", "*", ";", "-" );
479 + $replace_to = array( "\+", "\:", "\.", "\*", "\;", "\-" );
480 +
510 481 // For multiple queries we have to set correct relation
511 482 if( count( $queried_value['values'] ) > 1 ){
512 483 $relation = ( $queried_value['logic'] === 'and' ) ? 'AND' : 'OR';
513 484
@@ -512,12 +483,13 @@
512 483 $relation = ( $queried_value['logic'] === 'and' ) ? 'AND' : 'OR';
513 484
514 485 foreach ( $queried_value['values'] as $slug ) {
515 486 $term = $this->getTerm($slug);
487 + $term_value = str_replace( $replace_from, $replace_to, $term->meta_value );
516 488 $this->addMetaQueryArray(
517 489 array(
518 490 'key' => $queried_value['e_name'],
519 - 'value' => '.*;s:[0-9]+:"'.$term->meta_value.'".*',
491 + 'value' => '.*;s:[0-9]+:"'.$term_value.'".*',
520 492 'compare' => 'REGEXP'
521 493 ),
522 494 $relation
523 495 );
@@ -526,12 +498,13 @@
526 498 }else{
527 499 // Single term selected in filter
528 500 foreach ( $queried_value['values'] as $slug ) {
529 501 $term = $this->getTerm($slug);
502 + $term_value = str_replace( $replace_from, $replace_to, $term->meta_value );
530 503 $this->addMetaQueryArray(
531 504 array(
532 505 'key' => $queried_value['e_name'],
533 - 'value' => '.*;s:[0-9]+:"'.$term->meta_value.'".*',
506 + 'value' => '.*;s:[0-9]+:"'.$term_value.'".*',
534 507 'compare' => 'REGEXP'
535 508 )
536 509 );
537 510 }
@@ -563,9 +536,8 @@
563 536 'compare' => 'IN'
564 537 )
565 538 );
566 539 }
567 -
568 540 }
569 541
570 542 if( count($this->new_meta_query) > 1 ){
571 543 $this->new_meta_query['relation'] = 'AND';