PluginProbe
Ultimate Store Kit / trunk
Ultimate Store Kit vtrunk
3.0.8 3.0.9 3.1.0 3.1.2 3.1.3 3.0.7 3.0.5 3.0.4 3.0.3 3.0.2 trunk 1.5.0 1.5.1 1.5.2 1.6.1 1.6.2 1.6.3 1.6.4 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 All 92 releases
ultimate-store-kit / includes / builder / builder-integration.php

builder-integration.php in Ultimate Store Kit trunk, at includes/builder/builder-integration.php

519 lines 15.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace UltimateStoreKit\Builder;
4
5 if (! defined('ABSPATH')) {
6 exit; // Exit if accessed directly
7 }
8
9 // phpcs:disable WordPress.NamingConventions.PrefixAllGlobals -- BDTUSK_ / ultimate_store_kit_ / ultimate-store-kit- are this plugin's established public prefixes.
10
11 if (! defined('WPINC')) {
12 die;
13 }
14
15 use Elementor\Controls_Manager;
16 use Elementor\Plugin;
17 use UltimateStoreKit\Includes\Builder\Builder_Template_Helper;
18 use UltimateStoreKit\Base\Singleton;
19 use UltimateStoreKit\Includes\Builder\Meta;
20 use UltimateStoreKit\Includes\Controls\SelectInput\Dynamic_Select;
21
22
23 class Builder_Integration {
24
25 use Singleton;
26
27 private $current_template = null;
28 public $current_template_id = null;
29
30 function __construct() {
31 add_filter('template_include', [$this, 'set_builder_template'], 9999);
32 add_action('elementor/editor/init', [$this, 'set_sample_post'], 999);
33
34 add_action('print_default_editor_scripts', array($this, 'my_custom_fonts'));
35 add_filter("elementor/document/urls/wp_preview", [$this, 'change_preview_editor_url'], 999, 2);
36
37 add_action('elementor/documents/register_controls', [$this, 'register_document_controls']);
38
39 // The template preview used to accept the literal string "verified" in place
40 // of a nonce whenever $_SERVER['HTTP_HOST'] contained one of a list of demo
41 // hostnames. Because Host is attacker-controlled and the check was a substring
42 // match, any unauthenticated visitor could render the Elementor content of an
43 // arbitrary post id -- including drafts and private posts -- on any site whose
44 // Host reached PHP as localhost/127.0.0.1 (common behind a reverse proxy) or
45 // whose domain merely contained one of those strings. Preview is now gated on
46 // the real per-post nonce plus an edit_post capability check; see get_template_id().
47 }
48
49 public function change_preview_editor_url($url, $document) {
50 $post_id = $document->get_main_id();
51 $template_type = get_post_meta($post_id, Meta::TEMPLATE_TYPE, true);
52
53 if (empty($template_type) || get_post_type($post_id) !== Meta::POST_TYPE) {
54 return $url;
55 }
56
57 $template_data = explode(Builder_Template_Helper::separator(), $template_type);
58 if (count($template_data) < 2) {
59 return $url;
60 }
61
62 $post_type = $template_data[0];
63 $template_slug = $template_data[1];
64
65 // Get template URL based on template type
66 $template_url = '';
67
68 // Check for product category first
69 if ($template_slug === 'product-category') {
70 $product_cats = get_terms([
71 'taxonomy' => 'product_cat',
72 'hide_empty' => true,
73 'number' => 1,
74 ]);
75
76 if (!empty($product_cats) && !is_wp_error($product_cats)) {
77 $template_url = get_term_link($product_cats[0]);
78 }
79 }
80
81 // Check for product tag
82 elseif ($template_slug === 'product-tag') {
83 $product_tags = get_terms([
84 'taxonomy' => 'product_tag',
85 'hide_empty' => true,
86 'number' => 1,
87 ]);
88
89 if (!empty($product_tags) && !is_wp_error($product_tags)) {
90 $template_url = get_term_link($product_tags[0]);
91 }
92 } elseif ($template_slug === 'shop' || $template_slug === 'archive') {
93 $template_url = get_permalink(wc_get_page_id('shop'));
94 } elseif ($template_slug === 'single' && $post_type === 'product') {
95 $page_settings_manager = \Elementor\Core\Settings\Manager::get_settings_managers('page');
96 $page_settings_model = $page_settings_manager->get_model($post_id);
97 $sample_product = $page_settings_model->get_settings('usk_builder_sample_post_id');
98
99 $template_url = !empty($sample_product) ?
100 get_permalink($sample_product) :
101 $this->get_default_product_url();
102 } elseif ($template_slug === 'cart') {
103 $template_url = wc_get_cart_url();
104 } elseif ($template_slug === 'checkout') {
105 $template_url = wc_get_checkout_url();
106 } elseif (
107 $template_slug === 'myaccount'
108 || $template_slug === 'login'
109 || strpos($template_slug, 'myaccount-') === 0
110 ) {
111 $template_url = get_permalink(wc_get_page_id('myaccount'));
112 } elseif ($template_slug === 'order-received') {
113 $orders = wc_get_orders(['limit' => 1]);
114 if (!empty($orders)) {
115 $order = $orders[0];
116 $order_id = $order->get_id();
117 $template_url = add_query_arg('order-received', $order_id, wc_get_checkout_url());
118 }
119 }
120
121 if (empty($template_url)) {
122 return $url;
123 }
124
125 $param = [
126 'ultimate_store_kit_template_id' => $post_id,
127 'ultimate_store_kit_preview_nonce' => wp_create_nonce('ultimate_store_kit_template_preview_' . $post_id),
128 'preview' => true
129 ];
130
131 // Add parameters two URL
132 $url = add_query_arg($param, $template_url);
133
134 return $url;
135 }
136
137 public function my_custom_fonts() {
138 if (is_admin() && Plugin::instance()->editor->is_edit_mode()) {
139 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only check on whether a builder template is open; only enqueues a style.
140 if (isset($_REQUEST['usk-template'])) {
141 wp_register_style('usk-template-builder-hide-preview-btn-inline', false); // phpcs:ignore
142 wp_enqueue_style('usk-template-builder-hide-preview-btn-inline');
143 wp_add_inline_style(
144 'usk-template-builder-hide-preview-btn-inline',
145 '#elementor-panel-footer-saver-preview {display:none!important}'
146 );
147 }
148 }
149 }
150 function set_sample_post() {
151 if (Builder_Template_Helper::isTemplateEditMode()) {
152 $object = \UltimateStoreKit\Includes\Builder\Builder_Post_Singleton::instance();
153 $object::set_sample_post();
154 }
155 }
156
157 function register_document_controls($document) {
158 if (
159 ! $document instanceof \Elementor\Core\DocumentTypes\PageBase
160 || ! $document::get_property('has_elements')
161 ) {
162 return;
163 }
164
165 if (Plugin::instance()->preview->is_preview_mode())
166 return;
167
168 if (! Builder_Template_Helper::isTemplateEditMode()) {
169 return;
170 }
171
172 global $post;
173
174 if (! isset($post->ID)) {
175 return;
176 }
177 $meta = get_post_meta($post->ID);
178
179 $templateMeta = ultimate_store_kit_optional($meta)[Meta::TEMPLATE_TYPE];
180 if (! isset($templateMeta[0])) {
181 return;
182 }
183 $postMeta = $templateMeta[0];
184 $postMeta = explode('|', $postMeta);
185 $postType = $postMeta[0];
186
187 if ($postMeta[1] != 'single') {
188 return;
189 }
190
191 $document->start_controls_section(
192 'usk_page_setting_preview',
193 [
194 'label' => esc_html__('Builder Settings', 'ultimate-store-kit'),
195 'tab' => Controls_Manager::TAB_SETTINGS,
196 ]
197 );
198
199 $document->add_control(
200 'usk_builder_sample_post_id',
201 [
202 'label' => __('Builder Post', 'ultimate-store-kit'),
203 'type' => Dynamic_Select::TYPE,
204 'multiple' => false,
205 'label_block' => true,
206 'query_args' => [
207 'post_type' => $postType
208 ],
209 ]
210 );
211
212 $document->add_control(
213 'usk_builder_sample_apply_preview',
214 [
215 'type' => Controls_Manager::BUTTON,
216 'label' => esc_html__('Apply & Preview', 'ultimate-store-kit'),
217 'label_block' => true,
218 'show_label' => false,
219 'text' => esc_html__('Apply & Preview', 'ultimate-store-kit'),
220 'separator' => 'none',
221 'event' => 'ultimateStoreKitBuilderSetting:applySinglePagePostOnPreview',
222 ]
223 );
224
225 $document->end_controls_section();
226 }
227
228 /**
229 * Rewrite default template
230 *
231 */
232 function set_builder_template($template) {
233 if (Builder_Template_Helper::isTemplateEditMode()) {
234 return $this->setBackendTemplate($template);
235 } else {
236 return $this->setFrontendTemplate($template);
237 }
238 }
239
240
241 protected function setBackendTemplate($template) {
242 return $template;
243 }
244
245
246 protected function setFrontendTemplate($template) {
247
248 if (get_post_type() == 'product') {
249 global $product;
250 $product = wc_get_product();
251 }
252
253 if (defined('ELEMENTOR_PATH')) {
254 $elementorTem = ELEMENTOR_PATH . "modules/page-templates/templates/";
255 $elementorTem = explode($elementorTem, $template);
256 if (count($elementorTem) == 2) {
257 return $template;
258 }
259 }
260
261
262 if (is_post_type_archive('product') || is_page(wc_get_page_id('shop')) || is_product_taxonomy()) {
263 $template_type = 'shop';
264
265 if (is_tax('product_cat')) {
266 $template_type = 'category';
267 } elseif (is_tax('product_tag')) {
268 $template_type = 'tag';
269 }
270
271 if ($custom_template = $this->get_template_id($template_type)) {
272 $this->current_template_id = $custom_template;
273 return $this->getTemplatePath('woocommerce/archive-product', $template);
274 }
275 }
276
277
278 if ( is_cart() ) {
279 $cart_template = $this->resolve_cart_template( $template );
280
281 if ( $cart_template ) {
282 return $cart_template;
283 }
284 }
285
286 if (is_order_received_page()) {
287 if ($custom_template = $this->get_template_id('order-received', 'product')) {
288 $this->current_template_id = $custom_template;
289 return $this->getTemplatePath('woocommerce/order-received', $template);
290 }
291 }
292
293 if (is_checkout()) {
294 if ($custom_template = $this->get_template_id('checkout', 'product')) {
295 $this->current_template_id = $custom_template;
296 return $this->getTemplatePath('woocommerce/checkout', $template);
297 }
298 }
299
300 if (is_single() && get_post_type() == 'product') {
301 if ($custom_template = $this->get_template_id('single', 'product')) {
302 $this->current_template_id = $custom_template;
303 return $this->getTemplatePath('woocommerce/single-product', $template);
304 }
305 }
306
307 if (is_account_page()) {
308 global $wp;
309 $query_vars = WC()->query->get_query_vars();
310
311 /**
312 * Add wishlist endpoint
313 */
314 $query_vars['wishlist'] = 'wishlist';
315
316 $endpoint_match = array_intersect_key($wp->query_vars, $query_vars);
317
318 // Logged-out visitors landing on /my-account/ (no endpoint) see the
319 // single Login & Register template, which is responsible for both
320 // authentication flows. WooCommerce's own form-login.php renders
321 // the login and registration forms on the same URL, so one
322 // template covers both intents.
323 if (! is_user_logged_in() && empty($endpoint_match)) {
324 if ($custom_template = $this->get_template_id('login', 'account')) {
325 $this->current_template_id = $custom_template;
326
327 if ($newTemplate = $this->getTemplatePath('woocommerce/login')) {
328 return $newTemplate;
329 }
330
331 return $this->getTemplatePath('woocommerce/my-account', $template);
332 }
333 }
334
335 if ($endpoint = $endpoint_match) {
336 $endpoint = array_key_first($endpoint);
337
338 if ($endpoint && $custom_template = $this->get_template_id($endpoint, 'account')) {
339 $this->current_template_id = $custom_template;
340
341 if ($newTemplate = $this->getTemplatePath("woocommerce/{$endpoint}")) {
342 return $newTemplate;
343 }
344
345 return $this->getTemplatePath("woocommerce/my-account", '');
346 }
347
348 if ($custom_template = $this->get_template_id('myaccount-orders', 'account')) {
349 $this->current_template_id = $custom_template;
350 return $this->getTemplatePath('woocommerce/my-account', $template);
351 }
352 } else {
353 if ($custom_template = $this->get_template_id('myaccount', 'account')) {
354 $this->current_template_id = $custom_template;
355 return $this->getTemplatePath('woocommerce/my-account', $template);
356 }
357 }
358 }
359
360
361 if (is_single()) {
362 if ($custom_template = $this->get_template_id('single', 'post')) {
363 $this->current_template_id = $custom_template;
364 return $this->getTemplatePath('single-post', $template);
365 }
366 }
367
368 if (is_archive()) {
369 if ($custom_template = $this->get_template_id('archive', 'post')) {
370 $this->current_template_id = $custom_template;
371 return $this->getTemplatePath('archive-post', $template);
372 }
373 }
374
375 if (is_home()) {
376 if ($custom_template = $this->get_template_id('home', 'post')) {
377 $this->current_template_id = $custom_template;
378 return $this->getTemplatePath('home', $template);
379 }
380 }
381
382 if ($page_Id = ultimate_store_kit_get_compare_page_option()) {
383 if (is_page($page_Id)) {
384 if ($custom_template = $this->get_template_id('compare-products', 'product')) {
385 $this->current_template_id = $custom_template;
386 return $this->getTemplatePath('home', $template);
387 }
388 }
389 }
390
391 return $template;
392 }
393
394
395 public function getThemeTemplatePath($slug) {
396
397 $fullPath = get_template_directory() . "/ultimate-store-kit/$slug";
398 if (file_exists($fullPath)) {
399 return $fullPath;
400 }
401 }
402
403 public function getPluginTemplatePath($slug) {
404
405 $fullPath = BDTUSK_PATH . "includes/builder/templates/$slug";
406 if (file_exists($fullPath)) {
407 return $fullPath;
408 }
409 }
410
411
412 /**
413 * Get Template Path ID
414 *
415 * @param $slug
416 * @param $postType
417 *
418 * @return mixed|void|null
419 */
420 public function get_template_id($slug, $postType = false) {
421 // If we already have a template ID for this request, return it
422 if (null !== $this->current_template_id) {
423 return $this->current_template_id;
424 }
425
426 // Handle template preview from URL parameters.
427 if (!empty($_GET['preview']) && !empty($_GET['ultimate_store_kit_template_id']) && !empty($_GET['ultimate_store_kit_preview_nonce'])) {
428 $usk_template_id = absint(wp_unslash($_GET['ultimate_store_kit_template_id']));
429 $nonce = sanitize_text_field(wp_unslash($_GET['ultimate_store_kit_preview_nonce']));
430
431 // The nonce is bound to the specific template and to the user who
432 // generated it, and the capability check makes sure a leaked preview
433 // URL cannot be replayed by someone who may not edit the template.
434 $nonce_verified = $usk_template_id
435 && wp_verify_nonce($nonce, 'ultimate_store_kit_template_preview_' . $usk_template_id)
436 && current_user_can('edit_post', $usk_template_id);
437
438 if ($nonce_verified && get_post_type($usk_template_id) === Meta::POST_TYPE) {
439 $template_type = get_post_meta($usk_template_id, Meta::TEMPLATE_TYPE, true);
440 if (!empty($template_type)) {
441 $template_data = explode(Builder_Template_Helper::separator(), $template_type);
442 if (count($template_data) >= 2 && $template_data[1] === $slug && ($postType === false || $template_data[0] === $postType)) {
443 $this->current_template_id = $usk_template_id;
444 return $this->current_template_id;
445 }
446 }
447 }
448 }
449
450 // Regular template retrieval
451 $templateId = Builder_Template_Helper::getTemplate($slug, $postType);
452 $this->current_template_id = apply_filters('ultimate-store-kit-builder/custom-shop-template', $templateId);
453
454 return $this->current_template_id;
455 }
456
457
458 /**
459 * Get Template Path
460 *
461 * @param $slug
462 * @param $default
463 *
464 * @return mixed|string|void
465 */
466 protected function getTemplatePath($slug, $default = '') {
467 $phpSlug = "{$slug}.php";
468
469 if ($template = $this->getThemeTemplatePath($phpSlug)) {
470 return $template;
471 }
472
473 if ($template = $this->getPluginTemplatePath($phpSlug)) {
474 return $template;
475 }
476
477 return $default;
478 }
479
480 /**
481 * Resolve the plugin cart template for WooCommerce cart requests.
482 *
483 * @param string $template Default WordPress template path.
484 * @return string|false
485 */
486 protected function resolve_cart_template( $template ) {
487 if ( ! Cart_Render::is_available() ) {
488 return false;
489 }
490
491 $custom_template = $this->get_template_id( 'cart', 'product' );
492 $this->current_template_id = $custom_template ? absint( $custom_template ) : null;
493
494 return $this->getTemplatePath( 'woocommerce/cart', $template );
495 }
496
497 /**
498 * Get default product URL with proper error checking
499 *
500 * @return string
501 */
502 protected function get_default_product_url() {
503 $products = wc_get_products(['status' => 'publish', 'limit' => 1]);
504
505 if (!empty($products) && isset($products[0]) && is_object($products[0])) {
506 $product = $products[0];
507 if (method_exists($product, 'get_id')) {
508 return get_permalink($product->get_id());
509 }
510 }
511
512 // Fallback to shop page if no products found
513 return get_permalink(wc_get_page_id('shop'));
514 }
515 }
516
517
518 Builder_Integration::instance();
519