--- a/business-directory-plugin/business-directory-plugin.php
+++ b/business-directory-plugin/business-directory-plugin.php
@@ -3,7 +3,7 @@
* Plugin Name: Business Directory Plugin
* Plugin URI: https://businessdirectoryplugin.com
* Description: Provides the ability to maintain a free or paid business directory on your WordPress powered site.
- * Version: 6.4.21
+ * Version: 6.4.22
* Author: Business Directory Team
* Author URI: https://businessdirectoryplugin.com
* Text Domain: business-directory-plugin
--- a/business-directory-plugin/includes/class-wpbdp.php
+++ b/business-directory-plugin/includes/class-wpbdp.php
@@ -55,7 +55,7 @@
}
private function setup_constants() {
- define( 'WPBDP_VERSION', '6.4.21' );
+ define( 'WPBDP_VERSION', '6.4.22' );
define( 'WPBDP_PATH', wp_normalize_path( plugin_dir_path( WPBDP_PLUGIN_FILE ) ) );
define( 'WPBDP_INC', trailingslashit( WPBDP_PATH . 'includes' ) );
--- a/business-directory-plugin/includes/compatibility/class-cpt-compat-mode.php
+++ b/business-directory-plugin/includes/compatibility/class-cpt-compat-mode.php
@@ -11,6 +11,7 @@
add_filter( 'wpbdp_current_view', array( $this, 'maybe_change_current_view' ) );
add_action( 'wpbdp_before_dispatch', array( $this, 'before_dispatch' ) );
add_action( 'wpbdp_after_dispatch', array( $this, 'after_dispatch' ) );
+ add_action( 'admin_bar_menu', array( $this, 'update_admin_bar_edit_link' ), 90 );
}
public function maybe_change_current_view( $viewname ) {
@@ -50,6 +51,44 @@
return wpbdp_get_post_by_id_or_slug( $id_or_slug, 'id', 'id' );
}
+ /**
+ * Update the admin bar edit link to point to the listing.
+ *
+ * @since 6.4.22
+ *
+ * @param WP_Admin_Bar $wp_admin_bar The WP_Admin_Bar instance.
+ *
+ * @return void
+ */
+ public function update_admin_bar_edit_link( $wp_admin_bar ) {
+ if ( is_admin() || ! is_admin_bar_showing() ) {
+ return;
+ }
+
+ if ( 'show_listing' !== wpbdp_current_view() ) {
+ return;
+ }
+
+ $listing_id = $this->get_listing_id_from_query_var();
+ if ( ! $listing_id || ! current_user_can( 'edit_post', $listing_id ) ) {
+ return;
+ }
+
+ $edit_link = get_edit_post_link( $listing_id );
+ if ( ! $edit_link ) {
+ return;
+ }
+
+ $node = $wp_admin_bar->get_node( 'edit' );
+ if ( ! $node ) {
+ return;
+ }
+
+ $node->href = $edit_link;
+ $node->title = esc_html__( 'Edit Listing', 'business-directory-plugin' );
+ $wp_admin_bar->add_node( $node );
+ }
+
public function before_dispatch() {
global $wp_query;
--- a/business-directory-plugin/includes/controllers/pages/class-checkout.php
+++ b/business-directory-plugin/includes/controllers/pages/class-checkout.php
@@ -125,14 +125,19 @@
private function fetch_payment() {
$payment_id = wpbdp_get_var( array( 'param' => 'payment' ), 'request' );
+
+ if ( ! is_string( $payment_id ) ) {
+ wp_die( __( 'Invalid Payment ID/key', 'business-directory-plugin' ) );
+ }
+
if ( ! $this->payment_id && ! empty( $payment_id ) ) {
- $this->payment = WPBDP_Payment::objects()->get( array( 'payment_key' => $payment_id ) );
+ $this->payment = WPBDP_Payment::objects()->get( array( 'payment_key' => sanitize_text_field( $payment_id ) ) );
} elseif ( $this->payment_id ) {
$this->payment = WPBDP_Payment::objects()->get( $this->payment_id );
}
if ( ! $this->payment ) {
- wp_die( 'Invalid Payment ID/key' );
+ wp_die( __( 'Invalid Payment ID/key', 'business-directory-plugin' ) );
}
$this->payment_id = $this->payment->id;
--- a/business-directory-plugin/includes/db/class-db-query-set.php
+++ b/business-directory-plugin/includes/db/class-db-query-set.php
@@ -207,7 +207,13 @@
}
if ( is_array( $v ) ) {
- $filters[] = "$f IN ('" . implode( '','', $v ) . "')";
+ if ( empty( $v ) ) {
+ $filters[] = '1 = 0';
+ } else {
+ $placeholders = implode( ', ', array_fill( 0, count( $v ), '%s' ) );
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $f is a trusted column name.
+ $filters[] = $this->db->prepare( "$f IN ($placeholders)", ...array_values( $v ) );
+ }
} else {
$filters[] = $this->db->prepare( "$f $op %s", $v );
}
--- a/business-directory-plugin/includes/gateways/class-stripe-gateway.php
+++ b/business-directory-plugin/includes/gateways/class-stripe-gateway.php
@@ -422,6 +422,13 @@
'quantity' => 1,
),
);
+
+ // Include payment ID in payment intent metadata for retry handling.
+ $parameters['payment_intent_data'] = array(
+ 'metadata' => array(
+ 'wpbdp_payment_id' => $payment->id,
+ ),
+ );
}
return $parameters;
--- a/business-directory-plugin/includes/gateways/stripe/controllers/classStrpEventsController.php
+++ b/business-directory-plugin/includes/gateways/stripe/controllers/classStrpEventsController.php
@@ -190,13 +190,7 @@
switch ( $this->event->type ) {
case 'invoice.payment_failed':
- if ( $parent_payment && 'stripe' === $parent_payment->gateway ) {
- $cancel = WPBDPStrpConnectHelper::cancel_subscription( $subscription->get_subscription_id() );
- if ( $cancel ) {
- // Mark as canceled in BD.
- $subscription->cancel();
- }
- }
+ $this->process_invoice_payment_failed( $subscription, $parent_payment );
break;
case 'invoice.payment_succeeded':
if ( ! $subscription ) {
@@ -217,6 +211,35 @@
}
/**
+ * Handle invoice.payment_failed events.
+ *
+ * @since 6.4.22
+ *
+ * @param WPBDP__Listing_Subscription|null $subscription The subscription object.
+ * @param object|null $parent_payment The parent payment object.
+ *
+ * @return void
+ */
+ private function process_invoice_payment_failed( $subscription, $parent_payment ) {
+ if ( ! $parent_payment || 'stripe' !== $parent_payment->gateway ) {
+ return;
+ }
+
+ // Soft cancel: Mark listing as expired but preserve subscription data for potential recovery.
+ $listing = wpbdp_get_listing( $parent_payment->listing_id );
+
+ if ( ! $listing ) {
+ return;
+ }
+
+ $listing->set_status( 'expired' );
+ $listing->set_post_status( 'draft' );
+
+ // Store metadata to track this was a payment failure (for recovery on successful retry).
+ update_post_meta( $parent_payment->listing_id, '_wpbdp_stripe_payment_failed', time() );
+ }
+
+ /**
* @return object|null
*/
private function maybe_create_listing_subscription() {
@@ -267,20 +290,11 @@
* @return void
*/
private function process_payment_intent() {
- $event = $this->event->data;
-
if ( empty( $this->invoice->id ) || 'manual' === $this->invoice->confirmation_method ) {
return;
}
- $checkout = $this->verify_transaction();
- if ( ! $checkout ) {
- return;
- }
-
- $checkout = array_shift( $checkout );
- $payment = wpbdp_get_payment( $checkout->data->object->client_reference_id );
-
+ $payment = $this->find_payment_for_intent();
if ( ! $payment || 'completed' === $payment->status ) {
return;
}
@@ -299,6 +313,29 @@
}
$payment->save();
+ $this->maybe_reactivate_listing( $payment->listing_id );
+ }
+
+ /**
+ * Find the WPBDP payment associated with a payment intent.
+ *
+ * @since 6.4.22
+ *
+ * @return WPBDP_Payment|null The payment object if found, null otherwise.
+ */
+ private function find_payment_for_intent() {
+ $checkout = $this->verify_transaction();
+ if ( $checkout ) {
+ $checkout = array_shift( $checkout );
+ return wpbdp_get_payment( $checkout->data->object->client_reference_id );
+ }
+
+ // Fallback: Check if payment intent has metadata with payment ID.
+ if ( ! empty( $this->invoice->metadata->wpbdp_payment_id ) ) {
+ return wpbdp_get_payment( $this->invoice->metadata->wpbdp_payment_id );
+ }
+
+ return null;
}
/**
@@ -380,6 +417,8 @@
$parent_payment->gateway_tx_id = $invoice->charge;
$parent_payment->gateway = 'stripe';
$parent_payment->save();
+
+ $this->maybe_reactivate_listing( $parent_payment->listing_id );
return;
}
@@ -402,5 +441,32 @@
)
);
$subscription->renew();
+ $this->maybe_reactivate_listing( $parent_payment->listing_id );
+ }
+
+ /**
+ * Reactivate a listing if it was previously marked as failed.
+ *
+ * @since 6.4.22
+ *
+ * @param int $listing_id The listing ID to check and potentially reactivate.
+ *
+ * @return void
+ */
+ private function maybe_reactivate_listing( $listing_id ) {
+ $failed_timestamp = get_post_meta( $listing_id, '_wpbdp_stripe_payment_failed', true );
+ if ( ! $failed_timestamp ) {
+ return;
+ }
+
+ $listing = wpbdp_get_listing( $listing_id );
+ if ( ! $listing ) {
+ return;
+ }
+
+ $listing->set_status( 'complete' );
+ $listing->set_post_status( 'publish' );
+
+ delete_post_meta( $listing_id, '_wpbdp_stripe_payment_failed' );
}
}
--- a/business-directory-plugin/includes/helpers/class-email.php
+++ b/business-directory-plugin/includes/helpers/class-email.php
@@ -138,11 +138,14 @@
return false;
}
if ( $this->template ) {
+ $prepared_body = str_ireplace( array( '<br>', '<br/>', '<br />' ), "n", $this->body );
+ $prepared_body = nl2br( $prepared_body );
+
if ( $html_ = wpbdp_render(
$this->template,
array(
'subject' => $this->subject,
- 'body' => $this->body,
+ 'body' => $prepared_body,
)
) ) {
$this->html = $html_;
--- a/business-directory-plugin/includes/helpers/functions/general.php
+++ b/business-directory-plugin/includes/helpers/functions/general.php
@@ -979,6 +979,10 @@
return wpbdp_url( sprintf( '/%s/%s/', wpbdp_get_option( 'permalinks-' . $taxonomy_type . '-slug' ), $taxonomy->slug ) );
}
+/**
+ * Use this when the template should not be overridden, or it has already
+ * gone through the override check.
+ */
function wpbdp_render_page( $template, $vars = array(), $echo_output = false ) {
if ( empty( $template ) ) {
return '';
@@ -1003,62 +1007,73 @@
return $html;
}
-function wpbdp_locate_template( $template, $allow_override = true, $try_defaults = true ) {
+/**
+ * Also used to get taxonomy template when WPBDP is not using CPT.
+ *
+ * @uses WPBDP_Themes::locate_template()
+ *
+ * @used-by wpbdp_render()
+ */
+function wpbdp_locate_template( $template, $allow_override = true ) {
$template_file = '';
if ( ! is_array( $template ) ) {
$template = array( $template );
}
- if ( $allow_override ) {
- global $wpbdp;
-
- $search_for = array();
- $template_file = '';
-
+ if ( ! $allow_override ) {
+ // Only get the core file if it hasn't already been checked.
foreach ( $template as $t ) {
- $template_file = $wpbdp->themes->locate_template( $t );
- if ( $template_file ) {
- break;
- }
+ $template_path = WPBDP_TEMPLATES_PATH . '/' . $t . '.tpl.php';
- // These file checks could be a little risky and get unintended results.
- if ( wpbdp_get_option( 'disable-cpt' ) ) {
- $search_for[] = $t . '.tpl.php';
- $search_for[] = $t . '.php';
- $search_for[] = 'single/' . $t . '.tpl.php';
- $search_for[] = 'single/' . $t . '.php';
+ if ( file_exists( $template_path ) ) {
+ $template_file = $template_path;
+ break;
}
}
-
- // Check for the template in the WP theme.
- if ( empty( $template_file ) ) {
- $template_file = locate_template( $search_for );
- }
+ return $template_file;
}
- if ( $template_file && ! $try_defaults ) {
- _deprecated_argument( __FUNCTION__, '5.13.2', 'Defaults are always checked here. Use $wpbdp->themes->template_has_override' );
+ global $wpbdp;
- // Temporary reverse compatibility: The BD folder was checked when it shouldn't be. Remove it.
- if ( strpos( $template_file, WPBDP_TEMPLATES_PATH ) !== false ) {
- $template_file = '';
+ $search_for = array();
+ $template_file = '';
+
+ foreach ( $template as $t ) {
+ $template_file = $wpbdp->themes->locate_template( $t );
+ if ( $template_file ) {
+ break;
}
- } elseif ( ! $allow_override ) {
- // Only get the core file if it hasn't already been checked.
- foreach ( $template as $t ) {
- $template_path = WPBDP_TEMPLATES_PATH . '/' . $t . '.tpl.php';
- if ( file_exists( $template_path ) ) {
- $template_file = $template_path;
- break;
- }
+ // These file checks could be a little risky and get unintended results.
+ if ( wpbdp_get_option( 'disable-cpt' ) ) {
+ $search_for[] = $t . '.tpl.php';
+ $search_for[] = $t . '.php';
+ $search_for[] = 'single/' . $t . '.tpl.php';
+ $search_for[] = 'single/' . $t . '.php';
+ }
+ }
+
+ // Check for the template in the WP theme when CPT is disabled.
+ if ( empty( $template_file ) ) {
+ $template_file = locate_template( $search_for );
+ if ( $template_file ) {
+ $expected = 'business-directory/' . str_replace( ' ', '-', $template[0] ) . '.tpl.php';
+ _deprecated_argument(
+ __FUNCTION__,
+ '6.4',
+ 'This template will not be used in a future release: ' . $template_file .
+ '. Please use the template file ' . $expected . ' instead.'
+ );
}
}
return $template_file;
}
+/**
+ * @uses wpbdp_render_page()
+ */
function wpbdp_render( $template, $vars = array(), $allow_override = true ) {
$vars = wp_parse_args(
$vars,
--- a/business-directory-plugin/includes/themes.php
+++ b/business-directory-plugin/includes/themes.php
@@ -55,7 +55,9 @@
* @since 5.13.2
*/
private function set_template_dirs() {
- // Theme BD template dir is priority 1.
+ $this->add_wp_theme_dir();
+
+ // Theme BD template dir is priority 2.
$theme = $this->get_active_theme_data();
$this->template_dirs['bd'] = $theme->path . 'templates/';
@@ -63,6 +65,29 @@
}
/**
+ * Get the paths from locate_template() in WordPress core.
+ *
+ * @since 6.4.22
+ *
+ * @return void
+ */
+ private function add_wp_theme_dir() {
+ $stylesheet_path = get_stylesheet_directory();
+ $template_path = get_template_directory();
+ $is_child_theme = $stylesheet_path !== $template_path;
+
+ $bd_folder = '/business-directory/';
+
+ // WP theme template dir is priority 1.
+ $this->template_dirs['wp'] = $stylesheet_path . $bd_folder;
+
+ if ( $is_child_theme ) {
+ // Check the parent theme.
+ $this->template_dirs['wp-parent'] = $template_path . $bd_folder;
+ }
+ }
+
+ /**
* @since 5.13.2
*/
private function add_core_template_dir() {
@@ -583,16 +608,8 @@
}
array_push( $this->cache['template_vars_stack'], $vars );
- extract( $vars );
-
- ob_start();
- include $path;
- $html = ob_get_contents();
- ob_end_clean();
- if ( isset( $__template__['blocks'] ) && is_array( $__template__['blocks'] ) ) {
- $template_meta['blocks'] = array_merge( $__template__['blocks'], $template_meta['blocks'] );
- }
+ $html = wpbdp_render_page( $path, $vars );
$is_part = isset( $vars['_part'] ) && $vars['_part'];
@@ -826,6 +843,17 @@
}
}
+ /**
+ * Find the template file in the theme, core, or custom location.
+ *
+ * @used-by wpbdp_render()
+ * @used-by wpbdp_x_render()
+ * @used-by wpbdp_x_part()
+ *
+ * @param string $id Template name.
+ *
+ * @return bool|string Path to template file or false if not found.
+ */
public function locate_template( $id ) {
$id = str_replace( '.tpl.php', '', $id );
@@ -834,24 +862,26 @@
}
$filename = str_replace( ' ', '-', $id ) . '.tpl.php';
- $path = locate_template( 'business-directory/' . $filename );
+ $path = false;
// Find the template.
foreach ( $this->template_dirs as $p ) {
- if ( empty( $path ) && file_exists( $p . $filename ) ) {
+ if ( file_exists( $p . $filename ) ) {
$path = $p . $filename;
}
+ if ( empty( $path ) ) {
+ continue;
+ }
+
+ /**
+ * Allow override since the order isn't the most dependable indicator.
+ *
+ * @since 5.9.2
+ */
+ $path = apply_filters( 'wpbdp_use_template_' . $id, $path );
if ( $path ) {
- /**
- * Allow override since the order isn't the most dependable indicator.
- *
- * @since 5.9.2
- */
- $path = apply_filters( 'wpbdp_use_template_' . $id, $path );
- if ( $path ) {
- break;
- }
+ break;
}
}
--- a/business-directory-plugin/templates/excerpt.tpl.php
+++ b/business-directory-plugin/templates/excerpt.tpl.php
@@ -2,10 +2,10 @@
/**
* Template listing excerpt view.
*
+ * Template Blocks: before, after
+ *
* @package BDP/Templates/Excerpt
*/
-
-$__template__ = array( 'blocks' => array( 'before', 'after' ) );
?>
<div id="<?php echo esc_attr( $listing_css_id ); ?>" class="<?php echo esc_attr( $listing_css_class ); ?>" data-breakpoints='{"medium": [560,780], "large": [780,999999]}' data-breakpoints-class-prefix="wpbdp-listing-excerpt">
<?php