PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.82
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.82
51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 51.1.46 51.1.47 51.1.49 All 37 releases
king-addons / includes / helpers / Loop_Builder / Renderer.php

Renderer.php in King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder 51.1.82, at includes/helpers/Loop_Builder/Renderer.php

229 lines 6.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Renders a Loop Builder item template once per post.
4 *
5 * @package King_Addons
6 */
7
8 namespace King_Addons\Loop_Builder;
9
10 use Elementor\Plugin as Elementor_Plugin;
11
12 if (!defined('ABSPATH')) {
13 exit;
14 }
15
16 /**
17 * Puts one post into context and renders the chosen item template for it.
18 */
19 class Renderer
20 {
21 /**
22 * Templates whose CSS has already been printed on this request.
23 *
24 * @var array<int,bool>
25 */
26 private static $css_printed = [];
27
28 /**
29 * How many renders deep the element cache has been suspended.
30 *
31 * A card can hold a grid of a second template, so this has to be a depth
32 * count: a plain flag would let the inner render switch the cache back on
33 * while the outer one is still going.
34 *
35 * @var int
36 */
37 private static $cache_depth = 0;
38
39 /**
40 * Templates currently being rendered, to stop a loop inside itself.
41 *
42 * @var array<int,bool>
43 */
44 private static $rendering = [];
45
46 /**
47 * All published loop item templates, as id => title.
48 *
49 * @return array<int,string>
50 */
51 public static function get_templates(): array
52 {
53 $posts = get_posts([
54 'post_type' => 'elementor_library',
55 'post_status' => 'publish',
56 'posts_per_page' => 100,
57 'orderby' => 'title',
58 'order' => 'ASC',
59 'meta_key' => '_elementor_template_type',
60 'meta_value' => Document::get_type(),
61 'suppress_filters' => false,
62 ]);
63
64 $templates = [];
65 foreach ($posts as $post) {
66 $templates[(int) $post->ID] = $post->post_title !== ''
67 ? $post->post_title
68 /* translators: %d: template id. */
69 : sprintf(esc_html__('Loop item #%d', 'king-addons'), (int) $post->ID);
70 }
71
72 return $templates;
73 }
74
75 /**
76 * Whether the given id is a usable loop item template.
77 *
78 * @param int $template_id Template post id.
79 *
80 * @return bool
81 */
82 public static function is_template(int $template_id): bool
83 {
84 if ($template_id < 1) {
85 return false;
86 }
87
88 $post = get_post($template_id);
89 if (!$post || 'elementor_library' !== $post->post_type) {
90 return false;
91 }
92
93 return Document::get_type() === get_post_meta($template_id, '_elementor_template_type', true);
94 }
95
96 /**
97 * Render one template for one post.
98 *
99 * @param int $template_id Loop item template id.
100 * @param int $post_id Post to render it for.
101 *
102 * @return string
103 */
104 public static function render(int $template_id, int $post_id): string
105 {
106 if (!class_exists('Elementor\\Plugin') || !self::is_template($template_id)) {
107 return '';
108 }
109
110 $post = get_post($post_id);
111 if (!$post) {
112 return '';
113 }
114
115 // A card holding a Loop Grid that points back at its own template would
116 // recurse until the request runs out of memory.
117 if (!empty(self::$rendering[$template_id])) {
118 return '';
119 }
120
121 self::$rendering[$template_id] = true;
122
123 self::suspend_element_cache();
124
125 // Elementor's frontend uses the global post; WooCommerce widgets read
126 // the global $product. Both are restored below.
127 global $wp_query;
128
129 $previous_post = $GLOBALS['post'] ?? null;
130 $previous_product = $GLOBALS['product'] ?? null;
131 $previous_in_the_loop = isset($wp_query) ? $wp_query->in_the_loop : null;
132
133 $GLOBALS['post'] = $post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
134 setup_postdata($post);
135
136 if (isset($wp_query)) {
137 // Widgets and themes gate "current post" behaviour on this flag.
138 $wp_query->in_the_loop = true;
139 }
140
141 if (function_exists('wc_get_product') && 'product' === $post->post_type) {
142 $product = wc_get_product($post_id);
143 $GLOBALS['product'] = $product instanceof \WC_Product ? $product : null; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
144 }
145
146 // The template's CSS is the same for every card, so it is printed with
147 // the first one only.
148 $with_css = empty(self::$css_printed[$template_id]);
149 self::$css_printed[$template_id] = true;
150
151 $html = Elementor_Plugin::$instance->frontend->get_builder_content_for_display($template_id, $with_css);
152
153 if (isset($wp_query) && null !== $previous_in_the_loop) {
154 $wp_query->in_the_loop = $previous_in_the_loop;
155 }
156
157 wp_reset_postdata();
158 $GLOBALS['post'] = $previous_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
159 $GLOBALS['product'] = $previous_product; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
160
161 self::resume_element_cache();
162
163 unset(self::$rendering[$template_id]);
164
165 return (string) $html;
166 }
167
168 /**
169 * Turn Elementor's element cache off for the duration of a loop.
170 *
171 * Without this the first card's HTML is stored against the template and
172 * replayed for every following post, so a ten-post grid shows the same post
173 * ten times.
174 *
175 * @return void
176 */
177 private static function suspend_element_cache(): void
178 {
179 self::$cache_depth++;
180
181 if (self::$cache_depth > 1) {
182 return;
183 }
184
185 add_filter('pre_option_elementor_element_cache_ttl', [self::class, 'return_disable']);
186 }
187
188 /**
189 * Restore the element cache.
190 *
191 * @return void
192 */
193 private static function resume_element_cache(): void
194 {
195 if (self::$cache_depth < 1) {
196 return;
197 }
198
199 self::$cache_depth--;
200
201 if (0 === self::$cache_depth) {
202 remove_filter('pre_option_elementor_element_cache_ttl', [self::class, 'return_disable']);
203 }
204 }
205
206 /**
207 * Filter callback: report the element cache as switched off.
208 *
209 * @return string
210 */
211 public static function return_disable(): string
212 {
213 return 'disable';
214 }
215
216 /**
217 * Forget which templates printed their CSS.
218 *
219 * Used by the AJAX handlers, where each request is a fresh page as far as
220 * the browser is concerned and the styles have to come along again.
221 *
222 * @return void
223 */
224 public static function reset_css_state(): void
225 {
226 self::$css_printed = [];
227 }
228 }
229