[
'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 '%