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 / extensions / Loop_Builder / Loop_Builder.php

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

353 lines 10.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Loop Builder.
4 *
5 * Lets a card be designed once in Elementor and repeated for every post a query
6 * returns, which is what the Loop Grid widget does with it.
7 *
8 * @package King_Addons
9 */
10
11 namespace King_Addons;
12
13 use Elementor\Plugin as Elementor_Plugin;
14 use King_Addons\Loop_Builder\Document as Loop_Document;
15 use King_Addons\Loop_Builder\Renderer as Loop_Renderer;
16
17 if (!defined('ABSPATH')) {
18 exit;
19 }
20
21 /**
22 * Registers the loop item document type and everything around it.
23 */
24 class Loop_Builder
25 {
26 /**
27 * Admin page slug.
28 */
29 public const PAGE = 'king-addons-loop-builder';
30
31 /**
32 * Constructor.
33 */
34 public function __construct()
35 {
36 if (!did_action('elementor/loaded')) {
37 return;
38 }
39
40 require_once KING_ADDONS_PATH . 'includes/helpers/Loop_Builder/Document.php';
41 require_once KING_ADDONS_PATH . 'includes/helpers/Loop_Builder/Renderer.php';
42
43 add_action('elementor/documents/register', [$this, 'register_document_type']);
44
45 // The grid's load-more request never builds the page, so its handler
46 // cannot live in the widget file: admin-ajax.php does not load Elementor
47 // widgets.
48 add_action('wp_ajax_ka_loop_grid', [$this, 'ajax_loop_grid']);
49 add_action('wp_ajax_nopriv_ka_loop_grid', [$this, 'ajax_loop_grid']);
50
51 add_action('admin_menu', [$this, 'register_admin_page'], 11);
52 add_action('admin_post_king_addons_create_loop_template', [$this, 'handle_create_template']);
53
54 // A loop item is edited on its own, with no page around it, so the
55 // editor needs to know which post to preview it against.
56 add_action('elementor/documents/register_controls', [$this, 'register_document_controls']);
57 // Two hooks, because the editor renders a template two different ways:
58 // the whole document on the first preview load, and one widget at a
59 // time over AJAX afterwards.
60 add_action('elementor/frontend/before_get_builder_content', [$this, 'maybe_setup_preview_post']);
61 add_action('elementor/widget/before_render_content', [$this, 'maybe_setup_preview_post']);
62 }
63
64 /**
65 * Register the loop item document type with Elementor.
66 *
67 * @param \Elementor\Core\Documents_Manager $documents_manager Manager.
68 *
69 * @return void
70 */
71 public function register_document_type($documents_manager): void
72 {
73 if (!class_exists(Loop_Document::class)) {
74 return;
75 }
76
77 $documents_manager->register_document_type(Loop_Document::get_type(), Loop_Document::class);
78 }
79
80 /**
81 * Post types a loop template can be built for.
82 *
83 * @return array<string,string>
84 */
85 public static function get_source_post_types(): array
86 {
87 $types = [];
88
89 $skip = [
90 'attachment',
91 'elementor_library',
92 'e-floating-buttons',
93 'king-addons-el-hf',
94 'king_addons_ext_pb',
95 'king-addons-fb-sub',
96 ];
97
98 foreach (get_post_types(['public' => true], 'objects') as $slug => $type) {
99 if (in_array($slug, $skip, true)) {
100 continue;
101 }
102
103 $types[$slug] = $type->labels->name ?? $slug;
104 }
105
106 return $types;
107 }
108
109 /**
110 * Add the Loop Builder page under the King Addons menu.
111 *
112 * @return void
113 */
114 public function register_admin_page(): void
115 {
116 add_submenu_page(
117 'king-addons',
118 esc_html__('Loop Builder', 'king-addons'),
119 esc_html__('Loop Builder', 'king-addons'),
120 'edit_pages',
121 self::PAGE,
122 [$this, 'render_admin_page']
123 );
124 }
125
126 /**
127 * The Loop Builder screen: existing templates plus a form to add one.
128 *
129 * @return void
130 */
131 public function render_admin_page(): void
132 {
133 if (!current_user_can('edit_pages')) {
134 wp_die(esc_html__('You are not allowed to manage loop templates.', 'king-addons'));
135 }
136
137 require KING_ADDONS_PATH . 'includes/extensions/Loop_Builder/admin-page.php';
138 }
139
140 /**
141 * Create a loop item template and send the user straight to the editor.
142 *
143 * @return void
144 */
145 public function handle_create_template(): void
146 {
147 if (!current_user_can('edit_pages')) {
148 wp_die(esc_html__('You are not allowed to create loop templates.', 'king-addons'));
149 }
150
151 check_admin_referer('king_addons_create_loop_template');
152
153 $title = isset($_POST['loop_title']) ? sanitize_text_field(wp_unslash($_POST['loop_title'])) : '';
154 if ('' === $title) {
155 $title = esc_html__('Loop item', 'king-addons');
156 }
157
158 $source = isset($_POST['loop_source']) ? sanitize_key(wp_unslash($_POST['loop_source'])) : 'post';
159 $sources = self::get_source_post_types();
160 if (!isset($sources[$source])) {
161 $source = 'post';
162 }
163
164 $template_id = wp_insert_post([
165 'post_title' => $title,
166 'post_type' => 'elementor_library',
167 'post_status' => 'publish',
168 ]);
169
170 if (is_wp_error($template_id) || !$template_id) {
171 wp_safe_redirect(add_query_arg(['page' => self::PAGE, 'ka_error' => 'create'], admin_url('admin.php')));
172 exit;
173 }
174
175 update_post_meta($template_id, '_elementor_template_type', Loop_Document::get_type());
176 update_post_meta($template_id, '_elementor_edit_mode', 'builder');
177 update_post_meta($template_id, Loop_Document::META_SOURCE, $source);
178
179 wp_safe_redirect(add_query_arg(
180 ['post' => $template_id, 'action' => 'elementor'],
181 admin_url('post.php')
182 ));
183 exit;
184 }
185
186 /**
187 * Add the preview-post picker to the loop item document settings.
188 *
189 * @param \Elementor\Core\Base\Document $document Document being edited.
190 *
191 * @return void
192 */
193 public function register_document_controls($document): void
194 {
195 if (!$document instanceof \Elementor\Core\Base\Document) {
196 return;
197 }
198
199 if (Loop_Document::get_type() !== $document->get_name()) {
200 return;
201 }
202
203 $source = (string) get_post_meta($document->get_main_id(), Loop_Document::META_SOURCE, true);
204 if ('' === $source) {
205 $source = 'post';
206 }
207
208 $options = [0 => esc_html__('- Latest -', 'king-addons')];
209 foreach (get_posts([
210 'post_type' => $source,
211 'posts_per_page' => 25,
212 'post_status' => 'publish',
213 ]) as $post) {
214 $options[(int) $post->ID] = $post->post_title !== '' ? $post->post_title : '#' . $post->ID;
215 }
216
217 $document->start_controls_section(
218 'ka_loop_preview_section',
219 [
220 'label' => esc_html__('Loop Preview', 'king-addons'),
221 'tab' => \Elementor\Controls_Manager::TAB_SETTINGS,
222 ]
223 );
224
225 $document->add_control(
226 'ka_loop_preview_id',
227 [
228 'label' => esc_html__('Preview with', 'king-addons'),
229 'type' => \Elementor\Controls_Manager::SELECT,
230 'options' => $options,
231 'default' => 0,
232 'description' => esc_html__('Which entry the editor shows while you design the card.', 'king-addons'),
233 ]
234 );
235
236 $document->end_controls_section();
237 }
238
239 /**
240 * In the editor, put a real post behind the loop item being designed.
241 *
242 * Without this the card is built against the template post itself, so every
243 * dynamic value reads "Loop item" instead of showing real content.
244 *
245 * @return void
246 */
247 public function maybe_setup_preview_post(): void
248 {
249 static $done = false;
250
251 if ($done) {
252 return;
253 }
254
255 $template_id = 0;
256
257 // The id comes from the preview flag rather than get_the_ID(): by the
258 // time an element renders the global post has been through the theme's
259 // loop, and comparing against it made this check miss.
260 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Elementor's own preview flag.
261 if (isset($_GET['elementor-preview'])) {
262 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
263 $template_id = absint(wp_unslash($_GET['elementor-preview']));
264 } elseif (Elementor_Plugin::$instance->editor->is_edit_mode()) {
265 // A single widget re-rendered over AJAX: Elementor has already
266 // switched to the document being edited.
267 $document = Elementor_Plugin::$instance->documents->get_current();
268 if ($document) {
269 $template_id = (int) $document->get_main_id();
270 }
271 }
272
273 if (!$template_id || Loop_Document::get_type() !== get_post_meta($template_id, '_elementor_template_type', true)) {
274 return;
275 }
276
277 if (!current_user_can('edit_post', $template_id)) {
278 return;
279 }
280
281 $done = true;
282
283 $preview_id = (int) $this->get_preview_post_id($template_id);
284 if (!$preview_id) {
285 return;
286 }
287
288 $post = get_post($preview_id);
289 if (!$post) {
290 return;
291 }
292
293 $GLOBALS['post'] = $post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
294 setup_postdata($post);
295
296 if (function_exists('wc_get_product') && 'product' === $post->post_type) {
297 $product = wc_get_product($preview_id);
298 $GLOBALS['product'] = $product instanceof \WC_Product ? $product : null; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
299 }
300 }
301
302 /**
303 * The post the editor should preview a template with.
304 *
305 * @param int $template_id Template id.
306 *
307 * @return int
308 */
309 private function get_preview_post_id(int $template_id): int
310 {
311 $document = Elementor_Plugin::$instance->documents->get($template_id);
312 $chosen = 0;
313
314 if ($document) {
315 $chosen = (int) $document->get_settings('ka_loop_preview_id');
316 }
317
318 if ($chosen > 0 && get_post($chosen)) {
319 return $chosen;
320 }
321
322 $source = (string) get_post_meta($template_id, Loop_Document::META_SOURCE, true);
323 if ('' === $source) {
324 $source = 'post';
325 }
326
327 $latest = get_posts([
328 'post_type' => $source,
329 'posts_per_page' => 1,
330 'post_status' => 'publish',
331 'fields' => 'ids',
332 ]);
333
334 return $latest ? (int) $latest[0] : 0;
335 }
336
337 /**
338 * Load-more / paging for the Loop Grid widget.
339 *
340 * @return void
341 */
342 public function ajax_loop_grid(): void
343 {
344 require_once KING_ADDONS_PATH . 'includes/widgets/Loop_Grid/Loop_Grid.php';
345
346 if (!class_exists('King_Addons\\Loop_Grid')) {
347 wp_send_json_error(['message' => 'unavailable'], 500);
348 }
349
350 Loop_Grid::handle_ajax();
351 }
352 }
353