PluginProbe
Ultimate Post Kit – Elementor Post Grid, Post Carousel, Post Slider & Blog Layout Widgets / trunk
Ultimate Post Kit – Elementor Post Grid, Post Carousel, Post Slider & Blog Layout Widgets vtrunk
4.5.4 4.2.1 4.2.2 4.2.3 4.5.0 4.5.2 4.5.3 4.2.0 4.1.18 4.1.17 4.1.16 4.1.15 4.1.14 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.0.9 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 All 146 releases
ultimate-post-kit / traits / global-widget-functions.php

global-widget-functions.php in Ultimate Post Kit – Elementor Post Grid, Post Carousel, Post Slider & Blog Layout Widgets trunk, at traits/global-widget-functions.php

539 lines 17.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace UltimatePostKit\Traits;
4
5 use UltimatePostKit\Utils;
6
7 defined('ABSPATH') || die();
8
9 trait Global_Widget_Functions {
10
11 /**
12 * Default display flags for the load-more AJAX handlers.
13 *
14 * The widgets always emit every one of these keys into their data-settings
15 * payload, so this never changes rendering for a real request. It only matters
16 * for the unauthenticated wp_ajax_nopriv_* endpoints, where a partial payload
17 * would otherwise make the render loop read undefined array keys and emit a
18 * PHP warning per missing key. Values mirror the widgets' own defaults.
19 *
20 * @return array<string, string>
21 */
22 protected function loadmore_display_defaults() {
23 return [
24 'show_title' => 'yes',
25 'title_tags' => 'h3',
26 'title_style' => 'underline',
27 'show_author' => 'yes',
28 'show_author_name' => 'yes',
29 'show_author_avatar' => 'yes',
30 'show_date' => 'yes',
31 'show_time' => 'no',
32 'show_category' => 'yes',
33 'show_excerpt' => 'yes',
34 'show_readmore' => 'yes',
35 'readmore_type' => '',
36 'show_comments' => 'no',
37 'show_image' => 'yes',
38 'show_post_format' => 'no',
39 'show_reading_time' => 'no',
40 'show_counter_number' => 'no',
41 'human_diff_time' => 'no',
42 'upk_link_new_tab' => 'no',
43 'meta_separator' => '//',
44 ];
45 }
46
47 /**
48 * Render Ajax Qery Posts
49 */
50 function mapGroupControlQuery($term_ids = []) {
51 $terms = get_terms(
52 [
53 'term_taxonomy_id' => $term_ids,
54 'hide_empty' => false,
55 ]
56 );
57
58 $tax_terms_map = [];
59
60 foreach ($terms as $term) {
61 $taxonomy = $term->taxonomy;
62 $tax_terms_map[$taxonomy][] = $term->term_id;
63 }
64
65 return $tax_terms_map;
66 }
67 function query_args() {
68 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified in the load-more AJAX handler (check_ajax_referer 'upk-site') before this runs.
69 if ( isset( $_POST['settings'] ) && is_array( $_POST['settings'] ) ) {
70 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified in the load-more AJAX handler (check_ajax_referer 'upk-site') before this runs.
71 $request_settings = map_deep( wp_unslash( $_POST['settings'] ), 'sanitize_text_field' );
72
73 unset( $request_settings['this'], $request_settings['GLOBALS'] );
74
75 extract( $request_settings, EXTR_SKIP );
76 }
77
78 // extract() only defines what the request actually sent, and this handler is
79 // reachable unauthenticated, so any omitted key would leave the matching
80 // variable undefined. The three below are consumed unconditionally further
81 // down (orderby/order in $args, and $posts_select_date in the date query),
82 // unlike their siblings which are isset()-guarded at the point of use.
83 // Seed them so a partial request cannot emit PHP warnings or push a null
84 // into WP_Query.
85 $posts_orderby = isset( $posts_orderby ) ? $posts_orderby : 'date';
86 $posts_order = isset( $posts_order ) ? $posts_order : 'DESC';
87 $posts_select_date = isset( $posts_select_date ) ? $posts_select_date : '';
88
89 // This handler is reachable unauthenticated (wp_ajax_nopriv_*). Clamp the
90 // page size to a sane positive maximum so a request cannot ask for -1
91 // ("all posts") or a huge value and turn load-more into a DoS amplifier.
92 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified in the load-more AJAX handler (check_ajax_referer 'upk-site') before this runs.
93 $per_page = isset( $_POST['per_page'] ) ? absint( $_POST['per_page'] ) : 0;
94 $per_page = max( 1, min( 100, $per_page ) );
95 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified in the load-more AJAX handler (check_ajax_referer 'upk-site') before this runs.
96 $offset = isset( $_POST['offset'] ) ? absint( $_POST['offset'] ) : 0;
97
98 // setmeta args
99 $args = [
100 'posts_per_page' => $per_page,
101 'post_status' => 'publish',
102 'suppress_filters' => false,
103 'orderby' => $posts_orderby,
104 'order' => $posts_order,
105 'offset' => $offset,
106 ];
107 /**
108 * set feature image
109 *
110 */
111 if (isset($posts_only_with_featured_image) && $posts_only_with_featured_image === 'yes') {
112 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Elementor widget query built from user-configured controls; expected behaviour.
113 $args['meta_query'] = [
114 [
115 'key' => '_thumbnail_id',
116 'compare' => 'EXISTS',
117 ],
118 ];
119 }
120
121 /**
122 * set taxonomy query
123 */
124
125 /**
126 * set date query
127 */
128
129 $selected_date = $posts_select_date;
130
131 if (!empty($selected_date)) {
132 $date_query = [];
133
134 switch ($selected_date) {
135 case 'today':
136 $date_query['after'] = '-1 day';
137 break;
138
139 case 'week':
140 $date_query['after'] = '-1 week';
141 break;
142
143 case 'month':
144 $date_query['after'] = '-1 month';
145 break;
146
147 case 'quarter':
148 $date_query['after'] = '-3 month';
149 break;
150
151 case 'year':
152 $date_query['after'] = '-1 year';
153 break;
154
155 case 'exact':
156 $after_date = $posts_date_after;
157 if (!empty($after_date)) {
158 $date_query['after'] = $after_date;
159 }
160
161 $before_date = $posts_date_before;
162 if (!empty($before_date)) {
163 $date_query['before'] = $before_date;
164 }
165 $date_query['inclusive'] = true;
166 break;
167 }
168
169 if (!empty($date_query)) {
170 $args['date_query'] = $date_query;
171 }
172 }
173
174 $exclude_by = isset($posts_exclude_by) ? $posts_exclude_by : [];
175 $include_by = isset($posts_include_by) ? $posts_include_by : [];
176 $exclude_by = is_array($exclude_by) ? $exclude_by : ( '' === $exclude_by || null === $exclude_by ? [] : [ $exclude_by ] );
177 $include_by = is_array($include_by) ? $include_by : ( '' === $include_by || null === $include_by ? [] : [ $include_by ] );
178 $include_users = [];
179 $exclude_users = [];
180 // print_r($exclude_by);
181 /**
182 * ignore sticky post
183 */
184 if (!empty($exclude_by) && $posts_source === 'post' && $posts_ignore_sticky_posts === 'yes') {
185 $args['ignore_sticky_posts'] = true;
186 if (in_array('current_post', $exclude_by)) {
187 // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Elementor widget query built from user-configured controls; expected behaviour.
188 $args['post__not_in'] = [get_the_ID()];
189 }
190 }
191
192 /**
193 * set post type
194 */
195
196 if ($posts_source === 'manual_selection') {
197 /**
198 * Set Including Manually
199 */
200 $selected_ids = $posts_selected_ids;
201 $selected_ids = wp_parse_id_list($selected_ids);
202
203 // Both $posts_source and $posts_selected_ids arrive from $_POST on the
204 // wp_ajax_nopriv_* load-more handlers, so an anonymous caller can pick this
205 // branch and name arbitrary IDs. 'any' only filters on exclude_from_search,
206 // which is weaker than the allowlist the else branch below applies: a post
207 // type registered public => true, publicly_queryable => false is rejected by
208 // is_post_type_viewable() but still matched by 'any'. Route this branch
209 // through the same allowlist so every path out of query_args() agrees.
210 $args['post_type'] = ultimate_post_kit_sanitize_public_post_type(
211 array_values( get_post_types( [ 'public' => true, 'exclude_from_search' => false ] ) )
212 );
213 if (!empty($selected_ids)) {
214 $args['post__in'] = $selected_ids;
215 }
216 $args['ignore_sticky_posts'] = 1;
217 } elseif ('current_query' === $posts_source) {
218 /**
219 * Make Current Query
220 */
221 $args = $GLOBALS['wp_query']->query_vars;
222 $args = apply_filters('ultimate_post_kit/query/get_query_args/current_query', $args);
223 } elseif ('_related_post_type' === $posts_source) {
224 /**
225 * Set Related Query
226 */
227 $post_id = get_queried_object_id();
228 $related_post_id = is_singular() && (0 !== $post_id) ? $post_id : null;
229 $args['post_type'] = get_post_type($related_post_id);
230
231 // $include_by = $this->getGroupControlQueryParamBy('include');
232 if (in_array('authors', $include_by) && isset($posts_include_author_ids)) {
233 $args['author__in'] = wp_parse_id_list($posts_include_author_ids);
234 } else {
235 $args['author__in'] = get_post_field('post_author', $related_post_id);
236 }
237
238 // $exclude_by = $this->getGroupControlQueryParamBy('exclude');
239 if (in_array('authors', $exclude_by)) {
240 $args['author__not_in'] = wp_parse_id_list($posts_exclude_author_ids);
241 }
242
243 if (in_array('current_post', $exclude_by)) {
244 // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Elementor widget query built from user-configured controls; expected behaviour.
245 $args['post__not_in'] = [get_the_ID()];
246 }
247
248 $args['ignore_sticky_posts'] = 1;
249 $args = apply_filters('ultimate_post_kit/query/get_query_args/related_query', $args);
250 } else {
251 // This runs on wp_ajax_nopriv_* and $posts_source comes straight from $_POST,
252 // so restrict it to post types this site already exposes to anonymous visitors.
253 // Without this a request can enumerate published entries of post types that are
254 // deliberately hidden from anonymous access (e.g. Elementor's elementor_library).
255 $args['post_type'] = ultimate_post_kit_sanitize_public_post_type( $posts_source );
256 $current_post = [];
257
258 /**
259 * Set Taxonomy && Set Authors
260 */
261 $include_terms = [];
262 $exclude_terms = [];
263 $terms_query = [];
264
265 /**
266 * Set exclude Terms
267 */
268 if (!empty($exclude_by)) {
269 if (in_array('authors', $exclude_by)) {
270 $exclude_users = wp_parse_id_list($posts_exclude_author_ids);
271 $include_users = array_diff($include_users, $exclude_users);
272 }
273 if (!empty($exclude_users)) {
274 $args['author__not_in'] = $exclude_users;
275 }
276 if (in_array('current_post', $exclude_by) && is_singular()) {
277 $current_post[] = get_the_ID();
278 }
279 if (in_array('manual_selection', $exclude_by)) {
280 $exclude_ids = $posts_exclude_ids;
281 // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Elementor widget query built from user-configured controls; expected behaviour.
282 $args['post__not_in'] = array_merge($current_post, wp_parse_id_list($exclude_ids));
283 }
284 if (in_array('terms', $exclude_by)) {
285 $exclude_terms = wp_parse_id_list($posts_exclude_term_ids);
286 $include_terms = array_diff($include_terms, $exclude_terms);
287 }
288 if (!empty($exclude_terms)) {
289 $tax_terms_map = $this->mapGroupControlQuery($exclude_terms);
290
291 foreach ($tax_terms_map as $tax => $terms) {
292 $terms_query[] = [
293 'taxonomy' => $tax,
294 'field' => 'term_id',
295 'terms' => $terms,
296 'operator' => 'NOT IN',
297 ];
298 }
299 }
300 }
301
302 /**
303 * Set include Terms
304 */
305 if (!empty($include_by)) {
306
307 if (in_array('authors', $include_by)) {
308 $include_users = wp_parse_id_list($posts_include_author_ids);
309 }
310 if (!empty($include_users)) {
311 $args['author__in'] = $include_users;
312 }
313 if (in_array('terms', $include_by)) {
314 $include_terms = wp_parse_id_list($posts_include_term_ids);
315 }
316 $tax_terms_map = $this->mapGroupControlQuery($include_terms);
317 foreach ($tax_terms_map as $tax => $terms) {
318 $terms_query[] = [
319 'taxonomy' => $tax,
320 'field' => 'term_id',
321 'terms' => $terms,
322 'operator' => 'IN',
323 ];
324 }
325 }
326
327
328 if (!empty($terms_query)) {
329 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- Elementor widget query built from user-configured controls; expected behaviour.
330 $args['tax_query'] = $terms_query;
331 $args['tax_query']['relation'] = 'AND';
332 }
333 }
334
335 if ( empty( $args['post_status'] ) || 'publish' !== $args['post_status'] ) {
336 $args['post_status'] = 'publish';
337 }
338
339 $ajaxposts = new \WP_Query($args);
340 return $ajaxposts;
341 }
342
343 /**
344 * Render Ajax LoadMore
345 */
346 function render_ajax_loadmore() {
347 $settings = $this->get_settings_for_display();
348 if (!$this->get_settings('ajax_loadmore_enable')) {
349 return;
350 }
351 ?>
352 <div class="upk-loadmore-container">
353 <?php if ($settings['ajax_loadmore_btn'] == 'yes') : ?>
354 <span class="upk-loadmore-btn"><?php esc_html_e('Load More', 'ultimate-post-kit'); ?></span>
355 <?php elseif ($settings['ajax_loadmore_infinite_scroll'] == 'yes') : ?>
356 <div class="upk-ajax-loading" style="display: none;"></div>
357 <?php endif; ?>
358 </div>
359 <?php
360 }
361
362 /**
363 * New Image Render Method With Lazy Load Support
364 *
365 * @return void
366 */
367
368 function render_image($image_id, $size) {
369 $placeholder_image_src = Utils::get_placeholder_image_src();
370 $image_src = wp_get_attachment_image_src($image_id, $size);
371
372 if (!$image_src) {
373 printf('<img class="upk-img" src="%1$s" alt="%2$s">', esc_url($placeholder_image_src), esc_attr(get_the_title()));
374 } else {
375 print(wp_get_attachment_image(
376 $image_id,
377 $size,
378 false,
379 [
380 'class' => 'upk-img',
381 'alt' => esc_attr(get_the_title())
382 ]
383 ));
384 }
385 }
386
387 /**
388 * Old Image Render Method
389 * Not Using Anymore
390 * @return void
391 */
392 function __render_image($image_id, $size) {
393 $placeholder_image_src = Utils::get_placeholder_image_src();
394 $image_src = wp_get_attachment_image_src($image_id, $size);
395 if (!$image_src) {
396 $image_src = $placeholder_image_src;
397 } else {
398 $image_src = $image_src[0];
399 }
400 ?>
401 <img class="upk-img" src="<?php echo esc_url($image_src); ?>" alt="<?php echo esc_attr(get_the_title()); ?>">
402 <?php
403 }
404
405 function render_title($widget_name) {
406 $settings = $this->get_settings_for_display();
407 if (!$this->get_settings('show_title')) {
408 return;
409 }
410 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- established hook name relied on across the plugin family; renaming would break integration.
411 apply_filters('upk/' . $widget_name . '/before/title', '');
412 $title = get_the_title();
413 printf(
414 '<%1$s class="upk-title"><a href="%2$s" title="%5$s" class="title-animation-%4$s" aria-label="%5$s">%3$s</a></%1$s>',
415 esc_attr(Utils::get_valid_html_tag($settings['title_tags'])),
416 esc_url( get_permalink() ),
417 esc_html( $title ),
418 esc_attr($settings['title_style']),
419 esc_attr( $title )
420 );
421 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- established hook name relied on across the plugin family; renaming would break integration.
422 apply_filters('upk/' . $widget_name . '/after/title', '');
423 }
424
425
426
427 function render_category() {
428 if (!$this->get_settings('show_category')) {
429 return;
430 }
431 ?>
432 <div class="upk-category">
433 <?php echo wp_kses_post( upk_get_category($this->get_settings('posts_source')) ); ?>
434 </div>
435 <?php
436 }
437
438 function render_date() {
439 $settings = $this->get_settings_for_display();
440 if (!$this->get_settings('show_date')) {
441 return;
442 }
443 $meta_separator = isset( $settings['meta_separator'] ) ? $settings['meta_separator'] : '.';
444 ?>
445 <div class="upk-date">
446 <?php if ($settings['human_diff_time'] == 'yes') {
447 echo esc_html( ultimate_post_kit_post_time_diff( ($settings['human_diff_time_short'] == 'yes') ? 'short' : '' ) );
448 } else {
449 echo get_the_date();
450 } ?>
451 </div>
452
453 <?php if ($settings['show_time']) : ?>
454 <div class="upk-post-time" data-separator="<?php echo esc_attr( $meta_separator ); ?>">
455 <i class="upk-icon-clock" aria-hidden="true"></i>
456 <?php echo esc_html( get_the_time() ); ?>
457 </div>
458 <?php endif; ?>
459 <?php
460 }
461
462 function render_excerpt($excerpt_length) {
463 $settings = $this->get_settings_for_display();
464
465 if (!$this->get_settings('show_excerpt')) {
466 return;
467 }
468 $strip_shortcode = $this->get_settings_for_display('strip_shortcode');
469
470 $ellipsis = isset($settings['ellipsis']) ? $settings['ellipsis'] : '' ;
471 ?>
472 <div class="upk-text">
473 <?php
474 if (has_excerpt()) {
475 the_excerpt();
476 } else {
477 echo wp_kses_post( ultimate_post_kit_custom_excerpt($excerpt_length, $strip_shortcode, $ellipsis) );
478 }
479 ?>
480 </div>
481 <?php
482 }
483
484 /**
485 * Localized comment count with label. Echo with esc_html().
486 *
487 * @param int $post_id Post ID, or 0 for current post in The Loop.
488 * @return string
489 */
490 protected function upk_get_formatted_comments_count( $post_id = 0 ) {
491 return Utils::get_formatted_comments_count( $post_id );
492 }
493
494 /**
495 * Localized comment count number only. Echo with esc_html().
496 *
497 * @param int $post_id Post ID, or 0 for current post in The Loop.
498 * @return string
499 */
500 protected function upk_get_localized_comment_count( $post_id = 0 ) {
501 return Utils::get_localized_comment_count( $post_id );
502 }
503
504 function render_post_format() {
505 $settings = $this->get_settings_for_display();
506
507 if (!$settings['show_post_format']) {
508 return;
509 }
510 ?>
511 <div class="upk-post-format">
512 <a href="<?php echo esc_url(get_permalink()) ?>">
513 <?php if (has_post_format('aside')) : ?>
514 <i class="upk-icon-aside" aria-hidden="true"></i>
515 <?php elseif (has_post_format('gallery')) : ?>
516 <i class="upk-icon-gallery" aria-hidden="true"></i>
517 <?php elseif (has_post_format('link')) : ?>
518 <i class="upk-icon-link" aria-hidden="true"></i>
519 <?php elseif (has_post_format('image')) : ?>
520 <i class="upk-icon-image" aria-hidden="true"></i>
521 <?php elseif (has_post_format('quote')) : ?>
522 <i class="upk-icon-quote" aria-hidden="true"></i>
523 <?php elseif (has_post_format('status')) : ?>
524 <i class="upk-icon-status" aria-hidden="true"></i>
525 <?php elseif (has_post_format('video')) : ?>
526 <i class="upk-icon-video" aria-hidden="true"></i>
527 <?php elseif (has_post_format('audio')) : ?>
528 <i class="upk-icon-music" aria-hidden="true"></i>
529 <?php elseif (has_post_format('chat')) : ?>
530 <i class="upk-icon-chat" aria-hidden="true"></i>
531 <?php else : ?>
532 <i class="upk-icon-post" aria-hidden="true"></i>
533 <?php endif; ?>
534 </a>
535 </div>
536 <?php
537 }
538 }
539