PluginProbe ʕ •ᴥ•ʔ
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments / 3.0.0-beta2
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments v3.0.0-beta2
4.7.0 4.6.6 4.6.5 4.6.4 4.6.3 4.6.2 4.6.1 4.6.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 1 year ago Elements 1 year ago scripts 1 year ago BricksDynamicDataService.php 1 year ago BricksElementsService.php 1 year ago BricksServiceProvider.php 1 year ago
BricksDynamicDataService.php
734 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 );
171 }
172
173 /**
174 * Parse tag
175 *
176 * @param string $name Tag name.
177 * @param object $post Post object.
178 * @param array $filters Filters.
179 */
180 public function parseTag( $name, $post, $filters ) {
181 $post_id = isset( $post->ID ) ? $post->ID : '';
182 $product = $post_id ? sc_get_product( $post_id ) : false;
183
184 switch ( $name ) {
185 // static product price.
186 case 'product_price':
187 // Support ':value' filter to get the price value as a simple string (e.g.: 65.3, 2.5, 5 ).
188 if ( isset( $filters['value'] ) ) {
189 return esc_html( $product ? Currency::maybeConvertAmount( $product->initial_amount ) : '' );
190 }
191
192 // Support ':raw' filter to get the price value as a simple string (e.g.: 1250, 250, 5 ).
193 if ( isset( $filters['raw'] ) ) {
194 return esc_html( $product ? $product->initial_amount : '' );
195 }
196
197 // preview in the admin editor.
198 if ( $this->is_admin_editor() ) {
199 return ( $product->display_amount ?? Currency::format( 1200 ) );
200 }
201
202 return $product->display_amount;
203
204 // the dymamically selected price for the product.
205 case 'product_selected_price':
206 // preview in the admin editor.
207 if ( $this->is_admin_editor() ) {
208 return "<span class='wp-block-surecart-product-selected-price-amount'>" . ( $product->display_amount ?? Currency::format( 1200 ) ) . '</span>';
209 }
210
211 // IMPORTANT: Don't remove the trailing space or the block may break in some contexts.
212 return '<!-- wp:surecart/product-selected-price-amount --><!-- /wp:surecart/product-selected-price-amount --> ';
213
214 // the static price range.
215 case 'product_price_range':
216 return esc_html( $product ? $product->range_display_amount : '' );
217
218 // static product scratch price.
219 case 'product_scratch_price':
220 // Support ':value' filter to get the price value as a simple string (e.g.: 65.3, 2.5, 5 ).
221 if ( isset( $filters['value'] ) ) {
222 return esc_html( $product ? Currency::maybeConvertAmount( $product->scratch_amount ) : '' );
223 }
224
225 // Support ':raw' filter to get the price value as a simple string (e.g.: 1450, 250, 5 ).
226 if ( isset( $filters['raw'] ) ) {
227 return esc_html( $product ? $product->scratch_amount : '' );
228 }
229
230 // preview in the admin editor.
231 if ( $this->is_admin_editor() ) {
232 return ( $product->scratch_display_amount ?? Currency::format( 1400 ) );
233 }
234
235 return $product->scratch_display_amount;
236
237 // the dymamically selected scratch price for the product.
238 case 'product_selected_scratch_price':
239 // preview in the admin editor.
240 if ( $this->is_admin_editor() ) {
241 return "<span class='wp-block-surecart-product-selected-price-scratch-amount sc-price__amount'>" . ( $product->scratch_display_amount ?? '$14' ) . '</span>';
242 }
243
244 // IMPORTANT: Don't remove the trailing space or the block may break in some contexts.
245 return '<!-- wp:surecart/product-selected-price-scratch-amount --><!-- /wp:surecart/product-selected-price-scratch-amount --> ';
246
247 case 'product_description':
248 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>';
249
250 case 'product_stock':
251 // unlimited stock, don't display stock.
252 if ( $product ? $product->has_unlimited_stock : '' ) {
253 return '';
254 }
255 if ( isset( $filters['on_hand'] ) ) {
256 return esc_html( $product ? $product->stock : '' );
257 }
258 if ( isset( $filters['held_stock'] ) ) {
259 return esc_html( $product ? $product->held_stock : '' );
260 }
261 return esc_html( $product ? $product->available_stock : '' );
262
263 case 'product_sku':
264 return esc_html( $product ? $product->sku : '' );
265
266 case 'product_on_sale':
267 return esc_html( $product ? $product->is_on_sale : '' );
268
269 case 'product_trial':
270 if ( $this->is_admin_editor() ) {
271 return $product->trial_text ?? esc_html__( 'Starting in 15 days.', 'surecart' );
272 }
273 return $product->trial_text;
274
275 case 'product_selected_trial':
276 if ( $this->is_admin_editor() ) {
277 return "<span class='wp-block-surecart-product-selected-price-trial'>" . esc_html__( 'Starting in 15 days.', 'surecart' ) . '</span>';
278 }
279 // IMPORTANT: Don't remove the trailing space or the block may break in some contexts.
280 return '<!-- wp:surecart/product-selected-price-trial --><!-- /wp:surecart/product-selected-price-trial --> ';
281
282 case 'product_billing_interval':
283 if ( $this->is_admin_editor() ) {
284 return $product->billing_interval_text ?? esc_html__( '/ day (3 payments)', 'surecart' );
285 }
286 return $product->billing_interval_text ?? '';
287
288 case 'product_selected_billing_interval':
289 if ( $this->is_admin_editor() ) {
290 return "<span class='wp-block-surecart-product-selected-price-interval sc-price__amount'>" . esc_html__( '/ day (3 payments)', 'surecart' ) . '</span>';
291 }
292 // IMPORTANT: Don't remove the trailing space or the block may break in some contexts.
293 return '<!-- wp:surecart/product-selected-price-interval --><!-- /wp:surecart/product-selected-price-interval --> ';
294
295 case 'product_setup_fee':
296 if ( $this->is_admin_editor() ) {
297 // translators: %s: Setup Fee amount.
298 return $product->setup_fee_text ?? esc_html( sprintf( __( '%s setup fee.', 'surecart' ), Currency::format( 100 ) ) );
299 }
300 // IMPORTANT: Don't remove the trailing space or the block may break in some contexts.
301 return $product->setup_fee_text ?? '';
302
303 case 'product_selected_setup_fee':
304 if ( $this->is_admin_editor() ) {
305 // translators: %s: Setup Fee amount.
306 return "<span class='wp-block-surecart-product-selected-price-fees'>" . esc_html( sprintf( __( '%s setup fee.', 'surecart' ), Currency::format( 100 ) ) ) . '</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-fees --><!-- /wp:surecart/product-selected-price-fees --> ';
310
311 case 'product_stock':
312 // Support ':held filter to get the price value as a simple string (e.g.: 65.3, 2.5, 5 ).
313 if ( isset( $filters['held'] ) ) {
314 return $product->held_stock;
315 }
316
317 // Support ':on_hand' filter to get the price value as a simple string (e.g.: 1450, 250, 5 ).
318 if ( isset( $filters['on_hand'] ) ) {
319 return $product->stock;
320 }
321
322 return $product->available_stock;
323
324 case 'price_name':
325 if ( $this->is_admin_editor() ) {
326 // translators: %s: Setup Fee amount.
327 return "<span class='wp-block-surecart-price-name'>" . esc_html__( 'Price name', 'surecart' ) . '</span>';
328 }
329 // IMPORTANT: Don't remove the trailing space or the block may break in some contexts.
330 return '<!-- wp:surecart/price-name --><!-- /wp:surecart/price-name --> ';
331
332 case 'price_amount':
333 if ( $this->is_admin_editor() ) {
334 // translators: %s: Setup Fee amount.
335 return "<span class='wp-block-surecart-price-amount'>" . sprintf( esc_attr__( '%1$s %2$s', 'surecart' ), Currency::format( 200 ), '/mo' ) . '</span>';
336 }
337 // IMPORTANT: Don't remove the trailing space or the block may break in some contexts.
338 return '<!-- wp:surecart/price-amount --><!-- /wp:surecart/price-amount --> ';
339
340 case 'price_trial':
341 if ( $this->is_admin_editor() ) {
342 // translators: %s: Setup Fee amount.
343 return "<span class='wp-block-surecart-product-price-trial'>" . esc_html__( 'Starting in 15 days', 'surecart' ) . '</span>';
344 }
345 // IMPORTANT: Don't remove the trailing space or the block may break in some contexts.
346 return '<!-- wp:surecart/price-trial --><!-- /wp:surecart/price-trial --> ';
347
348 case 'price_setup_fee':
349 if ( $this->is_admin_editor() ) {
350 // translators: %s: Setup Fee amount.
351 return "<span class='wp-block-surecart-product-price-setup-fee'>" . esc_html__( '$12 Signup Fee', 'surecart' ) . '</span>';
352 }
353 // IMPORTANT: Don't remove the trailing space or the block may break in some contexts.
354 return '<!-- wp:surecart/price-setup-fee --><!-- /wp:surecart/price-setup-fee --> ';
355 }
356
357 return $name;
358 }
359
360 /**
361 * Dynamic tag exists in $content: Replaces dynamic tag with requested data.
362 *
363 * @param string $content Content.
364 * @param WP_Post $post Post object.
365 * @param string $context Context.
366 *
367 * @return string
368 */
369 public function render( $content, $post, $context = 'text' ) {
370 /**
371 * \w: Matches any word character (alphanumeric & underscore).
372 * Equivalent to [A-Za-z0-9_]
373 * "À-ÖØ-öø-ÿ" Add the accented characters
374 * "-" Needed because some post types handles are like "my-post-type"
375 * ":" Needed for extra arguments to dynamic data tags (e.g. post_excerpt:20 or wp_user_meta:my_meta_key)
376 * "|" and "," needed for the post terms like {post_terms_post_tag:sep} where sep could be a pipe or comma
377 * "(", ")" and "'" for the function arguments of the dynamic tag {echo}
378 * "@" to support email addresses as arguments of the dynamic tag {echo} #3kazphp
379 *
380 * @since 1.9.4: "u" modifier: Pattern strings are treated as UTF-8 to support Cyrillic, Arabic, etc.
381 * @since 1.10: "$", "+", "%", "#", "!", "=", "<", ">", "&", "~", "[", "]", ";" as arguments of the dynamic tag {echo}
382 *
383 * @see https://regexr.com/
384 */
385 $pattern = '/{([\wÀ-ÖØ-öø-ÿ\-\s\.\/:\(\)\'@|,$%#!+=<>&~\[\];]+)}/u';
386
387 /**
388 * Matches the echo tag pattern (#86bwebj6m)
389 *
390 * @since 1.9.8
391 */
392 $echo_pattern = '/echo:([a-zA-Z0-9_]+)/';
393
394 // Get a list of tags to exclude from the Dynamic Data logic.
395 $exclude_tags = apply_filters( 'bricks/dynamic_data/exclude_tags', [] );
396
397 /**
398 * STEP: Determine how many times we need to run the DD parser
399 *
400 * Previously we ran the parser by counting the number of open curly braces in the content. (@since 1.8)
401 * But this is not reliable because the content could contain curly braces in the code elements or any shortcodes.
402 * Causing the website to load extremely slow.
403 *
404 * @since 1.8.2 (#862jyyryg)
405 */
406 // Get all registered tags except the excluded ones.
407 // Example: [0 => "post_title", 1 => "woo_product_price", 2 => "echo"].
408 $registered_tags = array_filter(
409 array_column( $this->dynamicTags(), 'slug' ),
410 function ( $tag ) use ( $exclude_tags ) {
411 return ! in_array( $tag, $exclude_tags, true );
412 }
413 );
414
415 $dd_tags_in_content = [];
416 $dd_tags_found = [];
417 $echo_tags_found = [];
418
419 // Find all dynamic data tags in the content.
420 preg_match_all( $pattern, $content, $dd_tags_in_content );
421
422 $dd_tags_in_content = ! empty( $dd_tags_in_content[1] ) ? $dd_tags_in_content[1] : [];
423
424 // Find all echo tags in the content.
425 preg_match_all( $echo_pattern, $content, $echo_tags_found );
426
427 // Combine the dynamic data tags from the content and the echo tags.
428 if ( ! empty( $echo_tags_found[0] ) ) {
429 $dd_tags_in_content = array_merge( $dd_tags_in_content, $echo_tags_found[0] );
430 }
431
432 if ( ! empty( $dd_tags_in_content ) ) {
433 /**
434 * $dd_tags_in_content only matches the pattern, but some codes from Code element could match the pattern too.
435 * Example: function test() { return 'Hello World'; } will match the pattern, but it's not a dynamic data tag.
436 *
437 * Find all dynamic data tags in the content which starts with dynamic data tag from $registered_tags
438 * 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' )
439 *
440 * Example: $registered_tags = [0 => "post_title", 1 => "woo_product_price", 2 => "echo"]
441 * Example: $dd_tags_in_content = [0 => "post_title", 1 => "woo_product_price:value", 2 => "echo:my_function('Hello World')"]
442 */
443 $dd_tags_found = array_filter(
444 $dd_tags_in_content,
445 function ( $tag ) use ( $registered_tags ) {
446 foreach ( $registered_tags as $all_tag ) {
447 /**
448 * Skip not our tags.
449 */
450 if ( strpos( $tag, 'sc_' ) === 0 ) {
451 return true;
452 }
453
454 if ( strpos( $tag, $all_tag ) === 0 ) {
455 return true;
456 }
457 }
458 return false;
459 }
460 );
461 }
462
463 // Get the count of found dynamic data tags.
464 $dd_tag_count = count( $dd_tags_found );
465
466 // STEP: Run the parser based on the count of found dynamic data tags.
467 for ( $i = 0; $i < $dd_tag_count; $i++ ) {
468 preg_match_all( $pattern, $content, $matches );
469
470 if ( empty( $matches[0] ) ) {
471 return $content;
472 }
473
474 $run_again = false;
475
476 foreach ( $matches[1] as $key => $match ) {
477 $tag = $matches[0][ $key ];
478
479 if ( in_array( $match, $exclude_tags, true ) ) {
480 continue;
481 }
482
483 $value = $this->getTheTagValue( $match, $post, $context );
484
485 // Value is a WP_Error: Set value to false to avoid error in builder.
486 if ( is_a( $value, 'WP_Error' ) ) {
487 $value = false;
488 }
489
490 $content = str_replace( $tag, $value, $content );
491 }
492
493 if ( $run_again ) {
494 ++$dd_tag_count;
495 }
496 }
497
498 return $content;
499 }
500
501 /**
502 * Main function to render the tag value for WooCommerce provider
503 *
504 * @param string $tag Tag name.
505 * @param object $post Post object.
506 *
507 * @return string
508 */
509 public function getTheTagValue( $tag, $post ) {
510 $tags = $this->dynamicTags();
511
512 // Check if the tag exists in the registered tags.
513 $tag_exists = array_search( strtok( $tag, ':' ), array_column( $tags, 'slug' ), true );
514 if ( false === $tag_exists ) {
515 return $tag;
516 }
517
518 // parse tag and args.
519 $parsed = is_string( $tag ) ? $this->parse_tag_and_args( $tag ) : [
520 'tag' => $tag,
521 'args' => [],
522 ];
523 $tag = $parsed['tag'];
524 $args = $parsed['args'];
525
526 // Check for filter args.
527 $filters = $this->get_filters_from_args( $args );
528
529 // Get the tag name.
530 $name = isset( $tags[ $tag ]['render'] ) ? $tags[ $tag ]['render'] : str_replace( 'sc_', '', $tag );
531
532 return $this->parseTag( $name, $post, $filters );
533 }
534
535 /**
536 * Parse the tag and extract arguments
537 *
538 * @param string $tag The original tag string.
539 * @return array An array containing the parsed tag and arguments.
540 */
541 private function parse_tag_and_args( $tag ) {
542 $original_tag = $tag;
543 $args = [];
544
545 // Special case to allow using "@" as "echo:" tag parameter.
546 // TODO NEXT: More rebust DD argument parser (@see #86bzunbgf)
547 if ( strpos( $tag, 'echo:' ) === 0 ) {
548 return [
549 'tag' => 'echo',
550 'args' => [ substr( $tag, 5 ) ], // Everything after 'echo:'
551 'original_tag' => $original_tag,
552 ];
553 }
554
555 // Check if tag has ':' or '@' indicating it has arguments
556 if ( strpos( $tag, ':' ) !== false || strpos( $tag, '@' ) !== false ) {
557 // If there's a ':' before the first '@', split at the first ':'
558 if ( strpos( $tag, ':' ) !== false && ( strpos( $tag, '@' ) === false || strpos( $tag, ':' ) < strpos( $tag, '@' ) ) ) {
559 list($tag, $args_string) = explode( ':', $tag, 2 );
560 } else {
561 // If there's no ':' before the first '@', the tag is before the '@'
562 $args_string = $tag;
563 $tag = strtok( $tag, ' @' );
564 }
565
566 // Check if the args_string contains key-value pairs marked with '@'
567 if ( strpos( $args_string, '@' ) !== false ) {
568 list($standard_args, $kv_args_string) = explode( '@', $args_string, 2 );
569
570 // Add standard arguments to the args array
571 if ( ! empty( $standard_args ) ) {
572 $standard_args_array = explode( ':', $standard_args );
573 foreach ( $standard_args_array as $arg ) {
574 $args[] = trim( $arg );
575 }
576 }
577
578 // Split the key-value pairs
579 $kv_pairs = explode( '@', $kv_args_string );
580 foreach ( $kv_pairs as $pair ) {
581 list($key, $value) = explode( ':', $pair, 2 );
582 $args[ trim( $key ) ] = trim( $value );
583 }
584 } else {
585 // No key-value pairs, just standard arguments
586 $standard_args_array = explode( ':', $args_string );
587 foreach ( $standard_args_array as $arg ) {
588 $args[] = trim( $arg );
589 }
590 }
591 }
592
593 return [
594 'tag' => $tag,
595 'args' => $args,
596 'original_tag' => $original_tag,
597 ];
598 }
599
600 /**
601 * Calculate dynamic data filters according to the args received
602 *
603 * @param array $args
604 * @return array
605 */
606 public function get_filters_from_args( $args ) {
607 $filters = [
608 'object_type' => '',
609 ];
610
611 if ( empty( $args ) || ! is_array( $args ) ) {
612 return $filters;
613 }
614
615 foreach ( $args as $arg ) {
616 // Trim number of words or avatar size (in px)
617 if ( is_numeric( $arg ) ) {
618 $filters['num_words'] = $arg;
619 }
620
621 // Add context to the archive title
622 elseif ( $arg == 'context' || $arg == 'prefix' ) {
623 $filters['add_context'] = true;
624 }
625
626 // Output as image tag
627 elseif ( $arg == 'image' ) {
628 $filters['image'] = true;
629 }
630
631 // Wrap value in a link
632 elseif ( $arg == 'link' ) {
633 $filters['link'] = true;
634 }
635
636 // Open link in newTab
637 elseif ( $arg == 'newTab' ) {
638 $filters['newTab'] = true;
639 }
640
641 // Create a callable link
642 elseif ( $arg == 'tel' ) {
643 $filters['tel'] = true;
644 }
645
646 /**
647 * Return value instead of label
648 *
649 * Useful for dynamic data element conditions like MB checkbox_list, ACF true_false, etc. where the user can specify the value & label.
650 *
651 * @since 1.5.7
652 */
653 elseif ( $arg == 'value' ) {
654 $filters['value'] = true;
655 }
656
657 /**
658 * Return raw value (skip parsing DD tag)
659 *
660 * Useful to skip rendering one specific DD tag
661 *
662 * @since 1.6
663 */
664 elseif ( $arg == 'raw' ) {
665 $filters['raw'] = true;
666 }
667
668 /**
669 * Return URL
670 *
671 * Useful for field type 'file'
672 *
673 * NOTE: Undocumented
674 *
675 * @since 1.6
676 */
677 elseif ( $arg == 'url' ) {
678 $filters['url'] = true;
679 }
680
681 /**
682 * Keep formatting
683 *
684 * Useful for dynamic data with HTML
685 *
686 * Example: {post_excerpt:format}
687 *
688 * @since 1.6.2
689 */
690 elseif ( $arg == 'format' ) {
691 $filters['format'] = true;
692 }
693
694 /**
695 * Return plain text
696 *
697 * Strip HTML tags
698 *
699 * Example: {post_terms_category:plain}
700 *
701 * @since 1.7.2
702 */
703 elseif ( $arg == 'plain' ) {
704 $filters['plain'] = true;
705 }
706
707 /**
708 * Return array value
709 *
710 * Useful for dynamic data with array
711 *
712 * Example : {acf_link_field:array_value|title}
713 *
714 * @since 1.8
715 */
716 elseif ( strpos( $arg, 'array_value|' ) === 0 ) {
717 $filters['array_value'] = str_replace( 'array_value|', '', $arg );
718 }
719
720 // Default key: used for 1) user meta_key, 2) post terms separator or 3) image size, 4) date format
721 else {
722 $filters['meta_key'][] = $arg;
723 }
724 }
725
726 // Note: Use case where the date format contains a colon. E.g. "{post_date:jS F Y h:ia}"
727 if ( isset( $filters['meta_key'] ) ) {
728 $filters['meta_key'] = implode( ':', $filters['meta_key'] );
729 }
730
731 return $filters;
732 }
733 }
734