PluginProbe
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More / 2.3.3
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More v2.3.3
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.2 2.2.1 2.2.0 2.1.2 2.1.1 trunk 0.0.1 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.0.7 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 All 66 releases
better-payment / includes / Traits / Helper.php

Helper.php in Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More 2.3.3, at includes/Traits/Helper.php

938 lines 27.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Better_Payment\Lite\Traits;
4
5 use Better_Payment\Lite\Admin\Setup_Wizard;
6 use Better_Payment\Lite\Classes\Plugin_Usage_Tracker;
7
8 /**
9 * Exit if accessed directly
10 */
11 if (!defined('ABSPATH')) {
12 exit;
13 }
14
15 /**
16 * Helper trait
17 *
18 * @since 0.0.1
19 */
20 trait Helper
21 {
22 use Elements, ElementorHelper, WordPressHelper, TemplateQuery;
23
24 public function bp_template_render($filePath, $variables = array(), $print = true)
25 {
26 $output = NULL;
27
28 if (file_exists($filePath)) {
29 // Extract the variables to a local namespace
30 extract($variables);
31
32 // Start output buffering
33 ob_start();
34
35 // Include the template file
36 include $filePath;
37
38 // End buffering and return its contents
39 $output = ob_get_clean();
40 }
41
42 if ( $print ) {
43 print $output;
44 }
45
46 return $output;
47 }
48
49 /**
50 * List of allowed html tag for wp_kses
51 *
52 * bp_allowed_tags
53 * @return array
54 */
55 public function bp_allowed_tags() {
56 return [
57 'a' => [
58 'href' => [],
59 'title' => [],
60 'class' => [],
61 'rel' => [],
62 'id' => [],
63 'style' => [],
64 'target' => [],
65 ],
66 'q' => [
67 'cite' => [],
68 'class' => [],
69 'id' => [],
70 ],
71 'img' => [
72 'src' => [],
73 'alt' => [],
74 'height' => [],
75 'width' => [],
76 'class' => [],
77 'id' => [],
78 'style' => []
79 ],
80 'span' => [
81 'class' => [],
82 'id' => [],
83 'style' => []
84 ],
85 'dfn' => [
86 'class' => [],
87 'id' => [],
88 'style' => []
89 ],
90 'time' => [
91 'datetime' => [],
92 'class' => [],
93 'id' => [],
94 'style' => [],
95 ],
96 'cite' => [
97 'title' => [],
98 'class' => [],
99 'id' => [],
100 'style' => [],
101 ],
102 'hr' => [
103 'class' => [],
104 'id' => [],
105 'style' => [],
106 ],
107 'b' => [
108 'class' => [],
109 'id' => [],
110 'style' => [],
111 ],
112 'p' => [
113 'class' => [],
114 'id' => [],
115 'style' => []
116 ],
117 'i' => [
118 'class' => [],
119 'id' => [],
120 'style' => []
121 ],
122 'u' => [
123 'class' => [],
124 'id' => [],
125 'style' => []
126 ],
127 's' => [
128 'class' => [],
129 'id' => [],
130 'style' => [],
131 ],
132 'br' => [],
133 'em' => [
134 'class' => [],
135 'id' => [],
136 'style' => []
137 ],
138 'code' => [
139 'class' => [],
140 'id' => [],
141 'style' => [],
142 ],
143 'mark' => [
144 'class' => [],
145 'id' => [],
146 'style' => [],
147 ],
148 'small' => [
149 'class' => [],
150 'id' => [],
151 'style' => []
152 ],
153 'abbr' => [
154 'title' => [],
155 'class' => [],
156 'id' => [],
157 'style' => [],
158 ],
159 'strong' => [
160 'class' => [],
161 'id' => [],
162 'style' => []
163 ],
164 'del' => [
165 'class' => [],
166 'id' => [],
167 'style' => []
168 ],
169 'ins' => [
170 'class' => [],
171 'id' => [],
172 'style' => []
173 ],
174 'sub' => [
175 'class' => [],
176 'id' => [],
177 'style' => [],
178 ],
179 'sup' => [
180 'class' => [],
181 'id' => [],
182 'style' => [],
183 ],
184 'div' => [
185 'class' => [],
186 'id' => [],
187 'style' => []
188 ],
189 'strike' => [
190 'class' => [],
191 'id' => [],
192 'style' => [],
193 ],
194 'acronym' => [],
195 'h1' => [
196 'class' => [],
197 'id' => [],
198 'style' => [],
199 ],
200 'h2' => [
201 'class' => [],
202 'id' => [],
203 'style' => [],
204 ],
205 'h3' => [
206 'class' => [],
207 'id' => [],
208 'style' => [],
209 ],
210 'h4' => [
211 'class' => [],
212 'id' => [],
213 'style' => [],
214 ],
215 'h5' => [
216 'class' => [],
217 'id' => [],
218 'style' => [],
219 ],
220 'h6' => [
221 'class' => [],
222 'id' => [],
223 'style' => [],
224 ],
225 'button' => [
226 'class' => [],
227 'id' => [],
228 'style' => [],
229 'data-paypal-info' => [],
230 ],
231 'center' => [
232 'class' => [],
233 'id' => [],
234 'style' => [],
235 ],
236 'ul' => [
237 'class' => [],
238 'id' => [],
239 'style' => [],
240 ],
241 'ol' => [
242 'class' => [],
243 'id' => [],
244 'style' => [],
245 ],
246 'li' => [
247 'class' => [],
248 'id' => [],
249 'style' => [],
250 ],
251 'table' => [
252 'class' => [],
253 'id' => [],
254 'style' => [],
255 'dir' => [],
256 'align' => [],
257 ],
258 'thead' => [
259 'class' => [],
260 'id' => [],
261 'style' => [],
262 'align' => [],
263 ],
264 'tbody' => [
265 'class' => [],
266 'id' => [],
267 'style' => [],
268 'align' => [],
269 ],
270 'tfoot' => [
271 'class' => [],
272 'id' => [],
273 'style' => [],
274 'align' => [],
275 ],
276 'th' => [
277 'class' => [],
278 'id' => [],
279 'style' => [],
280 'align' => [],
281 'colspan' => [],
282 'rowspan' => [],
283 ],
284 'tr' => [
285 'class' => [],
286 'id' => [],
287 'style' => [],
288 'align' => [],
289 ],
290 'td' => [
291 'class' => [],
292 'id' => [],
293 'style' => [],
294 'align' => [],
295 'colspan' => [],
296 'rowspan' => [],
297 ],
298 ];
299 }
300
301 /**
302 * Optional usage tracker
303 *
304 * @since v1.1.1
305 */
306 public function start_plugin_tracking()
307 {
308 /**
309 * Lite-only, exactly as before — this guard used to live in the
310 * `! $this->pro_enabled` block in Admin::init(). It moved here when
311 * tracking was rewired to also run on cron requests, so the single
312 * remaining caller does not have to repeat it.
313 *
314 * Evaluating the filter at `init` rather than at `plugins_loaded` is
315 * strictly safer too: every plugin has loaded by then, so Pro cannot
316 * lose a race to register its filter and have Lite start tracking on a
317 * Pro site.
318 *
319 * @since 2.3.2
320 */
321 if ( apply_filters( 'better_payment/pro_enabled', false ) ) {
322 return;
323 }
324
325 /**
326 * Must run BEFORE init() below: init() calls maybe_schedule_tracking(),
327 * which only schedules once consent is on record. Recording consent
328 * first means a fresh install gets its daily event in this same request
329 * rather than the next one.
330 */
331 $this->maybe_auto_enable_usage_tracking();
332
333 $tracker = Plugin_Usage_Tracker::get_instance( BETTER_PAYMENT_FILE, [
334 'opt_in' => true,
335 'goodbye_form' => true,
336 'item_id' => '64e1f724b5e14edb343e'
337 ] );
338 $tracker->set_notice_options(array(
339 'notice' => 'Want to help make <strong>Better Payment</strong> even more awesome? You can get a <strong>10% discount coupon</strong> for Pro upgrade if you allow.',
340 'extra_notice' => 'Nothing is sent unless you allow it. If you do: your site URL and name;
341 WordPress, PHP and server versions; language and charset; your <strong>active</strong> plugins and
342 theme (and how many inactive); which Better Payment widgets, blocks and campaign elements you use,
343 and which features and gateways you have switched on; and your admin email, for the coupon.
344 We never collect your payment or transaction data, your customers\' details, your API keys,
345 or your inactive plugin names. No spam, I promise.',
346 ));
347 $tracker->init();
348 }
349
350 /**
351 * Turn usage tracking on for a fresh install that never answered the wizard.
352 *
353 * The wizard's two buttons cover the cases where the user answers: "Proceed
354 * To Next Step" and "Skip It" both call Setup_Wizard::wpins_process(). A user
355 * who closes the wizard, navigates away, or never opens it at all answers
356 * neither, and this is what covers them.
357 *
358 * Three things this deliberately does:
359 *
360 * 1. **Fresh installs only.** The `pending` marker is written by
361 * Installer::enable_setup_wizard() and only on a site with no
362 * `better_payment_setup_wizard` option — i.e. one being installed, not
363 * upgraded. An existing site keeps whatever tracking state it has.
364 * 2. **Exactly once.** The marker flips to `done` before any work, so this
365 * cannot re-assert consent on a later request. That is what keeps a
366 * future opt-out from being silently undone on the next page load, and
367 * it holds even if the call below throws.
368 * 3. **No forced send.** `wpins_process( false )` records consent and stops.
369 * This runs on `init` during an ordinary admin (or cron) request, and
370 * the tracker's send is a blocking POST with a 30s timeout — it belongs
371 * on the scheduled event, which init() arranges immediately after.
372 *
373 * Inherits start_plugin_tracking()'s Pro gate: a site running Better Payment
374 * Pro never reaches here, exactly like every other tracking code path.
375 *
376 * @return void
377 * @since 2.3.2
378 */
379 protected function maybe_auto_enable_usage_tracking()
380 {
381 if ( get_option( 'better_payment_tracking_auto_optin' ) !== 'pending' ) {
382 return;
383 }
384
385 update_option( 'better_payment_tracking_auto_optin', 'done' );
386
387 Setup_Wizard::wpins_process( false );
388 }
389
390 /**
391 * List of currencies not supported by paypal or stripe
392 *
393 * @since v1.3.1
394 */
395 public function bp_unsupported_currencies( $method = '' ) {
396 $method = sanitize_text_field( strtolower( $method ) );
397 $all_currencies = [
398 'paypal' => [
399 'AED',
400 'BGN',
401 'BAM',
402 'KES',
403 'NGN',
404 'RON',
405 'RSD',
406 'ZAR',
407 'GHS',
408 ],
409 'stripe' => [],
410 ];
411
412 return !empty( $all_currencies[ $method ] ) ? $all_currencies[ $method ] : [];
413 }
414
415 /**
416 * List of currencies supported by paystack
417 *
418 * @since v1.3.1
419 */
420 public function bp_supported_currencies( $method = '' ) {
421 $method = sanitize_text_field( strtolower( $method ) );
422 $supported_currencies = [
423 'paystack' => [
424 'GHS',
425 'KES',
426 'NGN',
427 'USD',
428 'ZAR'
429 ],
430 ];
431
432 return !empty( $supported_currencies[ $method ] ) ? $supported_currencies[ $method ] : [];
433 }
434
435 /**
436 * Get comprehensive currency data
437 *
438 * @since 0.0.2
439 * @return array Array of currency data with codes, names, and symbols
440 */
441 private function get_currency_data() {
442 return [
443 'USD' => ['name' => 'US Dollar', 'symbol' => '$'],
444 'EUR' => ['name' => 'Euro', 'symbol' => ''],
445 'GBP' => ['name' => 'British Pound', 'symbol' => '£'],
446 'AED' => ['name' => 'UAE Dirham', 'symbol' => 'د.إ'],
447 'AUD' => ['name' => 'Australian Dollar', 'symbol' => '$'],
448 'BGN' => ['name' => 'Bulgarian Lev', 'symbol' => 'лв'],
449 'BAM' => ['name' => 'Bosnia and Herzegovina Convertible Mark', 'symbol' => 'KM'],
450 'CAD' => ['name' => 'Canadian Dollar', 'symbol' => '$'],
451 'CHF' => ['name' => 'Swiss Franc', 'symbol' => 'CHF'],
452 'CZK' => ['name' => 'Czech Koruna', 'symbol' => ''],
453 'DKK' => ['name' => 'Danish Krone', 'symbol' => 'kr'],
454 'GHS' => ['name' => 'Ghanaian Cedi', 'symbol' => ''],
455 'HKD' => ['name' => 'Hong Kong Dollar', 'symbol' => '$'],
456 'HUF' => ['name' => 'Hungarian Forint', 'symbol' => 'ft'],
457 'ILS' => ['name' => 'Israeli Shekel', 'symbol' => ''],
458 'JPY' => ['name' => 'Japanese Yen', 'symbol' => '¥'],
459 'KES' => ['name' => 'Kenyan Shilling', 'symbol' => 'Ksh.'],
460 'MXN' => ['name' => 'Mexican Peso', 'symbol' => '$'],
461 'MYR' => ['name' => 'Malaysian Ringgit', 'symbol' => 'MYR'],
462 'NGN' => ['name' => 'Nigerian Naira', 'symbol' => ''],
463 'NOK' => ['name' => 'Norwegian Krone', 'symbol' => 'kr'],
464 'NZD' => ['name' => 'New Zealand Dollar', 'symbol' => '$'],
465 'PHP' => ['name' => 'Philippine Peso', 'symbol' => ''],
466 'PLN' => ['name' => 'Polish Zloty', 'symbol' => ''],
467 'RON' => ['name' => 'Romanian Leu', 'symbol' => 'lei'],
468 'RSD' => ['name' => 'Serbian Dinar', 'symbol' => 'din.'],
469 'RUB' => ['name' => 'Russian Ruble', 'symbol' => ''],
470 'SEK' => ['name' => 'Swedish Krona', 'symbol' => 'kr'],
471 'SGD' => ['name' => 'Singapore Dollar', 'symbol' => '$'],
472 'THB' => ['name' => 'Thai Baht', 'symbol' => '฿'],
473 'TRY' => ['name' => 'Turkish Lira', 'symbol' => ''],
474 'TWD' => ['name' => 'Taiwan Dollar', 'symbol' => '$'],
475 'ZAR' => ['name' => 'South African Rand', 'symbol' => 'R'],
476 ];
477 }
478
479 /**
480 * Get currency symbols list
481 *
482 * @since 0.0.2
483 * @return array Array of currency codes and their symbols
484 */
485 public function get_currency_symbols_list() {
486 $currency_data = $this->get_currency_data();
487 $symbols = [];
488
489 foreach ($currency_data as $code => $data) {
490 $symbols[$code] = $data['symbol'];
491 }
492
493 return $symbols;
494 }
495
496 /**
497 * Get currency list
498 *
499 * @since 0.0.2
500 * @return array Array of currency codes as key-value pairs
501 */
502 public function get_currency_list() {
503 $currency_data = $this->get_currency_data();
504 $currency_list = [];
505
506 foreach ($currency_data as $code => $data) {
507 $currency_list[$code] = $code;
508 }
509
510 return $currency_list;
511 }
512
513 /**
514 * Get currency names list
515 *
516 * @since 0.0.2
517 * @return array Array of currency codes and their full names
518 */
519 public function get_currency_names_list() {
520 $currency_data = $this->get_currency_data();
521 $names = [];
522
523 foreach ($currency_data as $code => $data) {
524 $names[$code] = $data['name'];
525 }
526
527 return $names;
528 }
529
530 /**
531 * Get currency symbol by code
532 *
533 * @since 0.0.2
534 * @param string $currency_code The currency code (e.g., 'USD', 'EUR')
535 * @return string The currency symbol or the code if symbol not found
536 */
537 public function get_currency_symbol($currency_code) {
538 $currency_data = $this->get_currency_data();
539
540 if (isset($currency_data[$currency_code])) {
541 return $currency_data[$currency_code]['symbol'];
542 }
543
544 return $currency_code; // Fallback to currency code
545 }
546
547 /**
548 * Get currency name by code
549 *
550 * @since 0.0.2
551 * @param string $currency_code The currency code (e.g., 'USD', 'EUR')
552 * @return string The currency name or the code if name not found
553 */
554 public function get_currency_name($currency_code) {
555 $currency_data = $this->get_currency_data();
556
557 if (isset($currency_data[$currency_code])) {
558 return $currency_data[$currency_code]['name'];
559 }
560
561 return $currency_code; // Fallback to currency code
562 }
563
564 /**
565 * Widget settings
566 *
567 * @since 1.0.0
568 */
569 public function get_elementor_widget_settings( $page_id, $widget_id ) {
570 $settings = [];
571
572 // First, check for Gutenberg block settings stored in transient.
573 // This is used when the form is rendered via a Gutenberg block.
574 $transient_key = 'bp_block_settings_' . $page_id . '_' . $widget_id;
575 $block_settings = get_transient( $transient_key );
576 if ( ! empty( $block_settings ) && is_array( $block_settings ) ) {
577 return $block_settings;
578 }
579
580 // Fallback to Elementor widget settings.
581 if ( class_exists( '\Elementor\Plugin' ) && ! empty( \Elementor\Plugin::$instance ) ) {
582 $document = \Elementor\Plugin::$instance->documents->get( $page_id );
583 if ( $document ) {
584 $elements = \Elementor\Plugin::instance()->documents->get( $page_id )->get_elements_data();
585 $widget_data = $this->find_element_recursive( $elements, $widget_id );
586 $widget = ! empty( $widget_data ) && is_array( $widget_data ) ? \Elementor\Plugin::instance()->elements_manager->create_element_instance( $widget_data ) : '';
587 if ( ! empty( $widget ) ) {
588 $settings = $widget->get_settings_for_display();
589 }
590 }
591 }
592
593 return $settings;
594 }
595
596 /**
597 * Find element recursive
598 *
599 * @since 1.0.0
600 */
601 public function find_element_recursive( $elements, $form_id ) {
602
603 foreach ( $elements as $element ) {
604 if ( $form_id === $element[ 'id' ] ) {
605 return $element;
606 }
607
608 if ( !empty( $element[ 'elements' ] ) ) {
609 $element = $this->find_element_recursive( $element[ 'elements' ], $form_id );
610
611 if ( $element ) {
612 return $element;
613 }
614 }
615 }
616
617 return false;
618 }
619
620 public function get_stripe_price_details( $price_id = '', $secret_key = '' ) {
621 if ( empty( $price_id ) || empty( $secret_key ) ) {
622 return [];
623 }
624
625 $api_url = 'https://api.stripe.com/v1/prices/' . $price_id;
626
627 $response = wp_remote_get( $api_url, [
628 'headers' => [
629 'Authorization' => 'Bearer ' . $secret_key,
630 ],
631 'timeout' => 20,
632 ] );
633
634 if ( is_wp_error( $response ) ) {
635 error_log( 'Stripe API error: ' . $response->get_error_message() );
636 return [];
637 }
638
639 $body = wp_remote_retrieve_body( $response );
640 $data = json_decode( $body, true );
641
642 if ( ! isset( $data ) || ! is_array( $data ) ) {
643 error_log( 'Stripe API response format error.' );
644 return [];
645 }
646
647 return $data;
648 }
649
650 /**
651 * Initialize widget usage
652 *
653 * @since 1.0.0
654 */
655 public function bp_init_widget_usage() {
656 // Run initial Elementor widget scan if not done yet
657 if (!get_option('bp_widget_usage_initial_scan_done', false)) {
658 $this->scan_existing_pages_for_widgets();
659 update_option('bp_widget_usage_initial_scan_done', true);
660 }
661
662 // Run initial Gutenberg block scan independently — separate flag so it
663 // still runs on sites where the widget scan already completed.
664 if (!get_option('bp_block_usage_initial_scan_done', false)) {
665 $this->scan_existing_pages_for_blocks();
666 update_option('bp_block_usage_initial_scan_done', true);
667 }
668 }
669
670 /**
671 * Track widget and block usage when a post is saved
672 *
673 * @since 1.0.0
674 * @param int $post_id
675 */
676 public function bp_widget_usage_on_save($post_id) {
677 // add capability check
678 if ( ! current_user_can( 'edit_post', $post_id ) ) {
679 return;
680 }
681
682 // Skip autosaves and revisions
683 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
684 return;
685 }
686
687 if (wp_is_post_revision($post_id)) {
688 return;
689 }
690
691 // Only track pages and posts
692 $post_type = get_post_type($post_id);
693 if (!in_array($post_type, ['page', 'post'])) {
694 return;
695 }
696
697 // Check Elementor widget usage
698 $elementor_data = get_post_meta($post_id, '_elementor_data', true);
699 if (!empty($elementor_data)) {
700 $this->update_widget_usage_flags($post_id, $elementor_data);
701 }
702
703 // Check Gutenberg block usage
704 $post = get_post($post_id);
705 if ($post && has_blocks($post->post_content)) {
706 if ($this->detect_better_payment_blocks(parse_blocks($post->post_content))) {
707 update_option('better_payment_any_block_used', true);
708 }
709 }
710 }
711
712 /**
713 * Scan all existing pages for Better Payment widgets
714 *
715 * @since 1.0.0
716 */
717 public function scan_existing_pages_for_widgets() {
718 global $wpdb;
719
720 // Get all posts/pages with Elementor data
721 $posts_with_elementor = $wpdb->get_results(
722 "SELECT post_id, meta_value FROM {$wpdb->postmeta}
723 WHERE meta_key = '_elementor_data'
724 AND meta_value != ''
725 AND meta_value != '[]'"
726 );
727
728 foreach ($posts_with_elementor as $post_data) {
729 $this->update_widget_usage_flags($post_data->post_id, $post_data->meta_value);
730 }
731 }
732
733 /**
734 * Update widget usage flags based on page content
735 *
736 * @since 1.0.0
737 * @param int $post_id
738 * @param string $elementor_data
739 */
740 public function update_widget_usage_flags($post_id, $elementor_data) {
741 // Parse Elementor data
742 $elements = json_decode($elementor_data, true);
743 if (!is_array($elements)) {
744 return;
745 }
746
747 // Check for Better Payment widgets
748 $widget_usage = $this->detect_better_payment_widgets($elements);
749
750 // Update overall usage flag
751 $any_widget_used = $widget_usage['better-payment'];
752
753 if ($any_widget_used) {
754 update_option('better_payment_any_widget_used', true);
755 } else {
756 delete_option('better_payment_any_widget_used');
757 }
758 }
759
760 /**
761 * Recursively detect Better Payment widgets in Elementor data
762 *
763 * @since 1.0.0
764 * @param array $elements
765 * @return array
766 */
767 public function detect_better_payment_widgets($elements) {
768 $widget_usage = [
769 'better-payment' => false,
770 ];
771
772 foreach ($elements as $element) {
773 // Check if this element is a Better Payment widget
774 if (isset($element['widgetType'])) {
775 $widget_type = $element['widgetType'];
776
777 if ($widget_type === 'better-payment') {
778 $widget_usage['better-payment'] = true;
779 } elseif ($widget_type === 'better-payment-user-dashboard') {
780 $widget_usage['better-payment'] = true;
781 } elseif ($widget_type === 'fundraising-campaign') {
782 $widget_usage['better-payment'] = true;
783 }
784 }
785
786 // Recursively check nested elements
787 if (!empty($element['elements']) && is_array($element['elements'])) {
788 $nested_usage = $this->detect_better_payment_widgets($element['elements']);
789
790 // Merge results (OR operation)
791 $widget_usage['better-payment'] = $widget_usage['better-payment'] || $nested_usage['better-payment'];
792 }
793 }
794
795 return $widget_usage;
796 }
797
798 /**
799 * Check if any Better Payment widget is being used
800 *
801 * @since 1.0.0
802 * @return bool
803 */
804 public function is_any_better_payment_widget_used() {
805 return get_option('better_payment_any_widget_used', false) ? '1' : '0';
806 }
807
808 /**
809 * Scan all existing published posts/pages for Better Payment Gutenberg blocks
810 *
811 * @since 2.0.0
812 */
813 public function scan_existing_pages_for_blocks() {
814 global $wpdb;
815
816 $block_found = $wpdb->get_var(
817 "SELECT ID FROM {$wpdb->posts}
818 WHERE post_status = 'publish'
819 AND post_content LIKE '%<!-- wp:better-payment/%'
820 LIMIT 1"
821 );
822
823 if ( $block_found ) {
824 update_option( 'better_payment_any_block_used', true );
825 }
826 }
827
828 /**
829 * Recursively detect Better Payment blocks in parsed Gutenberg block data
830 *
831 * @since 2.0.0
832 * @param array $blocks Parsed blocks from parse_blocks()
833 * @return bool
834 */
835 public function detect_better_payment_blocks( $blocks ) {
836 $bp_block_names = [ 'better-payment/payment-form', 'better-payment/user-dashboard' ];
837
838 foreach ( $blocks as $block ) {
839 if ( in_array( $block['blockName'], $bp_block_names, true ) ) {
840 return true;
841 }
842
843 // Recursively check inner blocks
844 if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
845 if ( $this->detect_better_payment_blocks( $block['innerBlocks'] ) ) {
846 return true;
847 }
848 }
849 }
850
851 return false;
852 }
853
854 /**
855 * Check if any Better Payment Gutenberg block is being used
856 *
857 * @since 2.0.0
858 * @return string '1' or '0'
859 */
860 public function is_any_better_payment_block_used() {
861 return get_option( 'better_payment_any_block_used', false ) ? '1' : '0';
862 }
863
864 /**
865 * Check if dismissible section should be shown
866 *
867 * @since 1.4.2
868 * @return bool
869 */
870 public function bp_section_dismissed() {
871 $progress_bar_dismissed = get_option('better_payment_progress_bar_dismissed', false);
872 $plugin_installed_fresh = get_option('better_payment_plugin_installed_fresh', false);
873 $progress_bar_dismissed_expiry_date = get_option('better_payment_progress_bar_dismissed_expiry_date', 0);
874
875 if ($progress_bar_dismissed) {
876 return true;
877 }
878
879 return $plugin_installed_fresh === 'yes' && time() > $progress_bar_dismissed_expiry_date;
880 }
881
882 public function bp_calculate_progress_steps($settings) {
883 $steps = [];
884
885 // Step 1: Check if API keys are configured
886 // Check PayPal keys
887 $paypal_business_email = !empty($settings['better_payment_settings_payment_paypal_email']);
888
889 // Check Stripe keys
890 $is_stripe_live_mode = $settings['better_payment_settings_payment_stripe_live_mode'] === 'yes' ? true : false;
891 if ( $is_stripe_live_mode ) {
892 $stripe_public = !empty($settings['better_payment_settings_payment_stripe_live_public']);
893 $stripe_secret = !empty($settings['better_payment_settings_payment_stripe_live_secret']);
894 } else {
895 $stripe_public = !empty($settings['better_payment_settings_payment_stripe_test_public']);
896 $stripe_secret = !empty($settings['better_payment_settings_payment_stripe_test_secret']);
897 }
898
899 // Check Paystack keys
900 $is_paystack_live_mode = $settings['better_payment_settings_payment_paystack_live_mode'] === 'yes' ? true : false;
901 if ( $is_paystack_live_mode ) {
902 $paystack_public = !empty($settings['better_payment_settings_payment_paystack_live_public']);
903 $paystack_secret = !empty($settings['better_payment_settings_payment_paystack_live_secret']);
904 } else {
905 $paystack_public = !empty($settings['better_payment_settings_payment_paystack_test_public']);
906 $paystack_secret = !empty($settings['better_payment_settings_payment_paystack_test_secret']);
907 }
908
909 $api_keys_configured = ( isset($paypal_business_email) && $paypal_business_email ) || ( isset($stripe_public) && isset($stripe_secret) && $stripe_public && $stripe_secret ) || ( isset($paystack_public) && isset($paystack_secret) && $paystack_public && $paystack_secret );
910 $payment_settings_url = admin_url('admin.php?page=better-payment-admin&tab=settings&id=paypal');
911
912 $steps[] = [
913 'completed' => $api_keys_configured,
914 'text' => sprintf(__('Add your Stripe, PayPal, or Paystack API keys in <a href="%s">Payment Settings</a>', 'better-payment'), $payment_settings_url)
915 ];
916
917 // Step 2: Check if Elementor widget has been added
918 $widget_used = $this->is_any_better_payment_widget_used() === '1' ? true : false;
919 // Step 3: Check if any Better Payment Gutenberg block has been added
920 $block_used = $this->is_any_better_payment_block_used() === '1' ? true : false;
921
922 $steps[] = [
923 'completed' => $widget_used || $block_used,
924 'text' => __('Open your page in', 'better-payment') . ' <a target="_blank" href="//betterpayment.co/docs/how-to-style-payment-form-in-elementor/">' . __('Elementor', 'better-payment') . '</a>' . __( ' or ', 'better-payment' ) . '<a target="_blank" href="//betterpayment.co/docs/configure-payment-form-in-gutenberg/">' . __('Gutenberg', 'better-payment') . '</a>' . __( ' and insert the Better Payment widget/block.', 'better-payment' ),
925 ];
926
927 // Step 4: Check if widget has been customized
928 $widget_customized = $widget_used || $block_used;
929
930 $steps[] = [
931 'completed' => $widget_customized,
932 'text' => __('Customize the settings to fit your needs, then save and publish', 'better-payment')
933 ];
934
935 return $steps;
936 }
937 }
938