PluginProbe ʕ •ᴥ•ʔ
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments / 4.5.0
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments v4.5.0
4.5.1 4.5.0 4.4.2 4.4.1 4.4.0 4.3.3 4.3.2 4.3.1 4.3.0 4.2.3 4.2.2 4.2.1 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 1.1.9 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.11.0 1.11.1 1.11.2 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.7.0 1.7.1 1.7.2 1.8.0 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.9.0 1.9.1 1.9.2 1.9.3 1.9.4 1.9.5 2.0.0 2.0.1 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.10.0 2.10.1 2.11.0 2.11.1 2.11.2 2.11.3 2.11.4 2.12.0 2.13.0 2.14.0 2.14.1 2.15.0 2.15.1 2.16.0 2.16.1 2.16.2 2.16.3 2.17.0 2.17.1 2.17.2 2.18.0 2.19.0 2.19.2 2.19.3 2.19.4 2.2.0 2.2.1 2.20.0 2.20.1 2.20.2 2.20.3 2.20.4 2.20.5 2.20.6 2.21.0 2.22.0 2.22.1 2.23.0 2.24.0 2.25.0 2.25.1 2.25.2 2.26.0 2.27.0 2.27.1 2.28.0 2.29.0 2.29.1 2.29.2 2.29.3 2.29.4 2.3.0 2.3.1 2.30.0 2.31.0 2.31.1 2.31.2 2.31.3 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.40.0 2.40.1 2.5.0 2.5.1 2.5.2 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 2.9.0 3.0.0 3.0.0-RC1 3.0.0-RC2 3.0.0-beta1 3.0.0-beta2 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.10.0 3.10.1 3.11.0 3.12.0 3.13.0 3.13.1 3.13.2 3.13.3 3.13.4 3.14.0 3.15.0 3.15.1 3.15.2 3.15.3 3.15.4 3.15.5 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.16.5 3.16.6 3.16.7 3.16.8 3.17.0 3.17.1 3.17.2 3.17.3 3.17.4 3.17.5 3.17.6 3.18.0 3.19.0 3.19.1 3.19.2 3.2.0 3.2.1 3.2.2 3.20.0 3.20.1 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.4.3 3.5.0 3.5.1 3.5.2 3.5.3 3.6.0 3.6.1 3.6.2 3.7.0 3.7.1 3.7.2 3.7.3 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.9.0 4.0.0 4.0.1 4.0.2 4.0.3 trunk 4.1.0 0.2.19.1 4.1.1 1.0.0 4.2.0 1.0.1 1.0.2
surecart / app / src / Integrations / Bricks / BricksDynamicDataService.php
surecart / app / src / Integrations / Bricks Last commit date
Concerns 4 months ago Elements 2 months ago scripts 1 year ago BricksDynamicDataService.php 4 months ago BricksElementsService.php 7 months ago BricksServiceProvider.php 1 year ago BricksTemplateService.php 8 months ago
BricksDynamicDataService.php
749 lines
1 <?php
2
3 namespace SureCart\Integrations\Bricks;
4
5 use SureCart\Support\Currency;
6
7 /**
8 * This class handles bricks dynamic data functionality.
9 */
10 class BricksDynamicDataService {
11 /**
12 * Provider name.
13 *
14 * @var string
15 */
16 protected $name = 'surecart';
17
18 /**
19 * Bootstrap the service.
20 *
21 * @return void
22 */
23 public function bootstrap() {
24 add_action( 'bricks/dynamic_tags_list', [ $this, 'dynamicTags' ] );
25 add_filter( 'bricks/dynamic_data/render_tag', [ $this, 'getTheTagValue' ], 10, 2 );
26 add_filter( 'bricks/frontend/render_data', [ $this, 'render' ], 10, 3 );
27 add_filter( 'bricks/dynamic_data/render_content', [ $this, 'render' ], 10, 3 );
28
29 // make sure scripts load in footer.
30 if ( ! defined( 'ACSS_FLAG_LOAD_DASHBOARD_SCRIPTS_IN_FOOTER' ) ) {
31 define( 'ACSS_FLAG_LOAD_DASHBOARD_SCRIPTS_IN_FOOTER', true );
32 }
33 }
34
35 /**
36 * Check if we are in the admin editor.
37 *
38 * @return bool
39 */
40 public function is_admin_editor() {
41 return ! bricks_is_frontend() || bricks_is_builder_call();
42 }
43
44 /**
45 * Register Tags.
46 *
47 * @param array $tags Existing tags.
48 *
49 * @return array
50 */
51 public function dynamicTags( $tags = [] ) {
52 return array_merge(
53 $tags,
54 [
55 [
56 'slug' => 'sc_product_price',
57 'name' => '{sc_product_price}',
58 'label' => esc_html__( 'Product price', 'surecart' ),
59 'group' => esc_html__( 'SureCart Product', 'surecart' ),
60 ],
61 [
62 'slug' => 'sc_product_selected_price',
63 'name' => '{sc_product_selected_price}',
64 'label' => esc_html__( 'Product selected price', 'surecart' ),
65 'group' => esc_html__( 'SureCart Product', 'surecart' ),
66 ],
67 [
68 'slug' => 'sc_product_scratch_price',
69 'name' => '{sc_product_scratch_price}',
70 'label' => esc_html__( 'Product scratch price', 'surecart' ),
71 'group' => esc_html__( 'SureCart Product', 'surecart' ),
72 ],
73 [
74 'slug' => 'sc_product_selected_scratch_price',
75 'name' => '{sc_product_selected_scratch_price}',
76 'label' => esc_html__( 'Product selected scratch price', 'surecart' ),
77 'group' => esc_html__( 'SureCart Product', 'surecart' ),
78 ],
79 [
80 'slug' => 'sc_product_price_range',
81 'name' => '{sc_product_price_range}',
82 'label' => esc_html__( 'Product price range', 'surecart' ),
83 'group' => esc_html__( 'SureCart Product', 'surecart' ),
84 ],
85 [
86 'slug' => 'sc_product_description',
87 'name' => '{sc_product_description}',
88 'label' => esc_html__( 'Product description', 'surecart' ),
89 'group' => esc_html__( 'SureCart Product', 'surecart' ),
90 ],
91 [
92 'slug' => 'sc_product_stock',
93 'name' => '{sc_product_stock}',
94 'label' => esc_html__( 'Product stock', 'surecart' ),
95 'group' => esc_html__( 'SureCart Product', 'surecart' ),
96 ],
97 [
98 'slug' => 'sc_product_sku',
99 'name' => '{sc_product_sku}',
100 'label' => esc_html__( 'Product SKU', 'surecart' ),
101 'group' => esc_html__( 'SureCart Product', 'surecart' ),
102 ],
103 [
104 'slug' => 'sc_product_on_sale',
105 'name' => '{sc_product_on_sale}',
106 'label' => esc_html__( 'Product on sale', 'surecart' ),
107 'group' => esc_html__( 'SureCart Product', 'surecart' ),
108 ],
109 [
110 'slug' => 'sc_product_trial',
111 'name' => '{sc_product_trial}',
112 'label' => esc_html__( 'Product trial', 'surecart' ),
113 'group' => esc_html__( 'SureCart Product', 'surecart' ),
114 ],
115 [
116 'slug' => 'sc_product_selected_trial',
117 'name' => '{sc_product_selected_trial}',
118 'label' => esc_html__( 'Product selected price trial', 'surecart' ),
119 'group' => esc_html__( 'SureCart Product', 'surecart' ),
120 ],
121 [
122 'slug' => 'sc_product_billing_interval',
123 'name' => '{sc_product_billing_interval}',
124 'label' => esc_html__( 'Product billing interval', 'surecart' ),
125 'group' => esc_html__( 'SureCart Product', 'surecart' ),
126 ],
127 [
128 'slug' => 'sc_product_selected_billing_interval',
129 'name' => '{sc_product_selected_billing_interval}',
130 'label' => esc_html__( 'Product selected price billing interval', 'surecart' ),
131 'group' => esc_html__( 'SureCart Product', 'surecart' ),
132 ],
133 [
134 'slug' => 'sc_product_setup_fee',
135 'name' => '{sc_product_setup_fee}',
136 'label' => esc_html__( 'Product setup fee', 'surecart' ),
137 'group' => esc_html__( 'SureCart Product', 'surecart' ),
138 ],
139 [
140 'slug' => 'sc_product_selected_setup_fee',
141 'name' => '{sc_product_selected_setup_fee}',
142 'label' => esc_html__( 'Product selected price setup fee', 'surecart' ),
143 'group' => esc_html__( 'SureCart Product', 'surecart' ),
144 ],
145 [
146 'slug' => 'sc_price_name',
147 'name' => '{sc_price_name}',
148 'label' => esc_html__( 'Price name', 'surecart' ),
149 'group' => esc_html__( 'SureCart Product', 'surecart' ),
150 ],
151 [
152 'slug' => 'sc_price_amount',
153 'name' => '{sc_price_amount}',
154 'label' => esc_html__( 'Price amount', 'surecart' ),
155 'group' => esc_html__( 'SureCart Product', 'surecart' ),
156 ],
157 [
158 'slug' => 'sc_price_trial',
159 'name' => '{sc_price_trial}',
160 'label' => esc_html__( 'Price trial', 'surecart' ),
161 'group' => esc_html__( 'SureCart Product', 'surecart' ),
162 ],
163 [
164 'slug' => 'sc_price_setup_fee',
165 'name' => '{sc_price_setup_fee}',
166 'label' => esc_html__( 'Price setup fee', 'surecart' ),
167 'group' => esc_html__( 'SureCart Product', 'surecart' ),
168 ],
169 [
170 'slug' => 'sc_product_review_average_ratings',
171 'name' => '{sc_product_review_average_ratings}',
172 'label' => esc_html__( 'Product review average ratings', 'surecart' ),
173 'group' => esc_html__( 'SureCart Product', 'surecart' ),
174 ],
175 [
176 'slug' => 'sc_product_review_total_ratings',
177 'name' => '{sc_product_review_total_ratings}',
178 'label' => esc_html__( 'Product review total ratings', 'surecart' ),
179 'group' => esc_html__( 'SureCart Product', 'surecart' ),
180 ],
181 ]
182 );
183 }
184
185 /**
186 * Parse tag
187 *
188 * @param string $name Tag name.
189 * @param object $post Post object.
190 * @param array $filters Filters.
191 */
192 public function parseTag( $name, $post, $filters ) {
193 $post_id = isset( $post->ID ) ? $post->ID : '';
194 $product = $post_id ? sc_get_product( $post_id ) : false;
195
196 switch ( $name ) {
197 // static product price.
198 case 'product_price':
199 // Support ':value' filter to get the price value as a simple string (e.g.: 65.3, 2.5, 5 ).
200 if ( isset( $filters['value'] ) ) {
201 return esc_html( $product ? Currency::maybeConvertAmount( $product->initial_amount ) : '' );
202 }
203
204 // Support ':raw' filter to get the price value as a simple string (e.g.: 1250, 250, 5 ).
205 if ( isset( $filters['raw'] ) ) {
206 return esc_html( $product ? $product->initial_amount : '' );
207 }
208
209 // preview in the admin editor.
210 if ( $this->is_admin_editor() ) {
211 return ( $product->display_amount ?? Currency::format( 1200 ) );
212 }
213
214 return $product->display_amount;
215
216 // the dymamically selected price for the product.
217 case 'product_selected_price':
218 // preview in the admin editor.
219 if ( $this->is_admin_editor() ) {
220 return "<span class='wp-block-surecart-product-selected-price-amount'>" . ( $product->display_amount ?? Currency::format( 1200 ) ) . '</span>';
221 }
222
223 // IMPORTANT: Don't remove the trailing space or the block may break in some contexts.
224 return '<!-- wp:surecart/product-selected-price-amount --><!-- /wp:surecart/product-selected-price-amount --> ';
225
226 // the static price range.
227 case 'product_price_range':
228 return esc_html( $product ? $product->range_display_amount : '' );
229
230 // static product scratch price.
231 case 'product_scratch_price':
232 // Support ':value' filter to get the price value as a simple string (e.g.: 65.3, 2.5, 5 ).
233 if ( isset( $filters['value'] ) ) {
234 return esc_html( $product ? Currency::maybeConvertAmount( $product->scratch_amount ) : '' );
235 }
236
237 // Support ':raw' filter to get the price value as a simple string (e.g.: 1450, 250, 5 ).
238 if ( isset( $filters['raw'] ) ) {
239 return esc_html( $product ? $product->scratch_amount : '' );
240 }
241
242 // preview in the admin editor.
243 if ( $this->is_admin_editor() ) {
244 return ( $product->scratch_display_amount ?? Currency::format( 1400 ) );
245 }
246
247 return $product->scratch_display_amount;
248
249 // the dymamically selected scratch price for the product.
250 case 'product_selected_scratch_price':
251 // preview in the admin editor.
252 if ( $this->is_admin_editor() ) {
253 return "<span class='wp-block-surecart-product-selected-price-scratch-amount sc-price__amount'>" . ( $product->scratch_display_amount ?? '$14' ) . '</span>';
254 }
255
256 // IMPORTANT: Don't remove the trailing space or the block may break in some contexts.
257 return '<!-- wp:surecart/product-selected-price-scratch-amount --><!-- /wp:surecart/product-selected-price-scratch-amount --> ';
258
259 case 'product_description':
260 $clean_excerpt = trim( wp_strip_all_tags( $post->post_excerpt ) );
261 if ( empty( $clean_excerpt ) ) {
262 return '';
263 }
264 return '<span class="sc-prose">' . wp_kses_post( ! empty( $filters['num_words'] ) ? \Bricks\Helpers::get_the_excerpt( $post, $filters['num_words'], null, true ) : $post->post_excerpt ?? '' ) . '</span>';
265
266 case 'product_stock':
267 // unlimited stock, don't display stock.
268 if ( $product ? $product->has_unlimited_stock : '' ) {
269 return '';
270 }
271 if ( isset( $filters['meta_key'] ) && 'on_hand' === $filters['meta_key'] ) {
272 return esc_html( $product ? $product->stock : '' );
273 }
274 if ( isset( $filters['meta_key'] ) && 'held' === $filters['meta_key'] ) {
275 return esc_html( $product ? $product->held_stock : '' );
276 }
277 return esc_html( $product ? $product->available_stock : '' );
278
279 case 'product_sku':
280 return esc_html( $product ? $product->sku : '' );
281
282 case 'product_on_sale':
283 return esc_html( $product ? $product->is_on_sale : '' );
284
285 case 'product_trial':
286 if ( $this->is_admin_editor() ) {
287 return $product->trial_text ?? esc_html__( 'Starting in 15 days.', 'surecart' );
288 }
289 return $product->trial_text;
290
291 case 'product_selected_trial':
292 if ( $this->is_admin_editor() ) {
293 return "<span class='wp-block-surecart-product-selected-price-trial'>" . esc_html__( 'Starting in 15 days.', 'surecart' ) . '</span>';
294 }
295 // IMPORTANT: Don't remove the trailing space or the block may break in some contexts.
296 return '<!-- wp:surecart/product-selected-price-trial --><!-- /wp:surecart/product-selected-price-trial --> ';
297
298 case 'product_billing_interval':
299 if ( $this->is_admin_editor() ) {
300 return $product->billing_interval_text ?? esc_html__( '/ day (3 payments)', 'surecart' );
301 }
302 return $product->billing_interval_text ?? '';
303
304 case 'product_selected_billing_interval':
305 if ( $this->is_admin_editor() ) {
306 return "<span class='wp-block-surecart-product-selected-price-interval sc-price__amount'>" . esc_html__( '/ day (3 payments)', 'surecart' ) . '</span>';
307 }
308 // IMPORTANT: Don't remove the trailing space or the block may break in some contexts.
309 return '<!-- wp:surecart/product-selected-price-interval --><!-- /wp:surecart/product-selected-price-interval --> ';
310
311 case 'product_setup_fee':
312 if ( $this->is_admin_editor() ) {
313 // translators: %s: Setup Fee amount.
314 return $product->setup_fee_text ?? esc_html( sprintf( __( '%s setup fee.', 'surecart' ), Currency::format( 100 ) ) );
315 }
316 // IMPORTANT: Don't remove the trailing space or the block may break in some contexts.
317 return $product->setup_fee_text ?? '';
318
319 case 'product_selected_setup_fee':
320 if ( $this->is_admin_editor() ) {
321 // translators: %s: Setup Fee amount.
322 return "<span class='wp-block-surecart-product-selected-price-fees'>" . esc_html( sprintf( __( '%s setup fee.', 'surecart' ), Currency::format( 100 ) ) ) . '</span>';
323 }
324 // IMPORTANT: Don't remove the trailing space or the block may break in some contexts.
325 return '<!-- wp:surecart/product-selected-price-fees --><!-- /wp:surecart/product-selected-price-fees --> ';
326
327 case 'price_name':
328 if ( $this->is_admin_editor() ) {
329 // translators: %s: Setup Fee amount.
330 return "<span class='wp-block-surecart-price-name'>" . esc_html__( 'Price name', 'surecart' ) . '</span>';
331 }
332 // IMPORTANT: Don't remove the trailing space or the block may break in some contexts.
333 return '<!-- wp:surecart/price-name --><!-- /wp:surecart/price-name --> ';
334
335 case 'price_amount':
336 if ( $this->is_admin_editor() ) {
337 // translators: %s: Setup Fee amount.
338 return "<span class='wp-block-surecart-price-amount'>" . sprintf( esc_attr__( '%1$s %2$s', 'surecart' ), Currency::format( 200 ), '/mo' ) . '</span>';
339 }
340 // IMPORTANT: Don't remove the trailing space or the block may break in some contexts.
341 return '<!-- wp:surecart/price-amount --><!-- /wp:surecart/price-amount --> ';
342
343 case 'price_trial':
344 if ( $this->is_admin_editor() ) {
345 // translators: %s: Setup Fee amount.
346 return "<span class='wp-block-surecart-product-price-trial'>" . esc_html__( 'Starting in 15 days', 'surecart' ) . '</span>';
347 }
348 // IMPORTANT: Don't remove the trailing space or the block may break in some contexts.
349 return '<!-- wp:surecart/price-trial --><!-- /wp:surecart/price-trial --> ';
350
351 case 'price_setup_fee':
352 if ( $this->is_admin_editor() ) {
353 // translators: %s: Setup Fee amount.
354 return "<span class='wp-block-surecart-product-price-setup-fee'>" . esc_html__( '$12 Signup Fee', 'surecart' ) . '</span>';
355 }
356 // IMPORTANT: Don't remove the trailing space or the block may break in some contexts.
357 return '<!-- wp:surecart/price-setup-fee --><!-- /wp:surecart/price-setup-fee --> ';
358
359 case 'product_review_average_ratings':
360 if ( $this->is_admin_editor() ) {
361 return '4.5';
362 }
363 return esc_html( $product ? $product->average_stars : '' );
364
365 case 'product_review_total_ratings':
366 if ( $this->is_admin_editor() ) {
367 return '125';
368 }
369 return esc_html( $product ? $product->total_reviews : '' );
370 }
371
372 return $name;
373 }
374
375 /**
376 * Dynamic tag exists in $content: Replaces dynamic tag with requested data.
377 *
378 * @param string $content Content.
379 * @param WP_Post $post Post object.
380 * @param string $context Context.
381 *
382 * @return string
383 */
384 public function render( $content, $post, $context = 'text' ) {
385 /**
386 * \w: Matches any word character (alphanumeric & underscore).
387 * Equivalent to [A-Za-z0-9_]
388 * "À-ÖØ-öø-ÿ" Add the accented characters
389 * "-" Needed because some post types handles are like "my-post-type"
390 * ":" Needed for extra arguments to dynamic data tags (e.g. post_excerpt:20 or wp_user_meta:my_meta_key)
391 * "|" and "," needed for the post terms like {post_terms_post_tag:sep} where sep could be a pipe or comma
392 * "(", ")" and "'" for the function arguments of the dynamic tag {echo}
393 * "@" to support email addresses as arguments of the dynamic tag {echo} #3kazphp
394 *
395 * @since 1.9.4: "u" modifier: Pattern strings are treated as UTF-8 to support Cyrillic, Arabic, etc.
396 * @since 1.10: "$", "+", "%", "#", "!", "=", "<", ">", "&", "~", "[", "]", ";" as arguments of the dynamic tag {echo}
397 *
398 * @see https://regexr.com/
399 */
400 $pattern = '/{([\wÀ-ÖØ-öø-ÿ\-\s\.\/:\(\)\'@|,$%#!+=<>&~\[\];]+)}/u';
401
402 /**
403 * Matches the echo tag pattern (#86bwebj6m)
404 *
405 * @since 1.9.8
406 */
407 $echo_pattern = '/echo:([a-zA-Z0-9_]+)/';
408
409 // Get a list of tags to exclude from the Dynamic Data logic.
410 $exclude_tags = apply_filters( 'bricks/dynamic_data/exclude_tags', [] );
411
412 /**
413 * STEP: Determine how many times we need to run the DD parser
414 *
415 * Previously we ran the parser by counting the number of open curly braces in the content. (@since 1.8)
416 * But this is not reliable because the content could contain curly braces in the code elements or any shortcodes.
417 * Causing the website to load extremely slow.
418 *
419 * @since 1.8.2 (#862jyyryg)
420 */
421 // Get all registered tags except the excluded ones.
422 // Example: [0 => "post_title", 1 => "woo_product_price", 2 => "echo"].
423 $registered_tags = array_filter(
424 array_column( $this->dynamicTags(), 'slug' ),
425 function ( $tag ) use ( $exclude_tags ) {
426 return ! in_array( $tag, $exclude_tags, true );
427 }
428 );
429
430 $dd_tags_in_content = [];
431 $dd_tags_found = [];
432 $echo_tags_found = [];
433
434 // Find all dynamic data tags in the content.
435 preg_match_all( $pattern, $content, $dd_tags_in_content );
436
437 $dd_tags_in_content = ! empty( $dd_tags_in_content[1] ) ? $dd_tags_in_content[1] : [];
438
439 // Find all echo tags in the content.
440 preg_match_all( $echo_pattern, $content, $echo_tags_found );
441
442 // Combine the dynamic data tags from the content and the echo tags.
443 if ( ! empty( $echo_tags_found[0] ) ) {
444 $dd_tags_in_content = array_merge( $dd_tags_in_content, $echo_tags_found[0] );
445 }
446
447 if ( ! empty( $dd_tags_in_content ) ) {
448 /**
449 * $dd_tags_in_content only matches the pattern, but some codes from Code element could match the pattern too.
450 * Example: function test() { return 'Hello World'; } will match the pattern, but it's not a dynamic data tag.
451 *
452 * Find all dynamic data tags in the content which starts with dynamic data tag from $registered_tags
453 * Cannot use array_in or array_intersect because $registered_tags only contains the tag name, somemore tags could have filters like {echo:my_function( 'Hello World' )
454 *
455 * Example: $registered_tags = [0 => "post_title", 1 => "woo_product_price", 2 => "echo"]
456 * Example: $dd_tags_in_content = [0 => "post_title", 1 => "woo_product_price:value", 2 => "echo:my_function('Hello World')"]
457 */
458 $dd_tags_found = array_filter(
459 $dd_tags_in_content,
460 function ( $tag ) use ( $registered_tags ) {
461 foreach ( $registered_tags as $all_tag ) {
462 /**
463 * Skip not our tags.
464 */
465 if ( strpos( $tag, 'sc_' ) === 0 ) {
466 return true;
467 }
468
469 if ( strpos( $tag, $all_tag ) === 0 ) {
470 return true;
471 }
472 }
473 return false;
474 }
475 );
476 }
477
478 // Get the count of found dynamic data tags.
479 $dd_tag_count = count( $dd_tags_found );
480
481 // STEP: Run the parser based on the count of found dynamic data tags.
482 for ( $i = 0; $i < $dd_tag_count; $i++ ) {
483 preg_match_all( $pattern, $content, $matches );
484
485 if ( empty( $matches[0] ) ) {
486 return $content;
487 }
488
489 $run_again = false;
490
491 foreach ( $matches[1] as $key => $match ) {
492 $tag = $matches[0][ $key ];
493
494 if ( in_array( $match, $exclude_tags, true ) ) {
495 continue;
496 }
497
498 $value = $this->getTheTagValue( $match, $post, $context );
499
500 // Value is a WP_Error: Set value to false to avoid error in builder.
501 if ( is_a( $value, 'WP_Error' ) ) {
502 $value = false;
503 }
504
505 $content = str_replace( $tag, $value, $content );
506 }
507
508 if ( $run_again ) {
509 ++$dd_tag_count;
510 }
511 }
512
513 return $content;
514 }
515
516 /**
517 * Main function to render the tag value for WooCommerce provider
518 *
519 * @param string $tag Tag name.
520 * @param object $post Post object.
521 *
522 * @return string
523 */
524 public function getTheTagValue( $tag, $post ) {
525 $tags = $this->dynamicTags();
526
527 // Check if the tag exists in the registered tags.
528 $tag_exists = array_search( strtok( $tag, ':' ), array_column( $tags, 'slug' ), true );
529 if ( false === $tag_exists ) {
530 return $tag;
531 }
532
533 // parse tag and args.
534 $parsed = is_string( $tag ) ? $this->parse_tag_and_args( $tag ) : [
535 'tag' => $tag,
536 'args' => [],
537 ];
538 $tag = $parsed['tag'];
539 $args = $parsed['args'];
540
541 // Check for filter args.
542 $filters = $this->get_filters_from_args( $args );
543
544 // Get the tag name.
545 $name = isset( $tags[ $tag ]['render'] ) ? $tags[ $tag ]['render'] : str_replace( 'sc_', '', $tag );
546
547 return $this->parseTag( $name, $post, $filters );
548 }
549
550 /**
551 * Parse the tag and extract arguments
552 *
553 * @param string $tag The original tag string.
554 * @return array An array containing the parsed tag and arguments.
555 */
556 private function parse_tag_and_args( $tag ) {
557 $original_tag = $tag;
558 $args = [];
559
560 // Special case to allow using "@" as "echo:" tag parameter.
561 // TODO NEXT: More rebust DD argument parser (@see #86bzunbgf)
562 if ( strpos( $tag, 'echo:' ) === 0 ) {
563 return [
564 'tag' => 'echo',
565 'args' => [ substr( $tag, 5 ) ], // Everything after 'echo:'
566 'original_tag' => $original_tag,
567 ];
568 }
569
570 // Check if tag has ':' or '@' indicating it has arguments
571 if ( strpos( $tag, ':' ) !== false || strpos( $tag, '@' ) !== false ) {
572 // If there's a ':' before the first '@', split at the first ':'
573 if ( strpos( $tag, ':' ) !== false && ( strpos( $tag, '@' ) === false || strpos( $tag, ':' ) < strpos( $tag, '@' ) ) ) {
574 list($tag, $args_string) = explode( ':', $tag, 2 );
575 } else {
576 // If there's no ':' before the first '@', the tag is before the '@'
577 $args_string = $tag;
578 $tag = strtok( $tag, ' @' );
579 }
580
581 // Check if the args_string contains key-value pairs marked with '@'
582 if ( strpos( $args_string, '@' ) !== false ) {
583 list($standard_args, $kv_args_string) = explode( '@', $args_string, 2 );
584
585 // Add standard arguments to the args array
586 if ( ! empty( $standard_args ) ) {
587 $standard_args_array = explode( ':', $standard_args );
588 foreach ( $standard_args_array as $arg ) {
589 $args[] = trim( $arg );
590 }
591 }
592
593 // Split the key-value pairs
594 $kv_pairs = explode( '@', $kv_args_string );
595 foreach ( $kv_pairs as $pair ) {
596 list($key, $value) = explode( ':', $pair, 2 );
597 $args[ trim( $key ) ] = trim( $value );
598 }
599 } else {
600 // No key-value pairs, just standard arguments
601 $standard_args_array = explode( ':', $args_string );
602 foreach ( $standard_args_array as $arg ) {
603 $args[] = trim( $arg );
604 }
605 }
606 }
607
608 return [
609 'tag' => $tag,
610 'args' => $args,
611 'original_tag' => $original_tag,
612 ];
613 }
614
615 /**
616 * Calculate dynamic data filters according to the args received
617 *
618 * @param array $args
619 * @return array
620 */
621 public function get_filters_from_args( $args ) {
622 $filters = [
623 'object_type' => '',
624 ];
625
626 if ( empty( $args ) || ! is_array( $args ) ) {
627 return $filters;
628 }
629
630 foreach ( $args as $arg ) {
631 // Trim number of words or avatar size (in px)
632 if ( is_numeric( $arg ) ) {
633 $filters['num_words'] = $arg;
634 }
635
636 // Add context to the archive title
637 elseif ( $arg == 'context' || $arg == 'prefix' ) {
638 $filters['add_context'] = true;
639 }
640
641 // Output as image tag
642 elseif ( $arg == 'image' ) {
643 $filters['image'] = true;
644 }
645
646 // Wrap value in a link
647 elseif ( $arg == 'link' ) {
648 $filters['link'] = true;
649 }
650
651 // Open link in newTab
652 elseif ( $arg == 'newTab' ) {
653 $filters['newTab'] = true;
654 }
655
656 // Create a callable link
657 elseif ( $arg == 'tel' ) {
658 $filters['tel'] = true;
659 }
660
661 /**
662 * Return value instead of label
663 *
664 * Useful for dynamic data element conditions like MB checkbox_list, ACF true_false, etc. where the user can specify the value & label.
665 *
666 * @since 1.5.7
667 */
668 elseif ( $arg == 'value' ) {
669 $filters['value'] = true;
670 }
671
672 /**
673 * Return raw value (skip parsing DD tag)
674 *
675 * Useful to skip rendering one specific DD tag
676 *
677 * @since 1.6
678 */
679 elseif ( $arg == 'raw' ) {
680 $filters['raw'] = true;
681 }
682
683 /**
684 * Return URL
685 *
686 * Useful for field type 'file'
687 *
688 * NOTE: Undocumented
689 *
690 * @since 1.6
691 */
692 elseif ( $arg == 'url' ) {
693 $filters['url'] = true;
694 }
695
696 /**
697 * Keep formatting
698 *
699 * Useful for dynamic data with HTML
700 *
701 * Example: {post_excerpt:format}
702 *
703 * @since 1.6.2
704 */
705 elseif ( $arg == 'format' ) {
706 $filters['format'] = true;
707 }
708
709 /**
710 * Return plain text
711 *
712 * Strip HTML tags
713 *
714 * Example: {post_terms_category:plain}
715 *
716 * @since 1.7.2
717 */
718 elseif ( $arg == 'plain' ) {
719 $filters['plain'] = true;
720 }
721
722 /**
723 * Return array value
724 *
725 * Useful for dynamic data with array
726 *
727 * Example : {acf_link_field:array_value|title}
728 *
729 * @since 1.8
730 */
731 elseif ( strpos( $arg, 'array_value|' ) === 0 ) {
732 $filters['array_value'] = str_replace( 'array_value|', '', $arg );
733 }
734
735 // Default key: used for 1) user meta_key, 2) post terms separator or 3) image size, 4) date format
736 else {
737 $filters['meta_key'][] = $arg;
738 }
739 }
740
741 // Note: Use case where the date format contains a colon. E.g. "{post_date:jS F Y h:ia}"
742 if ( isset( $filters['meta_key'] ) ) {
743 $filters['meta_key'] = implode( ':', $filters['meta_key'] );
744 }
745
746 return $filters;
747 }
748 }
749