PluginProbe
Filter Everything — WordPress & WooCommerce Filters / trunk
Filter Everything — WordPress & WooCommerce Filters vtrunk
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 / Entities / PostDateEntity.php

PostDateEntity.php in Filter Everything — WordPress & WooCommerce Filters trunk, at src/Entities/PostDateEntity.php

582 lines 18.9 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('ABSPATH') ) {
6 exit;
7 }
8
9 class PostDateEntity implements Entity
10 {
11 public $items = [];
12 /**
13 * Declared explicitly: assigned from the outside in
14 * EntityManager::prepareEntitiesToDisplay(); dynamic properties are
15 * deprecated since PHP 8.2.
16 */
17 public $items_sort = [];
18
19 public $filter = [];
20
21 public $entityName = '';
22
23 public $excludedTerms = [];
24
25 public $isInclude = false;
26
27 public $new_date_query = [];
28
29 public $postTypes = [];
30
31 public $from = false;
32
33 public $to = false;
34
35 public $time_from = false;
36
37 public $time_to = false;
38
39 public $date_type = '';
40
41 private $doRecalculate = '';
42
43 private $post_and_types = [];
44
45 public function __construct( $name, $postType )
46 {
47 /**
48 * @feature clean code from unused methods
49 */
50 // This object is being created two times
51 // One time without post type when RequestParser select all filter terms
52 // Second time with post type when Filters widget requires filter terms to display them
53 $this->entityName = $name;
54 $this->setPostTypes( array($postType) );
55 $this->setFromAndTo();
56
57 $this->getAllExistingTerms();
58 }
59
60 public function setPostTypes( $postTypes = [] )
61 {
62 if ( isset( $postTypes[0] ) ) {
63 $this->postTypes = $postTypes;
64 }
65 }
66
67 public static function inputName( $slug, $edge = 'from' )
68 {
69 return $slug . '_' . $edge;
70 }
71
72 public function setExcludedTerms($excludedTerms, $isInclude)
73 {
74 $this->excludedTerms = $excludedTerms;
75 $this->isInclude = $isInclude;
76 }
77
78 public function getName()
79 {
80 return $this->entityName;
81 }
82
83 function excludeTerms($terms)
84 {
85 return $terms;
86 }
87
88 function getTerms()
89 {
90 return $this->excludeTerms($this->getAllExistingTerms());
91 }
92
93 /**
94 * @param int $id term id
95 * @return false|object term object of false
96 */
97 public function getTerm( $termId )
98 {
99 if (!$termId) {
100 return false;
101 }
102
103 foreach ($this->getTerms() as $term) {
104 if ($termId == $term->term_id) {
105 return $term;
106 }
107 }
108
109 return false;
110 }
111
112 public function getTermId($slug)
113 {
114 /**
115 * Post meta num value has no typical ID, so slug will be instead
116 */
117 return $slug . '_' . $this->getName();
118 }
119
120 /**
121 * @return array list of term_id and names useful to create Select dropdown
122 */
123 public function getTermsForSelect()
124 {
125 $toSelect = [];
126 foreach ($this->getTerms() as $term) {
127 $toSelect[$term->slug] = $term->name;
128 }
129
130 return $toSelect;
131 }
132
133 public function getTermsForSelect2()
134 {
135 $toSelect = [];
136 foreach ($this->getTerms() as $term) {
137 $toSelect[] = array( 'id' => $term->slug, 'text' => $term->name );
138 }
139
140 return $toSelect;
141 }
142
143 function getAllExistingTerms( $force = false )
144 {
145 if (empty($this->items) || $force) {
146 $this->items = $this->selectTerms();
147 }
148 return $this->items;
149 }
150
151 function populateTermsWithPostIds( $setId, $post_type )
152 {
153 // Does nothing. It was already done before.
154 }
155
156 protected function queryTerms()
157 { global $wpdb;
158 // @todo delete appropriate transient in resetTransitions();
159 $IN = false;
160 $translatable_post_type_exists = false;
161 $lang = '';
162 $key_in = '';
163
164 /**
165 * Check if any post type is translatable
166 */
167 if ( flrt_wpml_active() && defined('ICL_LANGUAGE_CODE') ) {
168 $lang = ICL_LANGUAGE_CODE;
169 $wpml_settings = get_option('icl_sitepress_settings');
170
171 foreach ($this->postTypes as $type) {
172 if (isset($wpml_settings['custom_posts_sync_option'][$type])) {
173 if ($wpml_settings['custom_posts_sync_option'][$type] === '1') {
174 $translatable_post_type_exists = true;
175 break;
176 }
177 }
178 }
179 }
180
181 /**
182 * Set Post types
183 */
184 if (!empty($this->postTypes) && isset($this->postTypes[0]) && $this->postTypes[0]) {
185 foreach ($this->postTypes as $postType) {
186 $key_in .= '_' . $postType;
187 $pieces[] = $wpdb->prepare("%s", $postType );
188 }
189
190 $IN = implode(", ", $pieces);
191 }
192
193 /**
194 * Set transient key
195 */
196 $transient_key = flrt_get_terms_transient_key($this->getName() . $key_in . $lang);
197
198 if ( false === ( $result = flrt_get_transient( $transient_key ) ) ) {
199 // Get all post meta values
200 $sql[] = "SELECT {$wpdb->posts}.ID,{$wpdb->posts}.post_date,{$wpdb->posts}.post_type";
201 $sql[] = "FROM {$wpdb->posts}";
202
203 /**
204 * If post type is translatable with WPML, get post meta values only with current language
205 */
206 if (flrt_wpml_active() && $lang && $translatable_post_type_exists) {
207 $sql[] = "LEFT JOIN {$wpdb->prefix}icl_translations AS wpml_translations";
208 $sql[] = "ON {$wpdb->posts}.ID = wpml_translations.element_id";
209
210 if (!empty($this->postTypes)) {
211
212 $sql[] = "AND wpml_translations.element_type IN(";
213
214 foreach ($this->postTypes as $type) {
215 $LANG_IN[] = $wpdb->prepare("CONCAT('post_', '%s')", $type);
216 }
217 $sql[] = implode(",", $LANG_IN);
218
219 $sql[] = ")";
220 }
221 }
222
223 $sql[] = "WHERE 1=1";
224
225 if ($IN) {
226 $sql[] = "AND {$wpdb->posts}.post_type IN( {$IN} )";
227 }
228
229 if (flrt_wpml_active() && $lang && $translatable_post_type_exists) {
230 $sql[] = $wpdb->prepare("AND wpml_translations.language_code = '%s'", $lang);
231 }
232
233 /**
234 * Filters terms SQL-query and allows to modify it
235 */
236 $sql = apply_filters('wpc_filter_get_post_date_terms_sql', $sql, $this->postTypes);
237
238 $sql = implode(' ', $sql);
239
240 $result = $wpdb->get_results($sql, ARRAY_A);
241
242 flrt_set_transient( $transient_key, $result, FLRT_TRANSIENT_PERIOD_HOURS * HOUR_IN_SECONDS );
243 }
244
245 return $result;
246 }
247
248 /**
249 * Selects all post dates within specified post_type.
250 * Also generates $min and $max terms with list of posts belong to them.
251 * If the $alreadyFilteredPosts specified it limits selected posts to
252 * specified in the variable.
253 *
254 * @param array $alreadyFilteredPosts
255 */
256 public function selectTerms( $alreadyFilteredPosts = [] )
257 {
258 /**
259 * Do not query post dates from DB without post type
260 */
261 if ( ! $this->postTypes[0] ) {
262 return [];
263 }
264
265 $em = Container::instance()->getEntityManager();
266 $filter = $em->getFilterByEname( $this->entityName );
267 $format = $filter['date_format'];
268
269 $new_result = [];
270 $new_time_result = [];
271 $post_and_meta_values = [];
272 // $from_and_to = [
273 // 'from' => 0,
274 // 'to' => 0
275 // ];
276
277 $result = $this->queryTerms();
278 $doRecalculate = $this->doRecalculate( $alreadyFilteredPosts );
279
280 if ( ! empty( $result ) && $doRecalculate ) {
281
282 $postsIn_flipped = array_flip( $alreadyFilteredPosts );
283
284 foreach ( $result as $single_post ) {
285 /**
286 * If there are already filtered posts, we have to skip posts
287 * that are out of the queried list
288 */
289 if ( ! empty( $alreadyFilteredPosts ) ) {
290 if ( ! isset( $postsIn_flipped[$single_post['ID']] ) ) {
291 continue;
292 }
293 }
294
295 if (empty($single_post['post_date'])){
296 continue;
297 }
298
299 /**
300 * We have to generate and fill two arrays
301 * First to detect $min and $max values
302 * Second to map post_types with post IDs
303 */
304 $new_result[] = $single_post['post_date'];
305 $new_time_result[] = flrt_clean_date_time( $single_post['post_date'], 'time' );
306 $post_and_meta_values[ $single_post['ID'] ] = flrtBuildDateTimeString(flrt_apply_date_format( $single_post['post_date'], $format ), $format);
307
308 if ( $this->from !== false && flrt_clean_date_time( $single_post['post_date'], $this->date_type ) < $this->from ){
309 continue;
310 }
311
312 if ( $this->to !== false && flrt_clean_date_time( $single_post['post_date'], $this->date_type ) > $this->to ){
313 continue;
314 }
315
316 $this->post_and_types[$single_post['ID']] = $single_post['post_type'];
317 }
318 }
319
320 if ( ! empty( $new_result ) ) {
321 // We need to find minimum and maximum date values
322 $from_and_to = [
323 'from' => apply_filters( 'wpc_set_date_shift', min( $new_result ), $this->getName() ),
324 'to' => apply_filters( 'wpc_set_date_shift', max( $new_result ), $this->getName() ),
325 ];
326
327 $time_from_and_to = [
328 'from' => apply_filters( 'wpc_set_date_shift', min( $new_time_result ), $this->getName() ),
329 'to' => apply_filters( 'wpc_set_date_shift', max( $new_time_result ), $this->getName() ),
330 ];
331
332 } else {
333 // if we didn't recalculate items, it means we have them already stored in the object
334 return $this->items;
335 }
336
337 $from_and_to = apply_filters( 'wpc_set_from_to', $from_and_to, $this->getName() );
338 $x = $this->convertSelectResult( $from_and_to, $time_from_and_to, $post_and_meta_values );
339
340 return $x;
341 }
342
343 public function convertSelectResult( $result, $time_from_and_to, $post_and_meta_values ){
344 $return = [];
345
346 if( ! is_array( $result ) ){
347 return $return;
348 }
349
350 // To make standard format for terms array;
351 $i = 1;
352 $wpManager = Container::instance()->getWpManager();
353 $queried_values = $wpManager->getQueryVar( 'queried_values' );
354
355 foreach ( $result as $edge => $value ){
356 $time_edge = 'time_'.$edge;
357
358 $termObject = new \stdClass();
359 $termObject->slug = $edge;
360 $termObject->name = $this->createTermName( $edge, $value, $queried_values );
361 $termObject->term_id = $edge . '_' . $this->getName();
362 $termObject->posts = array_keys( $this->post_and_types );
363 $termObject->meta_values = $post_and_meta_values;
364 $termObject->count = 0;
365 $termObject->cross_count = 0;
366 $termObject->post_types = $this->post_and_types; //[];
367 $termObject->$edge = $value;
368 $termObject->$time_edge = $time_from_and_to[$edge];
369 $termObject->wp_queried = false;
370
371 $return[ $edge ] = $termObject;
372
373 $i++;
374 }
375
376 return $return;
377 }
378
379 protected function createTermName( $edge, $value, $queried_values )
380 {
381 $queriedFilter = false;
382 if( $edge === 'from' ) {
383 $name = esc_html__('After', 'filter-everything' );
384 } else if( $edge === 'to' ) {
385 $name = esc_html__('Before', 'filter-everything' );
386 }
387
388 if( $queried_values ){
389 foreach ( $queried_values as $slug => $filter ){
390 if( $filter['e_name'] === $this->getName() ){
391 $queriedFilter = $filter;
392 break;
393 }
394 }
395 }
396
397 if( isset( $queriedFilter['values'][$edge] ) ) {
398 $format = $queriedFilter['date_format'];
399 $date_type = false;
400
401 if ( isset( $queriedFilter['values']['from'] ) ) {
402 $date_type = flrt_detect_date_type( $queriedFilter['values']['from'] );
403 } else if ( isset( $queriedFilter['values']['to'] ) ) {
404 $date_type = flrt_detect_date_type($queriedFilter['values']['to']);
405 }
406
407 // IN case if we have several date filters on the same page
408 if ( in_array( $date_type, [ 'date', 'time' ] ) ) {
409 $maybe_split_format = flrt_split_date_time( $format );
410 if ( isset( $maybe_split_format[$date_type] ) ) {
411 $format = $maybe_split_format[$date_type];
412 }
413 }
414
415 $name = $name .' '. flrt_apply_date_format( $queriedFilter['values'][$edge], $format );
416 }else{
417 $name = $name .' '. $value;
418 }
419
420 return apply_filters( 'wpc_filter_post_date_term_name', $name, $this->getName() );
421 }
422
423 protected function doRecalculate($alreadyFilteredPosts )
424 {
425 $do_filtered_posts = md5( json_encode( $alreadyFilteredPosts ) );
426 $do_from = md5( $this->from );
427 $do_to = md5( $this->to );
428
429 $result = md5( $do_filtered_posts . $do_from . $do_to );
430
431 if ( $result === $this->doRecalculate ) {
432 return false;
433 }
434
435 $this->doRecalculate = $result;
436 return true;
437 }
438
439 /**
440 * Sets $this->from and $this->to values if there are queried date filter(s)
441 */
442 private function setFromAndTo()
443 {
444 $wpManager = Container::instance()->getWpManager();
445 $queried_values = $wpManager->getQueryVar( 'queried_values', [] );
446 $filter_slug = false;
447
448 /**
449 * Check if this filter was queried
450 */
451 foreach ( $queried_values as $slug => $filter ) {
452 /**
453 * At this point we do not know what exactly filter was queired
454 * because filters with the same slug can be in multiple Filter Sets with
455 * different date_type values.
456 */
457 if ( $filter['e_name'] === $this->getName() ) {
458 $filter_slug = $slug;
459 break;
460 }
461 }
462
463 /**
464 * If this filter was queried we have to receive its $from and $to values
465 */
466 if ( $filter_slug ) {
467 if ( isset( $queried_values[$filter_slug]['values']['to'] ) && $this->to === false ) {
468 $to = str_replace( '.', ':', $queried_values[$filter_slug]['values']['to'] );
469 $this->date_type = flrt_detect_date_type( $to );
470 $this->to = $this->time_to = apply_filters( 'wpc_unset_date_shift', $to, $this->getName(), $this->date_type );
471 if ( $this->date_type === 'time' ) {
472 $this->time_to = $this->to;
473 }
474 }
475
476 if ( isset( $queried_values[$filter_slug]['values']['from'] ) && $this->from === false ) {
477 $from = str_replace( '.', ':', $queried_values[$filter_slug]['values']['from'] );
478 $this->date_type = flrt_detect_date_type( $from );
479 $this->from = apply_filters( 'wpc_unset_date_shift', $from, $this->getName(), $this->date_type );
480 if ( $this->date_type === 'time' ) {
481 $this->time_from = $this->from;
482 }
483 }
484 }
485 }
486
487 /**
488 * Updates object $this->items in accordance with already queried posts
489 * @param $postsIn
490 */
491 public function updateMinAndMaxValues( $postsIn )
492 {
493 if ( ! empty( $this->items ) && ! empty( $postsIn ) ) {
494 $newItems = $this->selectTerms( $postsIn );
495
496 foreach ( $this->items as $index => $term ) {
497 $time_index = 'time_'.$index;
498 if ( isset( $this->items[$index]->$index ) ) {
499 $this->items[$index]->$index = $newItems[$index]->$index;
500 $this->items[$index]->$time_index = $newItems[$index]->$time_index;
501 }
502 }
503 }
504 }
505
506 /**
507 * @return object WP_Query
508 */
509 public function addTermsToWpQuery( $queried_value, $wp_query )
510 {
511 $wpc_date_query = [];
512 $possible_date_query = $wp_query->get( 'date_query' );
513
514 if( ! empty( $possible_date_query ) ) {
515 $this->new_date_query = $possible_date_query;
516 }
517 /**
518 * @todo Nested date queries?
519 */
520 $from = isset( $queried_value['values']['from'] ) ? $queried_value['values']['from'] : false;
521 $to = isset( $queried_value['values']['to'] ) ? $queried_value['values']['to'] : false;
522
523 if ( $from !== false ) {
524
525 $date_time_pieces = date_parse( $from );
526
527 foreach ( [ 'year', 'month', 'day', 'hour', 'minute', 'second' ] as $item) {
528 if ( $date_time_pieces[$item] !== false ) {
529 $date_from_query[$item] = $date_time_pieces[$item];
530 }
531 }
532
533 // It means datetime query
534 if ( count( $date_from_query ) > 3 ) {
535 $wpc_date_query['from'] = $from;
536 } else {
537 // either date or time request
538 if( isset( $date_from_query['year'] ) && isset( $date_from_query['month'] ) && isset( $date_from_query['day'] ) ) {
539 $this->new_date_query['after'] = $date_from_query;
540 $this->new_date_query['inclusive'] = true;
541 } else {
542 $date_from_query['compare'] = '>=' ;
543 $this->new_date_query[] = $date_from_query;
544 }
545 }
546 }
547
548 if( $to !== false ){
549 $date_time_pieces = date_parse( $to );
550
551 foreach ( [ 'year', 'month', 'day', 'hour', 'minute', 'second' ] as $item) {
552 if ( $date_time_pieces[$item] !== false ) {
553 $date_to_query[$item] = $date_time_pieces[$item];
554 }
555 }
556 // It means datetime query
557 if ( count( $date_to_query ) > 3 ) {
558 $wpc_date_query['to'] = $to;
559 } else {
560 if ( isset( $date_to_query['year'] ) && isset( $date_to_query['month'] ) && isset( $date_to_query['day'] ) ) {
561 $this->new_date_query['before'] = $date_to_query;
562 $this->new_date_query['inclusive'] = true;
563 } else {
564 $date_to_query['compare'] = '<=' ;
565 $this->new_date_query[] = $date_to_query;
566 }
567 }
568 }
569
570 if( ! empty( $this->new_date_query ) ) {
571 $wp_query->set( 'date_query', $this->new_date_query );
572 }
573
574 if ( ! empty( $wpc_date_query ) ) {
575 $wp_query->set( 'wpc_date_query', $wpc_date_query );
576 }
577
578 $this->new_date_query = [];
579
580 return $wp_query;
581 }
582 }