[ 'href' => [], 'title' => [], 'class' => [], 'rel' => [], 'id' => [], 'style' => [], 'target' => [], ], 'q' => [ 'cite' => [], 'class' => [], 'id' => [], ], 'img' => [ 'src' => [], 'alt' => [], 'height' => [], 'width' => [], 'class' => [], 'id' => [], 'style' => [] ], 'span' => [ 'class' => [], 'id' => [], 'style' => [] ], 'dfn' => [ 'class' => [], 'id' => [], 'style' => [] ], 'time' => [ 'datetime' => [], 'class' => [], 'id' => [], 'style' => [], ], 'cite' => [ 'title' => [], 'class' => [], 'id' => [], 'style' => [], ], 'hr' => [ 'class' => [], 'id' => [], 'style' => [], ], 'b' => [ 'class' => [], 'id' => [], 'style' => [], ], 'p' => [ 'class' => [], 'id' => [], 'style' => [] ], 'i' => [ 'class' => [], 'id' => [], 'style' => [] ], 'u' => [ 'class' => [], 'id' => [], 'style' => [] ], 's' => [ 'class' => [], 'id' => [], 'style' => [], ], 'br' => [], 'em' => [ 'class' => [], 'id' => [], 'style' => [] ], 'code' => [ 'class' => [], 'id' => [], 'style' => [], ], 'mark' => [ 'class' => [], 'id' => [], 'style' => [], ], 'small' => [ 'class' => [], 'id' => [], 'style' => [] ], 'abbr' => [ 'title' => [], 'class' => [], 'id' => [], 'style' => [], ], 'strong' => [ 'class' => [], 'id' => [], 'style' => [] ], 'del' => [ 'class' => [], 'id' => [], 'style' => [] ], 'ins' => [ 'class' => [], 'id' => [], 'style' => [] ], 'sub' => [ 'class' => [], 'id' => [], 'style' => [], ], 'sup' => [ 'class' => [], 'id' => [], 'style' => [], ], 'div' => [ 'class' => [], 'id' => [], 'style' => [] ], 'strike' => [ 'class' => [], 'id' => [], 'style' => [], ], 'acronym' => [], 'h1' => [ 'class' => [], 'id' => [], 'style' => [], ], 'h2' => [ 'class' => [], 'id' => [], 'style' => [], ], 'h3' => [ 'class' => [], 'id' => [], 'style' => [], ], 'h4' => [ 'class' => [], 'id' => [], 'style' => [], ], 'h5' => [ 'class' => [], 'id' => [], 'style' => [], ], 'h6' => [ 'class' => [], 'id' => [], 'style' => [], ], 'button' => [ 'class' => [], 'id' => [], 'style' => [], 'data-paypal-info' => [], ], 'center' => [ 'class' => [], 'id' => [], 'style' => [], ], 'ul' => [ 'class' => [], 'id' => [], 'style' => [], ], 'ol' => [ 'class' => [], 'id' => [], 'style' => [], ], 'li' => [ 'class' => [], 'id' => [], 'style' => [], ], 'table' => [ 'class' => [], 'id' => [], 'style' => [], 'dir' => [], 'align' => [], ], 'thead' => [ 'class' => [], 'id' => [], 'style' => [], 'align' => [], ], 'tbody' => [ 'class' => [], 'id' => [], 'style' => [], 'align' => [], ], 'tfoot' => [ 'class' => [], 'id' => [], 'style' => [], 'align' => [], ], 'th' => [ 'class' => [], 'id' => [], 'style' => [], 'align' => [], 'colspan' => [], 'rowspan' => [], ], 'tr' => [ 'class' => [], 'id' => [], 'style' => [], 'align' => [], ], 'td' => [ 'class' => [], 'id' => [], 'style' => [], 'align' => [], 'colspan' => [], 'rowspan' => [], ], ]; } /** * Optional usage tracker * * @since v1.1.1 */ public function start_plugin_tracking() { $tracker = Plugin_Usage_Tracker::get_instance( BETTER_PAYMENT_FILE, [ 'opt_in' => true, 'goodbye_form' => true, 'item_id' => '64e1f724b5e14edb343e' ] ); $tracker->set_notice_options(array( 'notice' => 'Want to help make Better Payment even more awesome? You can get a 10% discount coupon for Pro upgrade if you allow.', 'extra_notice' => 'We collect non-sensitive diagnostic data and plugin usage information. Your site URL, WordPress & PHP version, plugins & themes and email address to send you the discount coupon. This data lets us make sure this plugin always stays compatible with the most popular plugins and themes. No spam, I promise.', )); $tracker->init(); } /** * List of currencies not supported by paypal or stripe * * @since v1.3.1 */ public function bp_unsupported_currencies( $method = '' ) { $method = sanitize_text_field( strtolower( $method ) ); $all_currencies = [ 'paypal' => [ 'AED', 'BGN', 'BAM', 'KES', 'NGN', 'RON', 'RSD', 'ZAR', 'GHS', ], 'stripe' => [], ]; return !empty( $all_currencies[ $method ] ) ? $all_currencies[ $method ] : []; } /** * List of currencies supported by paystack * * @since v1.3.1 */ public function bp_supported_currencies( $method = '' ) { $method = sanitize_text_field( strtolower( $method ) ); $supported_currencies = [ 'paystack' => [ 'GHS', 'KES', 'NGN', 'USD', 'ZAR' ], ]; return !empty( $supported_currencies[ $method ] ) ? $supported_currencies[ $method ] : []; } /** * Get comprehensive currency data * * @since 0.0.2 * @return array Array of currency data with codes, names, and symbols */ private function get_currency_data() { return [ 'USD' => ['name' => 'US Dollar', 'symbol' => '$'], 'EUR' => ['name' => 'Euro', 'symbol' => '€'], 'GBP' => ['name' => 'British Pound', 'symbol' => '£'], 'AED' => ['name' => 'UAE Dirham', 'symbol' => 'د.إ'], 'AUD' => ['name' => 'Australian Dollar', 'symbol' => '$'], 'BGN' => ['name' => 'Bulgarian Lev', 'symbol' => 'лв'], 'BAM' => ['name' => 'Bosnia and Herzegovina Convertible Mark', 'symbol' => 'KM'], 'CAD' => ['name' => 'Canadian Dollar', 'symbol' => '$'], 'CHF' => ['name' => 'Swiss Franc', 'symbol' => 'CHF'], 'CZK' => ['name' => 'Czech Koruna', 'symbol' => 'Kč'], 'DKK' => ['name' => 'Danish Krone', 'symbol' => 'kr'], 'GHS' => ['name' => 'Ghanaian Cedi', 'symbol' => '₵'], 'HKD' => ['name' => 'Hong Kong Dollar', 'symbol' => '$'], 'HUF' => ['name' => 'Hungarian Forint', 'symbol' => 'ft'], 'ILS' => ['name' => 'Israeli Shekel', 'symbol' => '₪'], 'JPY' => ['name' => 'Japanese Yen', 'symbol' => '¥'], 'KES' => ['name' => 'Kenyan Shilling', 'symbol' => 'Ksh.'], 'MXN' => ['name' => 'Mexican Peso', 'symbol' => '$'], 'MYR' => ['name' => 'Malaysian Ringgit', 'symbol' => 'MYR'], 'NGN' => ['name' => 'Nigerian Naira', 'symbol' => '₦'], 'NOK' => ['name' => 'Norwegian Krone', 'symbol' => 'kr'], 'NZD' => ['name' => 'New Zealand Dollar', 'symbol' => '$'], 'PHP' => ['name' => 'Philippine Peso', 'symbol' => '₱'], 'PLN' => ['name' => 'Polish Zloty', 'symbol' => 'zł'], 'RON' => ['name' => 'Romanian Leu', 'symbol' => 'lei'], 'RSD' => ['name' => 'Serbian Dinar', 'symbol' => 'din.'], 'RUB' => ['name' => 'Russian Ruble', 'symbol' => '₽'], 'SEK' => ['name' => 'Swedish Krona', 'symbol' => 'kr'], 'SGD' => ['name' => 'Singapore Dollar', 'symbol' => '$'], 'THB' => ['name' => 'Thai Baht', 'symbol' => '฿'], 'TRY' => ['name' => 'Turkish Lira', 'symbol' => '₺'], 'TWD' => ['name' => 'Taiwan Dollar', 'symbol' => '$'], 'ZAR' => ['name' => 'South African Rand', 'symbol' => 'R'], ]; } /** * Get currency symbols list * * @since 0.0.2 * @return array Array of currency codes and their symbols */ public function get_currency_symbols_list() { $currency_data = $this->get_currency_data(); $symbols = []; foreach ($currency_data as $code => $data) { $symbols[$code] = $data['symbol']; } return $symbols; } /** * Get currency list * * @since 0.0.2 * @return array Array of currency codes as key-value pairs */ public function get_currency_list() { $currency_data = $this->get_currency_data(); $currency_list = []; foreach ($currency_data as $code => $data) { $currency_list[$code] = $code; } return $currency_list; } /** * Get currency names list * * @since 0.0.2 * @return array Array of currency codes and their full names */ public function get_currency_names_list() { $currency_data = $this->get_currency_data(); $names = []; foreach ($currency_data as $code => $data) { $names[$code] = $data['name']; } return $names; } /** * Get currency symbol by code * * @since 0.0.2 * @param string $currency_code The currency code (e.g., 'USD', 'EUR') * @return string The currency symbol or the code if symbol not found */ public function get_currency_symbol($currency_code) { $currency_data = $this->get_currency_data(); if (isset($currency_data[$currency_code])) { return $currency_data[$currency_code]['symbol']; } return $currency_code; // Fallback to currency code } /** * Get currency name by code * * @since 0.0.2 * @param string $currency_code The currency code (e.g., 'USD', 'EUR') * @return string The currency name or the code if name not found */ public function get_currency_name($currency_code) { $currency_data = $this->get_currency_data(); if (isset($currency_data[$currency_code])) { return $currency_data[$currency_code]['name']; } return $currency_code; // Fallback to currency code } /** * Widget settings * * @since 1.0.0 */ public function get_elementor_widget_settings( $page_id, $widget_id ) { $settings = []; // First, check for Gutenberg block settings stored in transient. // This is used when the form is rendered via a Gutenberg block. $transient_key = 'bp_block_settings_' . $page_id . '_' . $widget_id; $block_settings = get_transient( $transient_key ); if ( ! empty( $block_settings ) && is_array( $block_settings ) ) { return $block_settings; } // Fallback to Elementor widget settings. if ( class_exists( '\Elementor\Plugin' ) && ! empty( \Elementor\Plugin::$instance ) ) { $document = \Elementor\Plugin::$instance->documents->get( $page_id ); if ( $document ) { $elements = \Elementor\Plugin::instance()->documents->get( $page_id )->get_elements_data(); $widget_data = $this->find_element_recursive( $elements, $widget_id ); $widget = ! empty( $widget_data ) && is_array( $widget_data ) ? \Elementor\Plugin::instance()->elements_manager->create_element_instance( $widget_data ) : ''; if ( ! empty( $widget ) ) { $settings = $widget->get_settings_for_display(); } } } return $settings; } /** * Find element recursive * * @since 1.0.0 */ public function find_element_recursive( $elements, $form_id ) { foreach ( $elements as $element ) { if ( $form_id === $element[ 'id' ] ) { return $element; } if ( !empty( $element[ 'elements' ] ) ) { $element = $this->find_element_recursive( $element[ 'elements' ], $form_id ); if ( $element ) { return $element; } } } return false; } public function get_stripe_price_details( $price_id = '', $secret_key = '' ) { if ( empty( $price_id ) || empty( $secret_key ) ) { return []; } $api_url = 'https://api.stripe.com/v1/prices/' . $price_id; $response = wp_remote_get( $api_url, [ 'headers' => [ 'Authorization' => 'Bearer ' . $secret_key, ], 'timeout' => 20, ] ); if ( is_wp_error( $response ) ) { error_log( 'Stripe API error: ' . $response->get_error_message() ); return []; } $body = wp_remote_retrieve_body( $response ); $data = json_decode( $body, true ); if ( ! isset( $data ) || ! is_array( $data ) ) { error_log( 'Stripe API response format error.' ); return []; } return $data; } /** * Initialize widget usage * * @since 1.0.0 */ public function bp_init_widget_usage() { // Run initial Elementor widget scan if not done yet if (!get_option('bp_widget_usage_initial_scan_done', false)) { $this->scan_existing_pages_for_widgets(); update_option('bp_widget_usage_initial_scan_done', true); } // Run initial Gutenberg block scan independently — separate flag so it // still runs on sites where the widget scan already completed. if (!get_option('bp_block_usage_initial_scan_done', false)) { $this->scan_existing_pages_for_blocks(); update_option('bp_block_usage_initial_scan_done', true); } } /** * Track widget and block usage when a post is saved * * @since 1.0.0 * @param int $post_id */ public function bp_widget_usage_on_save($post_id) { // add capability check if ( ! current_user_can( 'edit_post', $post_id ) ) { return; } // Skip autosaves and revisions if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } if (wp_is_post_revision($post_id)) { return; } // Only track pages and posts $post_type = get_post_type($post_id); if (!in_array($post_type, ['page', 'post'])) { return; } // Check Elementor widget usage $elementor_data = get_post_meta($post_id, '_elementor_data', true); if (!empty($elementor_data)) { $this->update_widget_usage_flags($post_id, $elementor_data); } // Check Gutenberg block usage $post = get_post($post_id); if ($post && has_blocks($post->post_content)) { if ($this->detect_better_payment_blocks(parse_blocks($post->post_content))) { update_option('better_payment_any_block_used', true); } } } /** * Scan all existing pages for Better Payment widgets * * @since 1.0.0 */ public function scan_existing_pages_for_widgets() { global $wpdb; // Get all posts/pages with Elementor data $posts_with_elementor = $wpdb->get_results( "SELECT post_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key = '_elementor_data' AND meta_value != '' AND meta_value != '[]'" ); foreach ($posts_with_elementor as $post_data) { $this->update_widget_usage_flags($post_data->post_id, $post_data->meta_value); } } /** * Update widget usage flags based on page content * * @since 1.0.0 * @param int $post_id * @param string $elementor_data */ public function update_widget_usage_flags($post_id, $elementor_data) { // Parse Elementor data $elements = json_decode($elementor_data, true); if (!is_array($elements)) { return; } // Check for Better Payment widgets $widget_usage = $this->detect_better_payment_widgets($elements); // Update overall usage flag $any_widget_used = $widget_usage['better-payment']; if ($any_widget_used) { update_option('better_payment_any_widget_used', true); } else { delete_option('better_payment_any_widget_used'); } } /** * Recursively detect Better Payment widgets in Elementor data * * @since 1.0.0 * @param array $elements * @return array */ public function detect_better_payment_widgets($elements) { $widget_usage = [ 'better-payment' => false, ]; foreach ($elements as $element) { // Check if this element is a Better Payment widget if (isset($element['widgetType'])) { $widget_type = $element['widgetType']; if ($widget_type === 'better-payment') { $widget_usage['better-payment'] = true; } elseif ($widget_type === 'better-payment-user-dashboard') { $widget_usage['better-payment'] = true; } elseif ($widget_type === 'fundraising-campaign') { $widget_usage['better-payment'] = true; } } // Recursively check nested elements if (!empty($element['elements']) && is_array($element['elements'])) { $nested_usage = $this->detect_better_payment_widgets($element['elements']); // Merge results (OR operation) $widget_usage['better-payment'] = $widget_usage['better-payment'] || $nested_usage['better-payment']; } } return $widget_usage; } /** * Check if any Better Payment widget is being used * * @since 1.0.0 * @return bool */ public function is_any_better_payment_widget_used() { return get_option('better_payment_any_widget_used', false) ? '1' : '0'; } /** * Scan all existing published posts/pages for Better Payment Gutenberg blocks * * @since 2.0.0 */ public function scan_existing_pages_for_blocks() { global $wpdb; $block_found = $wpdb->get_var( "SELECT ID FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_content LIKE '%