PluginProbe
UiCore Elements – Free widgets and templates for Elementor / trunk
UiCore Elements – Free widgets and templates for Elementor vtrunk
1.3.16 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.3.0 All 40 releases
uicore-elements / includes / utils / meta-component.php

meta-component.php in UiCore Elements – Free widgets and templates for Elementor trunk, at includes/utils/meta-component.php

679 lines 25.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace UiCoreElements\Utils;
4
5 use Elementor\Controls_Manager;
6 use Elementor\Group_Control_Typography;
7 use UiCoreElements\Helper;
8
9 if (! defined('ABSPATH')) {
10 exit; // Exit if accessed directly
11 }
12
13 trait Meta_Trait
14 {
15
16 function get_meta_content_controls($product_metas = false)
17 {
18
19 // Meta options
20 $options = [
21 'none' => __('None', 'uicore-elements'),
22 ];
23 $generic_posts = [
24 'author' => __('Author', 'uicore-elements'),
25 'date' => __('Posted Date', 'uicore-elements'),
26 'updated date' => __('Updated Date', 'uicore-elements'),
27 'comment' => __('Comments Count', 'uicore-elements'),
28 'reading time' => __('Reading Time', 'uicore-elements'),
29 'category' => __('Category', 'uicore-elements'),
30 'tag' => __('Tag', 'uicore-elements'),
31 'excerpt' => __('Excerpt', 'uicore-elements'),
32 ];
33 $products = [
34 'product price' => __('Product Price', 'uicore-elements'),
35 'product rating' => __('Product Rating', 'uicore-elements'),
36 'product stock' => __('Product Stock', 'uicore-elements'),
37 'product category' => __('Product Category', 'uicore-elements'),
38 'product tag' => __('Product Tag', 'uicore-elements'),
39 'product attribute' => __('Product Attribute', 'uicore-elements'),
40 'product sale' => __('Product Sale', 'uicore-elements'),
41 ];
42 $custom = [
43 'custom meta' => __('Custom Meta', 'uicore-elements'),
44 'custom taxonomy' => __('Custom Taxonomy', 'uicore-elements'),
45 'custom html' => __('Custom HTML', 'uicore-elements'),
46 ];
47 $portfolio = [
48 'portfolio category' => __('Portfolio Category', 'uicore-elements'),
49 ];
50
51 $options = $product_metas ?
52 array_merge($options, $products, $custom) : // specific to product widgets
53 array_merge($options, $generic_posts, $products, $portfolio, $custom); // generic posts widgets
54
55
56 $repeater = new \Elementor\Repeater();
57 $repeater->add_control(
58 'type',
59 [
60 'label' => __('Meta', 'uicore-elements'),
61 'type' => Controls_Manager::SELECT,
62 'default' => '',
63 'options' => $options
64 ]
65 );
66 $repeater->add_control(
67 'type_custom',
68 [
69 'label' => __('Custom Field Name', 'uicore-elements'),
70 'type' => Controls_Manager::TEXT,
71 'condition' => [
72 'type' => ['custom meta', 'custom taxonomy']
73 ]
74 ]
75 );
76 $repeater->add_control(
77 'html_custom',
78 [
79 'label' => __('Custom HTML', 'uicore-elements'),
80 'type' => Controls_Manager::TEXTAREA,
81 'condition' => [
82 'type' => ['custom html']
83 ]
84 ]
85 );
86 $repeater->add_control(
87 'date_format',
88 [
89 'label' => esc_html__('Date Format', 'uicore-elements'),
90 'type' => Controls_Manager::SELECT,
91 'default' => 'default',
92 'options' => [
93 'default' => esc_html__('Default', 'uicore-elements'),
94 'F j, Y' => gmdate('F j, Y'),
95 'Y-m-d' => gmdate('Y-m-d'),
96 'm/d/Y' => gmdate('m/d/Y'),
97 'd/m/Y' => gmdate('d/m/Y'),
98 'custom' => esc_html__('Custom', 'uicore-elements'),
99 ],
100 'condition' => [
101 'type' => ['date', 'updated date']
102 ]
103 ]
104 );
105 $repeater->add_control(
106 'custom_format',
107 [
108 'label' => esc_html__('Custom Format', 'uicore-elements'),
109 'default' => get_option('date_format') . ' ' . get_option('time_format'),
110 'description' => sprintf('<a href="https://wordpress.org/documentation/article/customize-date-and-time-format/" target="_blank">%s</a>', esc_html__('Documentation on date and time formatting', 'uicore-elements')),
111 'condition' => [
112 'date_format' => 'custom',
113 'type' => ['date', 'updated date']
114 ],
115 ]
116 );
117 $repeater->add_control(
118 'stock_type',
119 [
120 'label' => esc_html__('Stock Data', 'uicore-elements'),
121 'type' => Controls_Manager::SELECT,
122 'default' => 'qty',
123 'options' => [
124 'qty' => esc_html__('Quantity', 'uicore-elements'),
125 'in' => esc_html__('In/Out of stock', 'uicore-elements'),
126 ],
127 'condition' => [
128 'type' => ['product stock']
129 ]
130 ]
131 );
132 $repeater->add_control(
133 'in_stock_text',
134 [
135 'label' => esc_html__('In Stock Text', 'uicore-elements'),
136 'type' => Controls_Manager::TEXT,
137 'default' => esc_html__('In Stock', 'uicore-elements'),
138 'condition' => [
139 'type' => ['product stock'],
140 'stock_type' => 'in',
141 ]
142 ]
143 );
144 $repeater->add_control(
145 'out_of_stock_text',
146 [
147 'label' => esc_html__('Out of Stock Text', 'uicore-elements'),
148 'type' => Controls_Manager::TEXT,
149 'default' => esc_html__('Out of Stock', 'uicore-elements'),
150 'condition' => [
151 'type' => ['product stock'],
152 ]
153 ]
154 );
155 $repeater->add_control(
156 'in_stock_color',
157 [
158 'label' => __('In Stock Color', 'uicore-elements'),
159 'type' => Controls_Manager::COLOR,
160 'selectors' => [
161 '{{WRAPPER}} .in-stock' => 'color: {{VALUE}}',
162 ],
163 'condition' => [
164 'type' => ['product stock'],
165 ]
166 ]
167 );
168 $repeater->add_control(
169 'out_of_stock_color',
170 [
171 'label' => __('Out of Stock Color', 'uicore-elements'),
172 'type' => Controls_Manager::COLOR,
173 'selectors' => [
174 '{{WRAPPER}} .out-of-stock' => 'color: {{VALUE}}',
175 ],
176 'condition' => [
177 'type' => ['product stock'],
178 ]
179 ]
180 );
181 $repeater->add_control(
182 'before',
183 [
184 'label' => __('Text Before', 'uicore-elements'),
185 'type' => Controls_Manager::TEXT,
186 'condition' => [
187 'type!' => 'none',
188 ]
189 ]
190 );
191 $repeater->add_control(
192 'after',
193 [
194 'label' => __('Text After', 'uicore-elements'),
195 'type' => Controls_Manager::TEXT,
196 'condition' => [
197 'type!' => 'none',
198 ]
199 ]
200 );
201 $repeater->add_control(
202 'autor_display',
203 [
204 'label' => __('Display Type', 'uicore-elements'),
205 'type' => Controls_Manager::SELECT,
206 'default' => 'name',
207 'options' => [
208 'name' => __('Name', 'uicore-elements'),
209 'full' => __('Avatar & Name', 'uicore-elements'),
210 'avatar' => __('Avatar', 'uicore-elements'),
211 ],
212 'condition' => [
213 'type' => 'author',
214 ],
215 ]
216 );
217 $repeater->add_control(
218 'icon',
219 [
220 'label' => __('Icon', 'uicore-elements'),
221 'type' => Controls_Manager::ICONS,
222 'condition' => [
223 'autor_display' => 'name',
224 'type!' => 'none',
225 ],
226 ]
227 );
228 return $repeater->get_controls();
229 }
230
231 function get_meta_style_controls($position = 'tb-meta')
232 {
233 $this->add_group_control(
234 Group_Control_Typography::get_type(),
235 [
236 'name' => $position . '_meta_typography',
237 'selector' => '{{WRAPPER}} .ui-e-' . $position,
238 'separator' => 'before',
239 ]
240 );
241 $this->add_control(
242 $position . '_meta_color',
243 [
244 'label' => __('Text Color', 'uicore-elements'),
245 'type' => Controls_Manager::COLOR,
246 'selectors' => [
247 '{{WRAPPER}} .ui-e-' . $position => 'color: {{VALUE}}',
248 '{{WRAPPER}} .ui-e-' . $position . ' svg' => 'fill: {{VALUE}}',
249 ],
250 ]
251 );
252 $this->add_control(
253 $position . '_link_color',
254 [
255 'label' => __('Link Color', 'uicore-elements'),
256 'type' => Controls_Manager::COLOR,
257 'selectors' => [
258 '{{WRAPPER}} .ui-e-' . $position . ' .ui-e-meta-item a' => 'color: {{VALUE}}',
259 '{{WRAPPER}} .ui-e-' . $position . ' .ui-e-meta-item svg' => 'fill: {{VALUE}}',
260 ],
261 ]
262 );
263 $this->add_control(
264 $position . '_linkh_color',
265 [
266 'label' => __('Link Hover Color', 'uicore-elements'),
267 'type' => Controls_Manager::COLOR,
268 'selectors' => [
269 '{{WRAPPER}} .ui-e-' . $position . ' .ui-e-meta-item a:hover' => 'color: {{VALUE}}',
270 ],
271 ]
272 );
273 $this->add_control(
274 $position . '_meta_background',
275 [
276 'label' => __('Background Color', 'uicore-elements'),
277 'type' => Controls_Manager::COLOR,
278 'selectors' => [
279 '{{WRAPPER}} .ui-e-' . $position . ' .ui-e-meta-item' => 'background-color: {{VALUE}}',
280
281 ],
282 ]
283 );
284 $this->add_control(
285 $position . '_meta_radius',
286 [
287 'label' => esc_html__('Border Radius', 'uicore-elements'),
288 'type' => Controls_Manager::DIMENSIONS,
289 'selectors' => [
290 '{{WRAPPER}} .ui-e-' . $position . ' .ui-e-meta-item' => 'border-radius: {{TOP}}px {{RIGHT}}px {{BOTTOM}}px {{LEFT}}px;'
291 ],
292 ]
293 );
294 $this->add_group_control(
295 \Elementor\Group_Control_Box_Shadow::get_type(),
296 [
297 'name' => $position . '_meta_shadow',
298 'label' => esc_html__('Box Shadow', 'uicore-elements'),
299 'selector' => '{{WRAPPER}} .ui-e-' . $position . ' .ui-e-meta-item',
300 ]
301 );
302 $this->add_responsive_control(
303 $position . '_meta_padding',
304 [
305 'label' => esc_html__('Padding', 'uicore-elements'),
306 'type' => Controls_Manager::DIMENSIONS,
307 'size_units' => ['px', 'em', '%'],
308 'selectors' => [
309 '{{WRAPPER}} .ui-e-' . $position . ' .ui-e-meta-item' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};'
310 ]
311 ]
312 );
313 if ($position === 'top') {
314 $this->add_responsive_control(
315 $position . '_meta_margin',
316 [
317 'label' => esc_html__('Margin', 'uicore-elements'),
318 'type' => Controls_Manager::DIMENSIONS,
319 'size_units' => ['px', 'em', '%'],
320 'selectors' => [
321 '{{WRAPPER}} .ui-e-' . $position => 'margin: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};'
322 ]
323 ]
324 );
325 } else {
326 $this->add_control(
327 $position . '_meta_margin',
328 [
329 'label' => __('Meta Top Space', 'uicore-elements'),
330 'type' => Controls_Manager::SLIDER,
331 'size_units' => ['em'],
332 'separator' => 'after',
333 'range' => [
334 'px' => [
335 'min' => 0,
336 'max' => 3,
337 'step' => 0.1,
338 ],
339 ],
340 'default' => [
341 'unit' => 'em',
342 'size' => 1.2,
343 ],
344 'selectors' => [
345 '{{WRAPPER}} .ui-e-' . $position => 'margin-top: {{SIZE}}em;',
346 ],
347 ]
348 );
349 }
350
351 $this->add_responsive_control(
352 $position . '_meta_gap',
353 [
354 'label' => __('Items Gap', 'uicore-elements'),
355 'type' => Controls_Manager::SLIDER,
356 'size_units' => ['px'],
357 'range' => [
358 'px' => [
359 'min' => 0,
360 'max' => 30,
361 ],
362 ],
363 'default' => [
364 'unit' => 'px',
365 'size' => 8,
366 ],
367 'selectors' => [
368 '{{WRAPPER}} .ui-e-' . $position . ' ' => 'gap: {{SIZE}}px;',
369 ],
370 ]
371 );
372 $this->add_control(
373 $position . '_meta_separator',
374 [
375 'label' => __('Separator', 'uicore-elements'),
376 'type' => Controls_Manager::TEXT,
377 ]
378 );
379 if ($position === 'top') {
380 $this->add_control(
381 $position . '_meta_placement',
382 [
383 'label' => __('Items placement', 'uicore-elements'),
384 'type' => Controls_Manager::SELECT,
385 'default' => '',
386 'options' => [
387 'start left' => __('Top Left', 'uicore-elements'),
388 'start right' => __('Top Right', 'uicore-elements'),
389 'end left' => __('Bottom Left', 'uicore-elements'),
390 'end right' => __('Bottom Right', 'uicore-elements'),
391 ],
392 'selectors' => [
393 '{{WRAPPER}} .ui-e-post-top-meta' => 'place-content: {{VALUE}};',
394 ],
395 ]
396 );
397 }
398 }
399
400 function get_meta_the_author($mode)
401 {
402 global $post;
403 $author_id = $post->post_author;
404 $author_name = get_the_author_meta('display_name', $author_id);
405
406 // name, full, avatar
407 if ($mode === 'avatar') {
408 $display = '<img class="ui-e-meta-avatar" src="' . esc_url(get_avatar_url($author_id, array('size' => 100))) . '" />';
409 } elseif ($mode === 'full') {
410 $display = '<img class="ui-e-meta-avatar" src="' . esc_url(get_avatar_url($author_id, array('size' => 100))) . '" /> ' . esc_html($author_name);
411 } else {
412 $display = esc_html($author_name);
413 }
414 $link = sprintf(
415 /* translators: 1: Author archive url, 2: Author meta title, 3: Author display name */
416 '<a href="%1$s" title="%2$s" rel="author">%3$s</a>',
417 esc_url(get_author_posts_url($author_id)),
418 /* translators: %s: Author's display name. */
419 esc_attr(sprintf(__('View %s&#8217;s posts', 'uicore-elements'), esc_html($author_name))),
420 $display
421 );
422 return $link;
423 }
424
425 function get_woo_meta($type, $meta = null)
426 {
427 global $product;
428
429 if (empty($product)) {
430 return 'No product found.';
431 }
432
433 switch ($type) {
434 case 'product price':
435 return $product->get_price_html();
436 case 'product rating':
437 return wc_get_rating_html($product->get_average_rating());
438 case 'product category':
439 return get_the_term_list($product->get_id(), 'product_cat', '', ', ', '');
440 case 'product tag':
441 return get_the_term_list($product->get_id(), 'product_tag', '', ', ', '');
442 case 'product stock':
443 return $this->get_product_stock_for_widget_meta($product, $meta);
444 case 'product attribute':
445 return $this->print_product_attributes($product);
446 case 'product sale':
447 return $product->is_on_sale() ? esc_html__('Sale!', 'uicore-elements') : '';
448 default:
449 return 'Invalid meta type.';
450 }
451 }
452
453 /**
454 * Get the product stock quantity for Advanced Product widget meta. If the product is variable, return the highest stock quantity among its variations.
455 * TODO: this function should be part of the product TRAIT, but product trait uses this meta trait, so we'd have a circular dependency.
456 *
457 * @param WC_Product $product The WooCommerce product object.
458 * @param array $meta The meta settings array.
459 */
460 function get_product_stock_for_widget_meta($product, $meta)
461 {
462
463 // In/out of stock return
464 if (isset($meta['stock_type']) && $meta['stock_type'] === 'in') {
465
466 $in_stock = false;
467
468 if ($product->is_type('variable')) {
469 foreach ($product->get_children() as $child_id) {
470 $variation = wc_get_product($child_id);
471 if ($variation && $variation->is_in_stock()) {
472 $in_stock = true;
473 break;
474 }
475 }
476 } else {
477 $in_stock = $product->is_in_stock();
478 }
479
480 return $in_stock
481 ? '<span class="in-stock">' . esc_html($meta['in_stock_text'] ?? __('In stock', 'uicore-elements')) . '</span>'
482 : '<span class="out-of-stock">' . esc_html($meta['out_of_stock_text'] ?? __('Out of stock', 'uicore-elements')) . '</span>';
483 }
484
485 // Quantity return
486 if ($product->is_type('variable')) {
487
488 $qty = 0;
489
490 // Find the maximum stock quantity among variations
491 foreach ($product->get_available_variations() as $variation) {
492 $obj = wc_get_product($variation['variation_id']);
493
494 if ($obj->managing_stock()) {
495 $stock = $obj->get_stock_quantity();
496 if ($stock > $qty) {
497 $qty = $stock;
498 }
499 }
500 }
501
502 return $qty > 0
503 ? '<span class="in-stock">' . esc_html($qty) . '</span>'
504 : '<span class="out-of-stock">' . esc_html($meta['out_of_stock_text'] ?? __('Out of stock', 'uicore-elements')) . '</span>';
505 }
506
507 if (!$product->managing_stock()) {
508 return '<span class="in-stock">' . esc_html($meta['in_stock_text'] ?? __('In stock', 'uicore-elements')) . '</span>';
509 }
510
511 return $product->get_stock_quantity() !== null
512 ? $product->get_stock_quantity()
513 : '';
514 }
515
516 /**
517 * Gets and prints a list of product attributes. We use a custom function for it because
518 * `wc_display_product_attributes` uses a table layout wich is not suitable for our widget.
519 *
520 * @param WC_Product $product The WooCommerce product object.
521 * @return string|false The HTML string of product attributes or false if none are visible.
522 * @since 1.3.13
523 */
524 function print_product_attributes($product)
525 {
526
527 $attributes = $product->get_attributes();
528
529 if (empty($attributes)) {
530 return false;
531 }
532
533 $default = true; // Flag to check if we're actually displaying any attribute
534 $content = '<ul class="ui-e-product-attributes">';
535
536 foreach ($attributes as $attribute) {
537 if (! $attribute->get_visible()) {
538 continue;
539 }
540
541 // We only want to display default attributes here that are not using framework swatches
542 $swatch = $this->get_swatch_type($attribute->get_name());
543 if ($swatch && in_array($swatch, ['color', 'label', 'button', 'image'])) {
544 continue;
545 }
546
547 $default = false; // Safe to output html
548 $name = wc_attribute_label($attribute->get_name());
549 $value = '';
550
551 if ($attribute->is_taxonomy()) {
552 $taxonomy = $attribute->get_name();
553 $terms = wc_get_product_terms(
554 $product->get_id(),
555 $taxonomy,
556 ['fields' => 'all']
557 );
558
559 $term_links = [];
560 $taxonomy_obj = get_taxonomy($taxonomy);
561 $has_archive = $taxonomy_obj && $taxonomy_obj->public && $taxonomy_obj->rewrite && !empty($taxonomy_obj->rewrite['slug']);
562
563 foreach ($terms as $term) {
564 if ($has_archive && get_term_link($term)) {
565 $term_links[] = '<a href="' . esc_url(get_term_link($term)) . '">' . esc_html($term->name) . '</a>';
566 } else {
567 $term_links[] = esc_html($term->name);
568 }
569 }
570 $value = implode(', ', $term_links);
571 } else {
572 $value = implode(', ', $attribute->get_options());
573 }
574
575 if (empty($value)) {
576 continue;
577 }
578
579 $content .= '<li>';
580 $content .= '<strong>' . esc_html($name) . ':</strong> ';
581 $content .= $value;
582 $content .= '</li>';
583 }
584
585 if ($default) {
586 return false;
587 }
588
589 $content .= '</ul>';
590 return $content;
591 }
592
593 /**
594 * Get the attribute swatch type from Uicore Framework plugin.
595 *
596 * @return string The swatch type.
597 * @since 1.3.15
598 */
599 function get_swatch_type($attribute_slug)
600 {
601 if (!class_exists('Uicore\WooCommerce\Swatches') && !function_exists('get_attribute_swatch_type')) {
602 return false;
603 }
604
605 return \Uicore\WooCommerce\Swatches::get_attribute_swatch_type($attribute_slug);
606 }
607
608 function display_meta($meta, $product_id = null)
609 {
610
611 if ($meta['type'] === 'none')
612 return;
613
614 $content = '';
615 $wrapper = '<div class="ui-e-meta-item">';
616 $type = $meta['type'];
617 $prefix = $meta['before'] ? '<span>' . esc_html($meta['before']) . '</span>' : '';
618 $suffix = $meta['after'] ? '<span class="ui-e-meta-after">' . esc_html($meta['after']) . '</span>' : '';
619
620 ob_start();
621 \Elementor\Icons_Manager::render_icon($meta['icon'], ['aria-hidden' => 'true', 'class' => 'ui-e-meta-icon'], 'span');
622 $icon = ob_get_clean();
623
624 // Build content
625 switch ($type) {
626 case 'author':
627 $content .= $this->get_meta_the_author($meta['autor_display']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
628 break;
629 case 'date':
630 $content .= Helper::format_date(get_the_date('U'), $meta['date_format'], $meta['custom_format']); // 'U' param returns the date in timestamp, necessary for format_date()
631 break;
632 case 'updated date':
633 $content .= Helper::format_date(get_the_modified_date('U'), $meta['date_format'], $meta['custom_format']); // 'U' param returns the date in timestamp, necessary for format_date()
634 break;
635 case 'category':
636 $content .= Helper::get_taxonomy('category');
637 break;
638 case 'tag':
639 $content .= Helper::get_taxonomy('post_tag');
640 break;
641 case 'portfolio category':
642 $content .= Helper::get_taxonomy('portfolio_category');
643 break;
644 case 'comment':
645 $content .= esc_html(get_comments_number());
646 break;
647 case 'custom meta':
648 $content .= Helper::get_custom_meta($meta['type_custom'], $product_id);
649 break;
650 case 'custom taxonomy':
651 $content .= Helper::get_taxonomy($meta['type_custom']);
652 break;
653 case 'custom html':
654 $content .= wp_kses_post($meta['html_custom']);
655 break;
656 case 'reading time':
657 $content .= esc_html(Helper::get_reading_time());
658 break;
659 case 'excerpt':
660 $content .= esc_html(get_the_excerpt());
661 break;
662 default:
663 if (strpos($type, 'product') === 0) {
664 $data = $this->get_woo_meta($type, $meta);
665 if ($data === false) return;
666 $content .= wp_kses_post($data);
667 } else {
668 echo \esc_html($type);
669 }
670 break;
671 }
672
673 // Only output if there's content
674 if (!empty($content)) {
675 echo $wrapper . Helper::esc_svg($icon) . $prefix . $content . $suffix . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
676 }
677 }
678 }
679