| @@ -1,8 +1,11 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | |
| 3 | 3 | namespace Better_Payment\Lite\Traits; |
| 4 | 4 | |
| 5 | +use Better_Payment\Lite\Admin\Setup_Wizard; | |
| 6 | +use Better_Payment\Lite\Classes\Plugin_Usage_Tracker; | |
| 7 | + | |
| 5 | 8 | /** |
| 6 | 9 | * Exit if accessed directly |
| 7 | 10 | */ |
| 8 | 11 | if (!defined('ABSPATH')) { |
| @@ -15,35 +18,10 @@ | ||
| 15 | 18 | * @since 0.0.1 |
| 16 | 19 | */ |
| 17 | 20 | trait Helper |
| 18 | 21 | { |
| 19 | - use Elements, ElementorHelper, WordPressHelper; | |
| 22 | + use Elements, ElementorHelper, WordPressHelper, TemplateQuery; | |
| 20 | 23 | |
| 21 | - public function bp_template_render($filePath, $variables = array(), $print = true) | |
| 22 | - { | |
| 23 | - $output = NULL; | |
| 24 | - | |
| 25 | - if (file_exists($filePath)) { | |
| 26 | - // Extract the variables to a local namespace | |
| 27 | - extract($variables); | |
| 28 | - | |
| 29 | - // Start output buffering | |
| 30 | - ob_start(); | |
| 31 | - | |
| 32 | - // Include the template file | |
| 33 | - include $filePath; | |
| 34 | - | |
| 35 | - // End buffering and return its contents | |
| 36 | - $output = ob_get_clean(); | |
| 37 | - } | |
| 38 | - | |
| 39 | - if ( $print ) { | |
| 40 | - print $output; | |
| 41 | - } | |
| 42 | - | |
| 43 | - return $output; | |
| 44 | - } | |
| 45 | - | |
| 46 | 24 | /** |
| 47 | 25 | * List of allowed html tag for wp_kses |
| 48 | 26 | * |
| 49 | 27 | * bp_allowed_tags |
| @@ -292,6 +270,671 @@ | ||
| 292 | 270 | 'colspan' => [], |
| 293 | 271 | 'rowspan' => [], |
| 294 | 272 | ], |
| 295 | 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; | |
| 296 | 939 | } |
| 297 | 940 | } |