PluginProbe
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant / 2.3.2
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant v2.3.2
2.3.2 2.3.1 2.3.0 2.2.8 2.2.7 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.11.0 1.11.1 1.11.2 1.6 1.7 1.8 1.8.1 1.8.2 1.8.3 1.9.0 1.9.1 1.9.10 1.9.11 All 60 releases
← All changes | inc/helpers.php +1245 -501 1.9.12.3.2 View file →
@@ -1,501 +1,1245 @@
1 -<?php
2 -
3 -/**
4 - * Helper functions.
5 - *
6 - * @package Merchant
7 - */
8 -
9 -if ( ! defined( 'ABSPATH' ) ) {
10 - exit; // Exit if accessed directly
11 -}
12 -
13 -/**
14 - * Allowed tags general.
15 - * Should be used to escape complex outputs like entire features html.
16 - *
17 - * @param array $extra
18 - * @param bool $include_post_tags Whether to include post kses allowed tags or not. Default true.
19 - *
20 - * @return array $allowed_tags
21 - */
22 -if ( ! function_exists( 'merchant_kses_allowed_tags' ) ) {
23 - function merchant_kses_allowed_tags( $extra = array(), $include_post_tags = true ) {
24 - // Default
25 - $allowed_tags = array_merge(
26 - array(
27 - // Meta
28 - 'meta' => array(
29 - 'name' => true,
30 - 'content' => true,
31 - ),
32 -
33 - // SVG Support
34 - 'svg' => array(
35 - 'class' => true,
36 - 'xmlns' => true,
37 - 'width' => true,
38 - 'height' => true,
39 - 'viewbox' => true,
40 - 'aria-hidden' => true,
41 - 'role' => true,
42 - 'focusable' => true,
43 - 'fill' => true,
44 - 'stroke' => true,
45 - 'stroke-linecap' => true,
46 - 'stroke-linejoin' => true,
47 - 'stroke-width' => true,
48 - ),
49 - 'g' => array(
50 - 'id' => true,
51 - 'class' => true,
52 - 'clip-path' => true,
53 - 'style' => true,
54 - 'transform' => true,
55 - ),
56 - 'path' => array(
57 - 'fill' => true,
58 - 'fill-rule' => true,
59 - 'd' => true,
60 - 'transform' => true,
61 - 'stroke' => true,
62 - 'stroke-width' => true,
63 - 'stroke-linejoin' => true,
64 - 'clip-rule' => true,
65 - ),
66 - 'polyline' => array(
67 - 'points' => true,
68 - 'fill' => true,
69 - 'fill-rule' => true,
70 - 'd' => true,
71 - 'transform' => true,
72 - 'stroke' => true,
73 - 'stroke-width' => true,
74 - 'stroke-linejoin' => true,
75 - ),
76 - 'polygon' => array(
77 - 'fill' => true,
78 - 'fill-rule' => true,
79 - 'points' => true,
80 - 'transform' => true,
81 - 'focusable' => true,
82 - 'stroke' => true,
83 - 'stroke-width' => true,
84 - ),
85 - 'rect' => array(
86 - 'x' => true,
87 - 'y' => true,
88 - 'rx' => true,
89 - 'width' => true,
90 - 'height' => true,
91 - 'transform' => true,
92 - 'fill' => true,
93 - 'stroke' => true,
94 - 'stroke-width' => true,
95 - ),
96 - 'circle' => array(
97 - 'cx' => true,
98 - 'cy' => true,
99 - 'r' => true,
100 - 'width' => true,
101 - 'height' => true,
102 - 'transform' => true,
103 - 'fill' => true,
104 - 'stroke' => true,
105 - 'stroke-width' => true,
106 - 'stroke-linecap' => true,
107 - 'stroke-linejoin' => true,
108 - ),
109 - 'line' => array(
110 - 'x1' => true,
111 - 'y1' => true,
112 - 'x2' => true,
113 - 'y2' => true,
114 - 'width' => true,
115 - 'height' => true,
116 - 'transform' => true,
117 - 'fill' => true,
118 - 'stroke' => true,
119 - 'stroke-width' => true,
120 - 'stroke-linecap' => true,
121 - 'stroke-linejoin' => true,
122 - ),
123 - 'clipPath' => array(
124 - 'id' => true,
125 - 'class' => true,
126 - 'style' => true,
127 - ),
128 - 'defs' => array(
129 - 'id' => true,
130 - ),
131 - ),
132 - $include_post_tags ? wp_kses_allowed_html( 'post' ) : array()
133 - );
134 -
135 - // Include schema markup tags
136 - if ( in_array( 'schema_markup', $extra, true ) || in_array( 'all', $extra, true ) ) {
137 - $tags = array( 'meta', 'nav', 'ul', 'li', 'a', 'span' );
138 -
139 - foreach ( $tags as $tag ) {
140 - if ( isset( $allowed_tags[ $tag ] ) ) {
141 - $allowed_tags[ $tag ]['itemprop'] = true;
142 - $allowed_tags[ $tag ]['itemscope'] = true;
143 - $allowed_tags[ $tag ]['itemtype'] = true;
144 - } else {
145 - $allowed_tags[ $tag ] = array(
146 - 'itemprop' => true,
147 - 'itemscope' => true,
148 - 'itemtype' => true,
149 - );
150 - }
151 - }
152 - }
153 -
154 - // Include iframe tags
155 - if ( in_array( 'iframe', $extra, true ) || in_array( 'all', $extra, true ) ) {
156 - $allowed_tags['iframe'] = array(
157 - 'src' => true,
158 - 'height' => true,
159 - 'width' => true,
160 - 'frameborder' => true,
161 - 'allowfullscreen' => true,
162 - );
163 - }
164 -
165 - if ( in_array( 'wc_email', $extra, true ) || in_array( 'all', $extra, true ) ) {
166 - $allowed_tags['body'] = array(
167 - 'class' => true,
168 - 'style' => true,
169 - 'leftmargin' => true,
170 - 'marginwidth' => true,
171 - 'topmargin' => true,
172 - 'marginheight' => true,
173 - 'offset' => true,
174 - );
175 -
176 - $allowed_tags['html'] = array(
177 - 'lang' => true,
178 - );
179 -
180 - $allowed_tags['style'] = array(
181 - 'type' => true,
182 - );
183 - $allowed_tags['head'] = array();
184 - $allowed_tags['meta']['http-equiv'] = true;
185 - }
186 -
187 - // Include nonce tags
188 - if ( in_array( 'nonce', $extra, true ) || in_array( 'all', $extra, true ) ) {
189 - $allowed_tags['input'] = array(
190 - 'type' => true,
191 - 'id' => true,
192 - 'name' => true,
193 - 'value' => true,
194 - 'data-name' => true,
195 - );
196 - }
197 -
198 - // Include bdi tag
199 - if ( in_array( 'bdi', $extra, true ) || in_array( 'all', $extra, true ) ) {
200 - $allowed_tags['bdi'] = array(
201 - 'class' => true,
202 - 'id' => true,
203 - 'style' => true,
204 - );
205 - }
206 -
207 - // Include select2
208 - if ( in_array( 'select2', $extra, true ) || in_array( 'all', $extra, true ) ) {
209 - $allowed_tags['select'] = array(
210 - 'name' => true,
211 - 'class' => true,
212 - 'id' => true,
213 - 'style' => true,
214 - 'data-name' => true,
215 - 'data-source' => true,
216 - 'multiple' => true,
217 - );
218 -
219 - $allowed_tags['option'] = array(
220 - 'value' => true,
221 - 'selected' => true,
222 - );
223 -
224 - $allowed_tags['optgroup'] = array(
225 - 'label' => true,
226 - );
227 - }
228 -
229 - // Include dd, dt tags
230 - if ( in_array( 'dd', $extra, true ) || in_array( 'all', $extra, true ) ) {
231 - $allowed_tags['dd'] = array(
232 - 'class' => true,
233 - 'id' => true,
234 - 'style' => true,
235 - );
236 -
237 - $allowed_tags['dt'] = array(
238 - 'class' => true,
239 - 'id' => true,
240 - 'style' => true,
241 - );
242 - }
243 -
244 - // Include forms tags.
245 - if ( in_array( 'forms', $extra, true ) || in_array( 'all', $extra, true ) ) {
246 - $tags = array( 'form', 'input', 'select', 'option', 'textarea' );
247 -
248 - foreach ( $tags as $tag ) {
249 - $allowed_tags[ $tag ] = array(
250 - 'id' => true,
251 - 'class' => true,
252 - 'style' => true,
253 - 'name' => true,
254 - 'value' => true,
255 - 'type' => true,
256 - 'placeholder' => true,
257 - 'data' => '*',
258 - 'data-source' => true,
259 - 'data-product_id' => true,
260 - 'data-product_variations' => true,
261 - 'data-attribute_name' => true,
262 - 'data-show_option_none' => true,
263 - 'data-name' => true,
264 - 'step' => true,
265 - 'min' => true,
266 - 'max' => true,
267 - 'selected' => true,
268 - 'checked' => true,
269 - 'onchange' => true,
270 - 'autocomplete' => true,
271 - 'required' => true,
272 - 'action' => true,
273 - 'method' => true,
274 - 'enctype' => true,
275 - 'size' => true,
276 - 'role' => true,
277 - 'inputmode' => true,
278 - 'aria-label' => true,
279 - 'multiple' => true,
280 - );
281 - }
282 - }
283 -
284 - /**
285 - * Filters the allowed tags.
286 - *
287 - * @since 1.2.5
288 - */
289 - return apply_filters( 'merchant_kses_allowed_tags', $allowed_tags );
290 - }
291 -}
292 -
293 -/**
294 - * Allowed tags for scripts.
295 - *
296 - * @return array $allowed_tags
297 - */
298 -if ( ! function_exists( 'merchant_kses_allowed_tags_for_code_snippets' ) ) {
299 - function merchant_kses_allowed_tags_for_code_snippets() {
300 - return array(
301 - 'script' => array(
302 - 'type' => true,
303 - ),
304 - );
305 - }
306 -}
307 -
308 -/**
309 - * Check if WooCommerce checkout page is being rendered by block.
310 - * Since WooCommerce 8.3.0 the checkout page is rendered by block.
311 - *
312 - * @return bool
313 - */
314 -if ( ! function_exists( 'merchant_is_checkout_block_layout' ) ) {
315 - function merchant_is_checkout_block_layout() {
316 - $checkout_page = wc_get_page_id( 'checkout' );
317 -
318 - if ( empty( $checkout_page ) ) {
319 - return false;
320 - }
321 -
322 - if ( function_exists( 'has_blocks' ) && has_blocks( $checkout_page ) ) {
323 - $post = get_post( $checkout_page );
324 - $blocks = parse_blocks( $post->post_content );
325 -
326 - foreach ( $blocks as $block ) {
327 - if ( 'woocommerce/checkout' === $block['blockName'] ) {
328 - return true;
329 - }
330 - }
331 - }
332 -
333 - return false;
334 - }
335 -}
336 -
337 -/**
338 - * Check if WooCommerce cart page is being rendered by block.
339 - * Since WooCommerce 8.3.0 the cart page is rendered by block.
340 - *
341 - * @return bool
342 - */
343 -if ( ! function_exists( 'merchant_is_cart_block_layout' ) ) {
344 - function merchant_is_cart_block_layout() {
345 - $cart_page = wc_get_page_id( 'cart' );
346 -
347 - if ( empty( $cart_page ) ) {
348 - return false;
349 - }
350 -
351 - if ( function_exists( 'has_blocks' ) && has_blocks( $cart_page ) ) {
352 - $post = get_post( $cart_page );
353 - $blocks = parse_blocks( $post->post_content );
354 -
355 - foreach ( $blocks as $block ) {
356 - if ( 'woocommerce/cart' === $block['blockName'] ) {
357 - return true;
358 - }
359 - }
360 - }
361 -
362 - return false;
363 - }
364 -}
365 -
366 -/**
367 - * Get the product categories.
368 - */
369 -if ( ! function_exists( 'merchant_get_product_categories' ) ) {
370 - function merchant_get_product_categories() {
371 - $product_categories = get_terms( array(
372 - 'taxonomy' => 'product_cat',
373 - 'hide_empty' => false,
374 - ) );
375 - $category_names = array();
376 - if ( ! empty( $product_categories ) && ! is_wp_error( $product_categories ) ) {
377 - foreach ( $product_categories as $category ) {
378 - $category_names[ $category->slug ] = $category->name;
379 - }
380 - }
381 -
382 - return $category_names;
383 - }
384 -}
385 -
386 -/**
387 - * Convert array to css.
388 - */
389 -if ( ! function_exists( 'merchant_array_to_css' ) ) {
390 - function merchant_array_to_css( $css_array ) {
391 - $css = '';
392 - if ( empty( $css_array ) ) {
393 - return $css;
394 - }
395 - foreach ( $css_array as $key => $value ) {
396 - $css .= $key . ':' . $value . ';';
397 - }
398 -
399 - return $css;
400 - }
401 -}
402 -
403 -/**
404 - * Get the share link data structure.
405 - *
406 - * @return array
407 - */
408 -if ( ! function_exists( 'merchant_get_share_link_data' ) ) {
409 - function merchant_get_share_link_data() {
410 - return array(
411 - 'facebook' => array(
412 - 'url' => 'https://www.facebook.com/sharer.php?u={{url}}',
413 - 'title' => __( 'Facebook', 'merchant' ),
414 - ),
415 - 'twitter' => array(
416 - 'url' => 'https://twitter.com/intent/tweet?url={{url}}&text={{title}}',
417 - 'title' => __( 'X', 'merchant' ),
418 - ),
419 - 'linkedin' => array(
420 - 'url' => 'https://www.linkedin.com/sharing/share-offsite/?url={{url}}',
421 - 'title' => __( 'LinkedIn', 'merchant' ),
422 - ),
423 - 'reddit' => array(
424 - 'url' => 'https://reddit.com/submit?url={{url}}&title={{title}}',
425 - 'title' => __( 'Reddit', 'merchant' ),
426 - ),
427 - 'whatsapp' => array(
428 - 'url' => 'https://api.whatsapp.com/send/?text={{url}}',
429 - 'title' => __( 'WhatsApp', 'merchant' ),
430 - ),
431 - 'pinterest' => array(
432 - 'url' => 'http://pinterest.com/pin/create/link/?url={{url}}',
433 - 'title' => __( 'Pinterest', 'merchant' ),
434 - ),
435 - 'telegram' => array(
436 - 'url' => 'https://t.me/share/url?url={{url}}&text={{title}}',
437 - 'title' => __( 'Telegram', 'merchant' ),
438 - ),
439 - 'weibo' => array(
440 - 'url' => 'http://service.weibo.com/share/share.php?url={{url}}&appkey=&title={{title}}&pic=&ralateUid=',
441 - 'title' => __( 'Weibo', 'merchant' ),
442 - ),
443 - 'vk' => array(
444 - 'url' => 'http://vk.com/share.php?url={{url}}&title={{title}}&comment={text}',
445 - 'title' => __( 'VK', 'merchant' ),
446 - ),
447 - 'ok' => array(
448 - 'url' => 'https://connect.ok.ru/dk?st.cmd=WidgetSharePreview&st.shareUrl={{url}}',
449 - 'title' => __( 'OK', 'merchant' ),
450 - ),
451 - 'xing' => array(
452 - 'url' => 'https://www.xing.com/spi/shares/new?url={{url}}',
453 - 'title' => __( 'Xing', 'merchant' ),
454 - ),
455 - 'mail' => array(
456 - 'url' => 'mailto:?subject={{title}}&body={{url}}',
457 - 'title' => __( 'Mail', 'merchant' ),
458 - ),
459 - );
460 - }
461 -}
462 -
463 -/**
464 - * Get the share link url.
465 - *
466 - * @param string $social_network
467 - *
468 - * @return string
469 - */
470 -if ( ! function_exists( 'merchant_get_share_link_url' ) ) {
471 - function merchant_get_share_link_url( $social_network, $url_to_share, $title_to_share = '' ) {
472 - $share_link_data = merchant_get_share_link_data();
473 -
474 - if ( ! isset( $share_link_data[ $social_network ] ) ) {
475 - return '';
476 - }
477 -
478 - $share_link_url = str_replace(
479 - array( '{{url}}', '{{title}}' ),
480 - array( $url_to_share, $title_to_share ),
481 - $share_link_data[ $social_network ]['url']
482 - );
483 -
484 - return $share_link_url;
485 - }
486 -}
487 -
488 -/**
489 - * Get the share link title.
490 - *
491 - * @param string $social_network
492 - *
493 - * @return string
494 - */
495 -if ( ! function_exists( 'merchant_get_share_link_title' ) ) {
496 - function merchant_get_share_link_title( $social_network ) {
497 - $share_link_data = merchant_get_share_link_data();
498 -
499 - return isset( $share_link_data[ $social_network ] ) ? $share_link_data[ $social_network ]['title'] : '';
500 - }
501 -}
1 +<?php
2 +
3 +/**
4 + * Helper functions.
5 + *
6 + * @package Merchant
7 + */
8 +
9 +if ( ! defined( 'ABSPATH' ) ) {
10 + exit; // Exit if accessed directly
11 +}
12 +
13 +/**
14 + * Allowed tags general.
15 + * Should be used to escape complex outputs like entire features html.
16 + *
17 + * @param array $extra
18 + * @param bool $include_post_tags Whether to include post kses allowed tags or not. Default true.
19 + *
20 + * @return array $allowed_tags
21 + */
22 +if ( ! function_exists( 'merchant_kses_allowed_tags' ) ) {
23 + function merchant_kses_allowed_tags( $extra = array(), $include_post_tags = true ) {
24 + // Default
25 + $allowed_tags = array_merge(
26 + array(
27 + // Meta
28 + 'meta' => array(
29 + 'name' => true,
30 + 'content' => true,
31 + ),
32 +
33 + // SVG Support
34 + 'svg' => array(
35 + 'class' => true,
36 + 'xmlns' => true,
37 + 'width' => true,
38 + 'height' => true,
39 + 'viewbox' => true,
40 + 'aria-hidden' => true,
41 + 'role' => true,
42 + 'focusable' => true,
43 + 'fill' => true,
44 + 'stroke' => true,
45 + 'stroke-linecap' => true,
46 + 'stroke-linejoin' => true,
47 + 'stroke-width' => true,
48 + ),
49 + 'g' => array(
50 + 'id' => true,
51 + 'class' => true,
52 + 'clip-path' => true,
53 + 'style' => true,
54 + 'transform' => true,
55 + ),
56 + 'path' => array(
57 + 'fill' => true,
58 + 'fill-rule' => true,
59 + 'd' => true,
60 + 'transform' => true,
61 + 'stroke' => true,
62 + 'stroke-width' => true,
63 + 'stroke-linejoin' => true,
64 + 'clip-rule' => true,
65 + 'opacity' => true,
66 + ),
67 + 'polyline' => array(
68 + 'points' => true,
69 + 'fill' => true,
70 + 'fill-rule' => true,
71 + 'd' => true,
72 + 'transform' => true,
73 + 'stroke' => true,
74 + 'stroke-width' => true,
75 + 'stroke-linejoin' => true,
76 + ),
77 + 'polygon' => array(
78 + 'fill' => true,
79 + 'fill-rule' => true,
80 + 'points' => true,
81 + 'transform' => true,
82 + 'focusable' => true,
83 + 'stroke' => true,
84 + 'stroke-width' => true,
85 + ),
86 + 'rect' => array(
87 + 'x' => true,
88 + 'y' => true,
89 + 'rx' => true,
90 + 'width' => true,
91 + 'height' => true,
92 + 'transform' => true,
93 + 'fill' => true,
94 + 'stroke' => true,
95 + 'stroke-width' => true,
96 + ),
97 + 'circle' => array(
98 + 'cx' => true,
99 + 'cy' => true,
100 + 'r' => true,
101 + 'width' => true,
102 + 'height' => true,
103 + 'transform' => true,
104 + 'fill' => true,
105 + 'stroke' => true,
106 + 'stroke-width' => true,
107 + 'stroke-linecap' => true,
108 + 'stroke-linejoin' => true,
109 + ),
110 + 'line' => array(
111 + 'x1' => true,
112 + 'y1' => true,
113 + 'x2' => true,
114 + 'y2' => true,
115 + 'width' => true,
116 + 'height' => true,
117 + 'transform' => true,
118 + 'fill' => true,
119 + 'stroke' => true,
120 + 'stroke-width' => true,
121 + 'stroke-linecap' => true,
122 + 'stroke-linejoin' => true,
123 + ),
124 + 'clipPath' => array(
125 + 'id' => true,
126 + 'class' => true,
127 + 'style' => true,
128 + ),
129 + 'defs' => array(
130 + 'id' => true,
131 + ),
132 + 'progress' => array(
133 + 'id' => true,
134 + 'class' => true,
135 + 'value' => true,
136 + 'max' => true,
137 + ),
138 + ),
139 + $include_post_tags ? wp_kses_allowed_html( 'post' ) : array()
140 + );
141 +
142 + // Include schema markup tags
143 + if ( in_array( 'schema_markup', $extra, true ) || in_array( 'all', $extra, true ) ) {
144 + $tags = array( 'meta', 'nav', 'ul', 'li', 'a', 'span' );
145 +
146 + foreach ( $tags as $tag ) {
147 + if ( isset( $allowed_tags[ $tag ] ) ) {
148 + $allowed_tags[ $tag ]['itemprop'] = true;
149 + $allowed_tags[ $tag ]['itemscope'] = true;
150 + $allowed_tags[ $tag ]['itemtype'] = true;
151 + } else {
152 + $allowed_tags[ $tag ] = array(
153 + 'itemprop' => true,
154 + 'itemscope' => true,
155 + 'itemtype' => true,
156 + );
157 + }
158 + }
159 + }
160 +
161 + // Include iframe tags
162 + if ( in_array( 'iframe', $extra, true ) || in_array( 'all', $extra, true ) ) {
163 + $allowed_tags['iframe'] = array(
164 + 'src' => true,
165 + 'height' => true,
166 + 'width' => true,
167 + 'frameborder' => true,
168 + 'allowfullscreen' => true,
169 + );
170 + }
171 +
172 + if ( in_array( 'wc_email', $extra, true ) || in_array( 'all', $extra, true ) ) {
173 + $allowed_tags['body'] = array(
174 + 'class' => true,
175 + 'style' => true,
176 + 'leftmargin' => true,
177 + 'marginwidth' => true,
178 + 'topmargin' => true,
179 + 'marginheight' => true,
180 + 'offset' => true,
181 + );
182 +
183 + $allowed_tags['html'] = array(
184 + 'lang' => true,
185 + );
186 +
187 + $allowed_tags['style'] = array(
188 + 'type' => true,
189 + );
190 + $allowed_tags['head'] = array();
191 + $allowed_tags['meta']['http-equiv'] = true;
192 + }
193 +
194 + // Include nonce tags
195 + if ( in_array( 'nonce', $extra, true ) || in_array( 'all', $extra, true ) ) {
196 + $allowed_tags['input'] = array(
197 + 'type' => true,
198 + 'id' => true,
199 + 'name' => true,
200 + 'value' => true,
201 + 'data-name' => true,
202 + );
203 + }
204 +
205 + // Include bdi tag
206 + if ( in_array( 'bdi', $extra, true ) || in_array( 'all', $extra, true ) ) {
207 + $allowed_tags['bdi'] = array(
208 + 'class' => true,
209 + 'id' => true,
210 + 'style' => true,
211 + );
212 + }
213 +
214 + // Include select2
215 + if ( in_array( 'select2', $extra, true ) || in_array( 'all', $extra, true ) ) {
216 + $allowed_tags['select'] = array(
217 + 'name' => true,
218 + 'class' => true,
219 + 'id' => true,
220 + 'style' => true,
221 + 'data-name' => true,
222 + 'data-source' => true,
223 + 'multiple' => true,
224 + );
225 +
226 + $allowed_tags['option'] = array(
227 + 'value' => true,
228 + 'selected' => true,
229 + );
230 +
231 + $allowed_tags['optgroup'] = array(
232 + 'label' => true,
233 + );
234 + }
235 +
236 + // Include dd, dt tags
237 + if ( in_array( 'dd', $extra, true ) || in_array( 'all', $extra, true ) ) {
238 + $allowed_tags['dd'] = array(
239 + 'class' => true,
240 + 'id' => true,
241 + 'style' => true,
242 + );
243 +
244 + $allowed_tags['dt'] = array(
245 + 'class' => true,
246 + 'id' => true,
247 + 'style' => true,
248 + );
249 + }
250 +
251 + // Include forms tags.
252 + if ( in_array( 'forms', $extra, true ) || in_array( 'all', $extra, true ) ) {
253 + $tags = array( 'form', 'input', 'select', 'option', 'textarea', 'a' );
254 +
255 + foreach ( $tags as $tag ) {
256 + $allowed_tags[ $tag ] = array(
257 + 'id' => true,
258 + 'class' => true,
259 + 'style' => true,
260 + 'name' => true,
261 + 'href' => true,
262 + 'target' => true,
263 + 'value' => true,
264 + 'type' => true,
265 + 'placeholder' => true,
266 + 'data' => '*',
267 + 'data-source' => true,
268 + 'data-product_id' => true,
269 + 'data-product_variations' => true,
270 + 'data-attribute_name' => true,
271 + 'data-show_option_none' => true,
272 + 'data-name' => true,
273 + 'data-allowed-types' => true,
274 + 'step' => true,
275 + 'min' => true,
276 + 'max' => true,
277 + 'selected' => true,
278 + 'checked' => true,
279 + 'onchange' => true,
280 + 'autocomplete' => true,
281 + 'required' => true,
282 + 'action' => true,
283 + 'method' => true,
284 + 'enctype' => true,
285 + 'size' => true,
286 + 'role' => true,
287 + 'inputmode' => true,
288 + 'aria-label' => true,
289 + 'multiple' => true,
290 + );
291 +
292 + if ( $tag === 'a' ) {
293 + $allowed_tags[ $tag ]['href'] = true;
294 + $allowed_tags[ $tag ]['title'] = true;
295 + $allowed_tags[ $tag ]['target'] = true;
296 + }
297 + }
298 + }
299 +
300 +
301 + // Include script tag
302 + if ( in_array( 'script', $extra, true ) ) {
303 + $allowed_tags['script'] = array(
304 + 'id' => true,
305 + 'src' => true,
306 + 'type' => true,
307 + 'async' => true,
308 + 'defer' => true,
309 + );
310 + }
311 +
312 + if ( in_array( 'div', $extra, true ) ) {
313 + $tags = array( 'form', 'input', 'select', 'option', 'textarea', 'a', 'div' );
314 +
315 + foreach ( $tags as $tag ) {
316 + $allowed_tags[ $tag ] = array(
317 + 'id' => true,
318 + 'class' => true,
319 + 'style' => true,
320 + 'name' => true,
321 + 'href' => true,
322 + 'target' => true,
323 + 'value' => true,
324 + 'type' => true,
325 + 'placeholder' => true,
326 + 'data' => '*',
327 + 'data-source' => true,
328 + 'data-product_id' => true,
329 + 'data-product_variations' => true,
330 + 'data-attribute_name' => true,
331 + 'data-show_option_none' => true,
332 + 'data-name' => true,
333 + 'data-allowed-types' => true,
334 + 'step' => true,
335 + 'min' => true,
336 + 'max' => true,
337 + 'selected' => true,
338 + 'checked' => true,
339 + 'onchange' => true,
340 + 'autocomplete' => true,
341 + 'required' => true,
342 + 'action' => true,
343 + 'method' => true,
344 + 'enctype' => true,
345 + 'size' => true,
346 + 'role' => true,
347 + 'inputmode' => true,
348 + 'aria-label' => true,
349 + 'multiple' => true,
350 + 'title' => true,
351 + 'data-id' => true,
352 + 'data-product-id' => true,
353 + );
354 +
355 + if ( $tag === 'a' ) {
356 + $allowed_tags[ $tag ]['href'] = true;
357 + $allowed_tags[ $tag ]['title'] = true;
358 + $allowed_tags[ $tag ]['target'] = true;
359 + }
360 + }
361 + }
362 +
363 + /**
364 + * Filters the allowed tags.
365 + *
366 + * @since 1.2.5
367 + */
368 + return apply_filters( 'merchant_kses_allowed_tags', $allowed_tags );
369 + }
370 +}
371 +
372 +/**
373 + * Allowed tags for scripts.
374 + *
375 + * @return array $allowed_tags
376 + */
377 +if ( ! function_exists( 'merchant_kses_allowed_tags_for_code_snippets' ) ) {
378 + function merchant_kses_allowed_tags_for_code_snippets() {
379 + return array(
380 + 'script' => array(
381 + 'type' => true,
382 + ),
383 + );
384 + }
385 +}
386 +
387 +/**
388 + * Sanitize SVG markup.
389 + *
390 + * Uses the existing SVG allowlist from merchant_kses_allowed_tags()
391 + * to preserve SVG elements while stripping unsafe content.
392 + *
393 + * @param mixed $value The raw SVG markup.
394 + *
395 + * @return string The sanitized SVG markup.
396 + *
397 + * @since 2.3.0
398 + */
399 +if ( ! function_exists( 'merchant_sanitize_svg' ) ) {
400 + function merchant_sanitize_svg( $value ) {
401 + return wp_kses( (string) $value, merchant_kses_allowed_tags( array(), false ) );
402 + }
403 +}
404 +
405 +/**
406 + * Format a price for admin preview display.
407 + *
408 + * Uses wc_price() when WooCommerce is available, otherwise falls back to a plain
409 + * currency symbol + number_format string.
410 + *
411 + * @param float $price The price value.
412 + * @param string $currency Currency symbol fallback (used only when WC is unavailable).
413 + *
414 + * @return string Formatted price HTML.
415 + *
416 + * @since 2.2.5
417 + */
418 +if ( ! function_exists( 'merchant_preview_price' ) ) {
419 + function merchant_preview_price( $price, $currency = '$' ) {
420 + if ( function_exists( 'wc_price' ) ) {
421 + return wp_kses( wc_price( $price ), merchant_kses_allowed_tags( array( 'bdi' ) ) );
422 + }
423 +
424 + return esc_html( $currency ) . esc_html( number_format( (float) $price, 2 ) );
425 + }
426 +}
427 +
428 +/**
429 + * Check if WooCommerce checkout page is being rendered by block.
430 + * Since WooCommerce 8.3.0 the checkout page is rendered by block.
431 + *
432 + * @return bool
433 + */
434 +if ( ! function_exists( 'merchant_is_checkout_block_layout' ) ) {
435 + function merchant_is_checkout_block_layout() {
436 + $checkout_page = wc_get_page_id( 'checkout' );
437 +
438 + if ( empty( $checkout_page ) ) {
439 + return false;
440 + }
441 +
442 + if ( function_exists( 'has_blocks' ) && has_blocks( $checkout_page ) ) {
443 + $post = get_post( $checkout_page );
444 + $blocks = parse_blocks( $post->post_content );
445 +
446 + foreach ( $blocks as $block ) {
447 + if ( 'woocommerce/checkout' === $block['blockName'] ) {
448 + return true;
449 + }
450 + }
451 + }
452 +
453 + return false;
454 + }
455 +}
456 +
457 +/**
458 + * Check if WooCommerce cart page is being rendered by block.
459 + * Since WooCommerce 8.3.0 the cart page is rendered by block.
460 + *
461 + * @return bool
462 + */
463 +if ( ! function_exists( 'merchant_is_cart_block_layout' ) ) {
464 + function merchant_is_cart_block_layout() {
465 + $cart_page = wc_get_page_id( 'cart' );
466 +
467 + if ( empty( $cart_page ) ) {
468 + return false;
469 + }
470 +
471 + if ( function_exists( 'has_blocks' ) && has_blocks( $cart_page ) ) {
472 + $post = get_post( $cart_page );
473 + $blocks = parse_blocks( $post->post_content );
474 +
475 + foreach ( $blocks as $block ) {
476 + if ( 'woocommerce/cart' === $block['blockName'] ) {
477 + return true;
478 + }
479 + }
480 + }
481 +
482 + return false;
483 + }
484 +}
485 +
486 +/**
487 + * Get the product categories.
488 + */
489 +if ( ! function_exists( 'merchant_get_product_categories' ) ) {
490 + function merchant_get_product_categories() {
491 + $args = array(
492 + 'taxonomy' => 'product_cat',
493 + 'hide_empty' => false,
494 + );
495 +
496 + $categories = get_terms( $args );
497 +
498 + $categories_tree = array();
499 + $indexed_categories = array();
500 +
501 + if ( ! empty( $categories ) && ! is_wp_error( $categories ) ) {
502 + foreach ( $categories as $category ) {
503 + $indexed_categories[ $category->term_id ] = array(
504 + 'id' => $category->term_id,
505 + 'slug' => $category->slug,
506 + 'name' => $category->name,
507 + 'parent' => $category->parent,
508 + 'children' => array(),
509 + );
510 + }
511 +
512 + foreach ( $indexed_categories as &$cat ) {
513 + if ( (int) $cat['parent'] === 0 ) {
514 + $categories_tree[] = &$cat;
515 + } else {
516 + $indexed_categories[ $cat['parent'] ]['children'][] = &$cat;
517 + }
518 + }
519 + }
520 +
521 + return $categories_tree;
522 + }
523 +}
524 +
525 +/**
526 + * Get every descendant category slug of a given category term.
527 + *
528 + * Uses a single `child_of` term query, so the whole branch is returned in one
529 + * call regardless of how deeply the tree is nested.
530 + *
531 + * @param int $term_id Parent product category term ID.
532 + *
533 + * @return string[] Descendant category slugs. Empty when the term has no children.
534 + */
535 +if ( ! function_exists( 'merchant_get_category_descendant_slugs' ) ) {
536 + function merchant_get_category_descendant_slugs( $term_id ) {
537 + $descendants = get_terms(
538 + array(
539 + 'taxonomy' => 'product_cat',
540 + 'child_of' => (int) $term_id,
541 + 'fields' => 'slugs',
542 + 'hide_empty' => false,
543 + )
544 + );
545 +
546 + if ( is_wp_error( $descendants ) || ! is_array( $descendants ) ) {
547 + return array();
548 + }
549 +
550 + return $descendants;
551 + }
552 +}
553 +
554 +/**
555 + * Expand category slugs to include all of their descendant categories.
556 + *
557 + * Products are only ever assigned to the categories picked for them, so a
558 + * product sitting in "Hoodies" is not matched by a rule targeting its parent
559 + * "Clothing". Expanding the parent into its whole branch makes that match work.
560 + *
561 + * Slugs that do not resolve to a category are passed through untouched, so an
562 + * unknown slug never silently disappears from a rule.
563 + *
564 + * @param string[] $slugs Category slugs to expand.
565 + *
566 + * @return string[] The given slugs plus every descendant slug, de-duplicated.
567 + */
568 +if ( ! function_exists( 'merchant_expand_categories_with_children' ) ) {
569 + function merchant_expand_categories_with_children( $slugs ) {
570 + $slugs = array_filter( array_map( 'strval', (array) $slugs ), 'strlen' );
571 +
572 + if ( empty( $slugs ) ) {
573 + return array();
574 + }
575 +
576 + $expanded = $slugs;
577 +
578 + foreach ( $slugs as $slug ) {
579 + $term = get_term_by( 'slug', $slug, 'product_cat' );
580 +
581 + if ( empty( $term ) || is_wp_error( $term ) ) {
582 + continue;
583 + }
584 +
585 + $expanded = array_merge( $expanded, merchant_get_category_descendant_slugs( $term->term_id ) );
586 + }
587 +
588 + return array_values( array_unique( $expanded ) );
589 + }
590 +}
591 +
592 +/**
593 + * Expand category slugs only when a campaign opted into subcategories.
594 + *
595 + * Call this at the point a campaign's category slugs are read, so matching
596 + * behaviour stays byte-for-byte identical while the toggle is off.
597 + *
598 + * @param string[] $slugs Category slugs from the campaign.
599 + * @param array<string, mixed> $settings Campaign settings holding the toggle.
600 + *
601 + * @return string[] Expanded slugs when the toggle is on, otherwise the input.
602 + */
603 +if ( ! function_exists( 'merchant_maybe_expand_categories' ) ) {
604 + function merchant_maybe_expand_categories( $slugs, $settings ) {
605 + if ( empty( $settings['include_subcategories'] ) ) {
606 + return (array) $slugs;
607 + }
608 +
609 + return merchant_expand_categories_with_children( $slugs );
610 + }
611 +}
612 +
613 +/**
614 + * Get the product tags.
615 + */
616 +if ( ! function_exists( 'merchant_get_product_tags' ) ) {
617 + function merchant_get_product_tags() {
618 + $product_tags = get_terms( array(
619 + 'taxonomy' => 'product_tag',
620 + 'hide_empty' => false,
621 + ) );
622 +
623 + $tag_names = array();
624 +
625 + if ( ! empty( $product_tags ) && ! is_wp_error( $product_tags ) ) {
626 + foreach ( $product_tags as $tag ) {
627 + $tag_names[ $tag->slug ] = $tag->name;
628 + }
629 + }
630 +
631 + return $tag_names;
632 + }
633 +}
634 +
635 +/**
636 + * Convert array to css.
637 + */
638 +if ( ! function_exists( 'merchant_array_to_css' ) ) {
639 + function merchant_array_to_css( $css_array ) {
640 + $css = '';
641 + if ( empty( $css_array ) ) {
642 + return $css;
643 + }
644 + foreach ( $css_array as $key => $value ) {
645 + $css .= $key . ':' . $value . ';';
646 + }
647 +
648 + return $css;
649 + }
650 +}
651 +
652 +/**
653 + * Get the share link data structure.
654 + *
655 + * @return array
656 + */
657 +if ( ! function_exists( 'merchant_get_share_link_data' ) ) {
658 + function merchant_get_share_link_data() {
659 + return array(
660 + 'facebook' => array(
661 + 'url' => 'https://www.facebook.com/sharer.php?u={{url}}',
662 + 'title' => __( 'Facebook', 'merchant' ),
663 + ),
664 + 'twitter' => array(
665 + 'url' => 'https://twitter.com/intent/tweet?url={{url}}&text={{title}}',
666 + 'title' => __( 'X', 'merchant' ),
667 + ),
668 + 'linkedin' => array(
669 + 'url' => 'https://www.linkedin.com/sharing/share-offsite/?url={{url}}',
670 + 'title' => __( 'LinkedIn', 'merchant' ),
671 + ),
672 + 'reddit' => array(
673 + 'url' => 'https://reddit.com/submit?url={{url}}&title={{title}}',
674 + 'title' => __( 'Reddit', 'merchant' ),
675 + ),
676 + 'whatsapp' => array(
677 + 'url' => 'https://api.whatsapp.com/send/?text={{url}}',
678 + 'title' => __( 'WhatsApp', 'merchant' ),
679 + ),
680 + 'pinterest' => array(
681 + 'url' => 'http://pinterest.com/pin/create/link/?url={{url}}',
682 + 'title' => __( 'Pinterest', 'merchant' ),
683 + ),
684 + 'telegram' => array(
685 + 'url' => 'https://t.me/share/url?url={{url}}&text={{title}}',
686 + 'title' => __( 'Telegram', 'merchant' ),
687 + ),
688 + 'weibo' => array(
689 + 'url' => 'http://service.weibo.com/share/share.php?url={{url}}&appkey=&title={{title}}&pic=&ralateUid=',
690 + 'title' => __( 'Weibo', 'merchant' ),
691 + ),
692 + 'vk' => array(
693 + 'url' => 'http://vk.com/share.php?url={{url}}&title={{title}}&comment={text}',
694 + 'title' => __( 'VK', 'merchant' ),
695 + ),
696 + 'ok' => array(
697 + 'url' => 'https://connect.ok.ru/dk?st.cmd=WidgetSharePreview&st.shareUrl={{url}}',
698 + 'title' => __( 'OK', 'merchant' ),
699 + ),
700 + 'xing' => array(
701 + 'url' => 'https://www.xing.com/spi/shares/new?url={{url}}',
702 + 'title' => __( 'Xing', 'merchant' ),
703 + ),
704 + 'mail' => array(
705 + 'url' => 'mailto:?subject={{title}}&body={{url}}',
706 + 'title' => __( 'Mail', 'merchant' ),
707 + ),
708 + );
709 + }
710 +}
711 +
712 +/**
713 + * Get the share link url.
714 + *
715 + * @param string $social_network
716 + *
717 + * @return string
718 + */
719 +if ( ! function_exists( 'merchant_get_share_link_url' ) ) {
720 + function merchant_get_share_link_url( $social_network, $url_to_share, $title_to_share = '' ) {
721 + $share_link_data = merchant_get_share_link_data();
722 +
723 + if ( ! isset( $share_link_data[ $social_network ] ) ) {
724 + return '';
725 + }
726 +
727 + $share_link_url = str_replace(
728 + array( '{{url}}', '{{title}}' ),
729 + array( $url_to_share, $title_to_share ),
730 + $share_link_data[ $social_network ]['url']
731 + );
732 +
733 + return $share_link_url;
734 + }
735 +}
736 +
737 +/**
738 + * Get the share link title.
739 + *
740 + * @param string $social_network
741 + *
742 + * @return string
743 + */
744 +if ( ! function_exists( 'merchant_get_share_link_title' ) ) {
745 + function merchant_get_share_link_title( $social_network ) {
746 + $share_link_data = merchant_get_share_link_data();
747 +
748 + return isset( $share_link_data[ $social_network ] ) ? $share_link_data[ $social_network ]['title'] : '';
749 + }
750 +}
751 +
752 +if ( ! function_exists( 'merchant_timezone' ) ) {
753 + /**
754 + * Get the WP timezone.
755 + *
756 + * @return string
757 + */
758 + function merchant_timezone() {
759 + /**
760 + * Filter the storewide sale timezone.
761 + *
762 + * @param string $timezone
763 + *
764 + * @since 1.9.9
765 + */
766 + return apply_filters(
767 + 'merchant_storewide_sale_timezone',
768 + wp_timezone_string()
769 + );
770 + }
771 +}
772 +
773 +if ( ! function_exists( 'merchant_get_current_timestamp' ) ) {
774 + /**
775 + * Get the current timestamp.
776 + *
777 + * @return int|string
778 + */
779 + function merchant_get_current_timestamp() {
780 + $timezone = new DateTimeZone( merchant_timezone() );
781 +
782 + // Get the timestamp
783 + return ( new DateTime( 'now', $timezone ) )->getTimestamp();
784 + }
785 +}
786 +
787 +if ( ! function_exists( 'merchant_convert_date_to_timestamp' ) ) {
788 + /**
789 + * Convert date to timestamp.
790 + *
791 + * @param string $date The date to convert
792 + * @param string $format The format of the date
793 + *
794 + * @return int The timestamp
795 + */
796 + function merchant_convert_date_to_timestamp( $date, $format = 'm-d-Y h:i A' ) {
797 + $timezone = new DateTimeZone( merchant_timezone() );
798 + $date_object = DateTime::createFromFormat( $format, $date, $timezone ); // Create DateTime object with specified format and timezone
799 + if ( false === $date_object ) {
800 + return 0;
801 + }
802 +
803 + return $date_object->getTimestamp(); // Output the timestamp
804 + }
805 +}
806 +
807 +if ( ! function_exists( 'merchant_date_string_to_timestamp' ) ) {
808 + /**
809 + * Convert a date string to a Unix timestamp based on the specified format and timezone.
810 + *
811 + * This function signature matches Merchant Pro for compatibility.
812 + *
813 + * @param string $date The date string to convert.
814 + * @param string|null $timezone_offset The timezone offset or timezone string. Defaults to WordPress timezone.
815 + * @param string $format The format of the date string. Defaults to 'm-d-Y h:i A'.
816 + *
817 + * @return int The timestamp, or 0 on failure.
818 + */
819 + function merchant_date_string_to_timestamp( $date, $timezone_offset = null, $format = 'm-d-Y h:i A' ) {
820 + try {
821 + // If it's already numeric, return it as-is
822 + if ( is_numeric( $date ) ) {
823 + return (int) $date;
824 + }
825 +
826 + // Use WordPress timezone if not specified
827 + $timezone_offset = $timezone_offset ?? wp_timezone_string();
828 +
829 + // Create DateTime object from the specified format and timezone
830 + $date_object = DateTime::createFromFormat( $format, $date, new DateTimeZone( $timezone_offset ) );
831 +
832 + return $date_object ? $date_object->getTimestamp() : 0;
833 + } catch ( Exception $e ) {
834 + return 0;
835 + }
836 + }
837 +}
838 +
839 +/**
840 + * Parses a list of product IDs
841 + * @return array
842 + */
843 +if ( ! function_exists( 'merchant_parse_product_ids' ) ) {
844 + function merchant_parse_product_ids( $product_ids ) {
845 + $parsed_ids = $product_ids ?? array();
846 +
847 + // Convert to array if it's not already an array
848 + $parsed_ids = ! is_array( $parsed_ids ) ? explode( ',', $parsed_ids ) : $parsed_ids;
849 +
850 + // Convert all elements to integers
851 + $parsed_ids = array_map( 'intval', $parsed_ids );
852 +
853 + return $parsed_ids;
854 + }
855 +}
856 +
857 +/**
858 + * Check if the user condition is passed.
859 + *
860 + * @param $args
861 + *
862 + * @return bool
863 + */
864 +if ( ! function_exists( 'merchant_is_user_condition_passed' ) ) {
865 + function merchant_is_user_condition_passed( $args = array() ) {
866 + $args = is_array( $args ) ? $args : array();
867 +
868 + $is_logged_in = is_user_logged_in();
869 + $current_user = $is_logged_in ? wp_get_current_user() : null;
870 + $customer_id = is_object( $current_user ) && isset( $current_user->ID ) ? (int) $current_user->ID : 0;
871 + $user_role = is_object( $current_user ) && isset( $current_user->roles ) && ! empty( $current_user->roles ) ? $current_user->roles[0] : '';
872 +
873 + $condition = $args['user_condition'] ?? 'all';
874 +
875 + $is_exclusion_enabled = $args['user_exclusion_enabled'] ?? false;
876 + $exclude_users_raw = $args['exclude_users'] ?? array();
877 + $excluded_customers = array_map( 'intval', is_array( $exclude_users_raw ) ? $exclude_users_raw : array() );
878 + $excluded_roles_raw = $args['exclude_roles'] ?? array();
879 + $excluded_roles = is_array( $excluded_roles_raw ) ? $excluded_roles_raw : array();
880 +
881 + switch ( $condition ) {
882 + case 'all':
883 + case '':
884 + if ( $is_exclusion_enabled ) {
885 + if ( in_array( $customer_id, $excluded_customers, true ) ) {
886 + return false;
887 + }
888 +
889 + if ( $condition === 'all' && in_array( $user_role, $excluded_roles, true ) ) {
890 + return false;
891 + }
892 + }
893 +
894 + return true;
895 +
896 + case 'logged-in':
897 + return $is_logged_in;
898 +
899 + case 'roles':
900 + if ( $is_exclusion_enabled && in_array( $customer_id, $excluded_customers, true ) ) {
901 + return false;
902 + }
903 +
904 + $allowed_roles_raw = $args['user_condition_roles'] ?? array();
905 + $allowed_roles = is_array( $allowed_roles_raw ) ? $allowed_roles_raw : array();
906 + return in_array( $user_role, $allowed_roles, true );
907 +
908 + case 'customers':
909 + case 'users':
910 + $allowed_users_raw = $args['user_condition_users'] ?? array();
911 + $allowed_customers = array_map( 'intval', is_array( $allowed_users_raw ) ? $allowed_users_raw : array() );
912 + return $is_logged_in && in_array( $customer_id, $allowed_customers, true );
913 +
914 + default:
915 + return false;
916 + }
917 + }
918 +}
919 +
920 +/**
921 + * Check if a product is excluded based on given arguments.
922 + *
923 + * @param $product_id
924 + * @param $args
925 + *
926 + * @return bool
927 + */
928 +if ( ! function_exists( 'merchant_is_product_excluded' ) ) {
929 + function merchant_is_product_excluded( $product_id, $args = array() ) {
930 + if ( empty( $args['exclusion_enabled'] ) ) {
931 + return false;
932 + }
933 +
934 + $display_rule = $args['rules_to_display'] ?? $args['display_rules'] ?? $args['rules_to_apply'] ?? $args['trigger_on'] ?? 'products';
935 +
936 + $rules = array(
937 + 'all',
938 + 'all_products',
939 + 'categories',
940 + 'category',
941 + 'by_category',
942 + 'tags',
943 + 'by_tags',
944 + 'featured_products',
945 + 'products_on_sale',
946 + 'new_products',
947 + 'out_of_stock',
948 + 'pre-order',
949 + );
950 +
951 + $product = wc_get_product( $product_id );
952 + $_product_id = $product && $product->is_type( 'variation' ) ? $product->get_parent_id() : $product_id;
953 +
954 + // Exclude products
955 + if ( in_array( $display_rule, $rules, true ) ) {
956 + $excluded_product_ids = $args['excluded_products'] ?? array();
957 + $excluded_product_ids = merchant_parse_product_ids( $excluded_product_ids );
958 +
959 + if ( in_array( (int) $product_id, $excluded_product_ids, true ) || in_array( (int) $_product_id, $excluded_product_ids, true ) ) {
960 + return true;
961 + }
962 + }
963 +
964 + // Exclude categories
965 + if ( in_array( $display_rule, array( 'all', 'all_products', 'categories', 'category', 'by_category' ), true ) ) {
966 + $excluded_categories_slugs = merchant_maybe_expand_categories( $args['excluded_categories'] ?? array(), $args );
967 +
968 + if ( ! empty( $excluded_categories_slugs ) && has_term( $excluded_categories_slugs, 'product_cat', $_product_id ) ) {
969 + return true;
970 + }
971 + }
972 +
973 + // Exclude tags
974 + if ( in_array( $display_rule, array( 'all', 'all_products', 'tags', 'by_tags' ), true ) ) {
975 + $excluded_tags_slugs = $args['excluded_tags'] ?? array();
976 +
977 + if ( ! empty( $excluded_tags_slugs ) && has_term( $excluded_tags_slugs, 'product_tag', $_product_id ) ) {
978 + return true;
979 + }
980 + }
981 +
982 + // Exclude brands
983 + if ( in_array( $display_rule, array( 'all', 'all_products', 'brands', 'by_brands' ), true ) ) {
984 + $excluded_brands_slugs = $args['excluded_brands'] ?? array();
985 +
986 + if ( ! empty( $excluded_brands_slugs ) && has_term( $excluded_brands_slugs, 'product_brand', $_product_id ) ) {
987 + return true;
988 + }
989 + }
990 +
991 + // Exclude categories with toggle
992 + if ( ! empty( $args['exclude_categories_toggle'] ) && in_array( $display_rule, array( 'all', 'all_products', 'categories', 'category', 'by_category' ), true ) ) {
993 + $excluded_categories_slugs = merchant_maybe_expand_categories( $args['excluded_categories'] ?? array(), $args );
994 +
995 + if ( ! empty( $excluded_categories_slugs ) && has_term( $excluded_categories_slugs, 'product_cat', $_product_id ) ) {
996 + return true;
997 + }
998 + }
999 +
1000 + // Exclude tags with toggle
1001 + if ( ! empty( $args['exclude_tags_toggle'] ) && in_array( $display_rule, array( 'all', 'all_products', 'tags', 'by_tags' ), true ) ) {
1002 + $excluded_tags_slugs = $args['excluded_tags'] ?? array();
1003 +
1004 + if ( ! empty( $excluded_tags_slugs ) && has_term( $excluded_tags_slugs, 'product_tag', $_product_id ) ) {
1005 + return true;
1006 + }
1007 + }
1008 +
1009 + // Exclude brands with toggle
1010 + if ( ! empty( $args['exclude_brands_toggle'] ) && in_array( $display_rule, array( 'all', 'all_products', 'brands', 'by_brands' ), true ) ) {
1011 + $excluded_brands_slugs = $args['excluded_brands'] ?? array();
1012 +
1013 + if ( ! empty( $excluded_brands_slugs ) && has_term( $excluded_brands_slugs, 'product_brand', $_product_id ) ) {
1014 + return true;
1015 + }
1016 + }
1017 +
1018 + return false;
1019 + }
1020 +}
1021 +
1022 +/**
1023 + * Check if a product or any of its variations is excluded based on the given offer.
1024 + *
1025 + * This method determines if a product (simple or variable) or any of its variations
1026 + * satisfies the merchant exclusion rules.
1027 + *
1028 + * @param int $product_id The ID of the product to check.
1029 + * @param array $offer The offer data to evaluate against exclusion rules.
1030 + *
1031 + * @return bool True if the product or any of its variations is excluded, false otherwise.
1032 + */
1033 +if ( ! function_exists( 'merchant_is_product_or_variation_excluded' ) ) {
1034 + function merchant_is_product_or_variation_excluded( $product_id, $offer = array() ) {
1035 + $product = wc_get_product( $product_id );
1036 +
1037 + if ( ! $product ) {
1038 + return false;
1039 + }
1040 +
1041 + // Get IDs to check for exclusion
1042 + $product_ids = $product->is_type( 'variable' ) ? $product->get_children() : array( $product_id );
1043 +
1044 + foreach ( $product_ids as $id ) {
1045 + if ( merchant_is_product_excluded( $id, $offer ) ) {
1046 + return true; // Excluded if any ID matches
1047 + }
1048 + }
1049 +
1050 + return false;
1051 + }
1052 +}
1053 +
1054 +/**
1055 + * Get the label of the first active payment gateway in WooCommerce.
1056 + *
1057 + * @return string|null The label of the first active payment gateway, or null if none are found.
1058 + */
1059 +if ( ! function_exists( 'merchant_get_first_active_payment_gateway_label' ) ) {
1060 + function merchant_get_first_active_payment_gateway_label() {
1061 + // Get the available payment gateways
1062 + $available_gateways = WC()->payment_gateways->get_available_payment_gateways();
1063 +
1064 + // Check if there are any active gateways
1065 + if ( ! empty( $available_gateways ) ) {
1066 + // Get the first active gateway
1067 + return reset( $available_gateways )->get_title();
1068 + }
1069 +
1070 + // Return null if no active gateways are found
1071 + return null;
1072 + }
1073 +}
1074 +
1075 +/**
1076 + * Get the review count of a product.
1077 + *
1078 + * @param int $product_id The ID of the product to get the review count for.
1079 + *
1080 + * @return int The review count of the product.
1081 + */
1082 +if ( ! function_exists( 'merchant_get_product_reviews_count' ) ) {
1083 + function merchant_get_product_reviews_count( $product_id ) {
1084 + $product = wc_get_product( $product_id );
1085 +
1086 + return $product ? $product->get_review_count() : 0;
1087 + }
1088 +}
1089 +
1090 +if ( ! function_exists( 'merchant_get_modules_campaign_data' ) ) {
1091 + /**
1092 + * Retrieves campaign data for a specific module.
1093 + *
1094 + * This function fetches campaign data based on the provided module ID and processes it
1095 + * into a standardized format. The campaigns are indexed by their `flexible_id` (expected to be a UUID v4)
1096 + * and include details such as ID, title, and status.
1097 + *
1098 + * @param string $module_id The ID of the module for which to retrieve campaign data.
1099 + * Expected values include constants like `Merchant_Buy_X_Get_Y::MODULE_ID`,
1100 + * `Merchant_Pre_Orders::MODULE_ID`, and `Merchant_Product_Labels::MODULE_ID`.
1101 + *
1102 + * @return array An associative array of campaigns, where each key is the `flexible_id` (UUID v4)
1103 + * of the campaign, and the value is an array containing the campaign's `id`, `title`,
1104 + * and `status`.
1105 + * Example:
1106 + * [
1107 + * '550e8400-e29b-41d4-a716-446655440000' => [
1108 + * 'id' => '550e8400-e29b-41d4-a716-446655440000',
1109 + * 'title' => 'Campaign Title',
1110 + * 'status' => 'active',
1111 + * ],
1112 + * 'f47ac10b-58cc-4372-a567-0e02b2c3d479' => [
1113 + * 'id' => 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
1114 + * 'title' => 'Another Campaign',
1115 + * 'status' => 'inactive',
1116 + * ],
1117 + * ]
1118 + */
1119 + function merchant_get_module_campaigns_data( $module_id ) {
1120 + $campaigns = array();
1121 +
1122 + // Determine the key to use based on the module ID
1123 + $key = '';
1124 + if ( Merchant_Buy_X_Get_Y::MODULE_ID === $module_id || Merchant_Pre_Orders::MODULE_ID === $module_id ) {
1125 + $key = 'rules';
1126 + } elseif ( Merchant_Product_Labels::MODULE_ID === $module_id ) {
1127 + $key = 'labels';
1128 + } else {
1129 + $key = 'offers';
1130 + }
1131 +
1132 + // Fetch the module campaigns
1133 + $module_campaigns = Merchant_Admin_Options::get( $module_id, $key, array() );
1134 + if ( empty( $module_campaigns ) ) {
1135 + return $campaigns;
1136 + }
1137 +
1138 + // Process each campaign
1139 + foreach ( $module_campaigns as $campaign ) {
1140 + if ( empty( $campaign['flexible_id'] ) ) {
1141 + continue;
1142 + }
1143 +
1144 + $campaigns[ $campaign['flexible_id'] ] = array(
1145 + 'campaign_key' => $key,
1146 + 'campaign_id' => $campaign['flexible_id'],
1147 + 'campaign_title' => $campaign['offer-title'] ?? $campaign['label-title'] ?? '',
1148 + 'campaign_status' => ( isset( $campaign['campaign_status'] ) && $campaign['campaign_status'] === 'inactive' ) ? 'inactive' : 'active',
1149 + );
1150 + }
1151 +
1152 + return $campaigns;
1153 + }
1154 +}
1155 +
1156 +if ( ! function_exists( 'merchant_get_modules_data' ) ) {
1157 + /**
1158 + * Get the data of all the modules.
1159 + *
1160 + * @return array
1161 + */
1162 + function merchant_get_modules_data() {
1163 + $modules = array();
1164 + $registered_modules = Merchant_Admin_Modules::get_modules();
1165 + foreach ( $registered_modules as $module_category => $module_data ) {
1166 + foreach ( $module_data['modules'] as $module_id => $module ) {
1167 + if ( ! empty( $module ) ) {
1168 + $modules[ $module_id ] = array(
1169 + 'id' => $module_id,
1170 + 'active' => Merchant_Modules::is_module_active( $module_id ),
1171 + 'name' => $module['title'],
1172 + 'desc' => $module['desc'],
1173 + 'category' => $module_category,
1174 + 'pro' => $module['pro'],
1175 + );
1176 +
1177 + $campaigns = merchant_get_module_campaigns_data( $module_id );
1178 +
1179 + if ( ! empty( $campaigns ) ) {
1180 + $modules[ $module_id ]['campaigns'] = $campaigns;
1181 + }
1182 + }
1183 + }
1184 + }
1185 +
1186 + return $modules;
1187 + }
1188 +}
1189 +
1190 +if ( ! function_exists( 'merchant_get_active_modules' ) ) {
1191 + /**
1192 + * Get the active modules.
1193 + *
1194 + * @return array
1195 + */
1196 + function merchant_get_active_modules() {
1197 + $modules = array_keys( merchant_get_modules_data() );
1198 +
1199 + return array_values( array_filter( $modules, static function ( $module_id ) {
1200 + return Merchant_Modules::is_module_active( $module_id );
1201 + } ) );
1202 + }
1203 +}
1204 +
1205 +if ( ! function_exists( 'merchant_get_campaign_data' ) ) {
1206 + /**
1207 + * Get the data of a specific campaign.
1208 + *
1209 + * @param string $campaign_id The ID of the campaign.
1210 + * @param string $module_id The ID of the module.
1211 + *
1212 + * @return array
1213 + */
1214 + function merchant_get_campaign_data( $campaign_id, $module_id ) {
1215 + if ( Merchant_Buy_X_Get_Y::MODULE_ID === $module_id || Merchant_Pre_Orders::MODULE_ID === $module_id ) {
1216 + $key = 'rules';
1217 + } elseif ( Merchant_Product_Labels::MODULE_ID === $module_id ) {
1218 + $key = 'labels';
1219 + } elseif ( 'buy-now' === $module_id ) {
1220 + $key = 'campaigns';
1221 + } else {
1222 + $key = 'offers';
1223 + }
1224 +
1225 + $all_modules = merchant_get_modules_data();
1226 + $module_campaigns = Merchant_Admin_Options::get( $module_id, $key, array() );
1227 + if ( ! empty( $module_campaigns ) ) {
1228 + foreach ( $module_campaigns as $campaign ) {
1229 + if ( isset( $all_modules[ $module_id ], $campaign['flexible_id'] ) && $campaign_id === $campaign['flexible_id'] ) {
1230 + return array(
1231 + 'campaign_id' => $campaign['flexible_id'],
1232 + 'campaign_title' => $campaign['offer-title'] ?? $campaign['label-title'] ?? '',
1233 + 'campaign_status' => $campaign['status'] ?? 'active',
1234 + 'module_id' => $module_id,
1235 + 'module_name' => $all_modules[ $module_id ]['name'],
1236 + 'is_pro' => $all_modules[ $module_id ]['pro'],
1237 + 'is_module_active' => $all_modules[ $module_id ]['active'],
1238 + );
1239 + }
1240 + }
1241 + }
1242 +
1243 + return array();
1244 + }
1245 +}