PluginProbe
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More / 2.4.1
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More v2.4.1
2.4.1 2.4.0 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 All 68 releases
better-payment / includes / Traits / Helper.php

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

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