Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/latepoint/blocks/build/book-button/index.asset.php
+++ b/latepoint/blocks/build/book-button/index.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => '95be338c3b59bfcb5992');
+<?php return array('dependencies' => array('react', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => '726fd945d15d002e9ba4');
--- a/latepoint/blocks/build/book-form/index.asset.php
+++ b/latepoint/blocks/build/book-form/index.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-element', 'wp-i18n'), 'version' => '79e174e6d3109daacdc5');
+<?php return array('dependencies' => array('react', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-element', 'wp-i18n'), 'version' => 'e20858903a3ccc791cb2');
--- a/latepoint/blocks/build/calendar/index.asset.php
+++ b/latepoint/blocks/build/calendar/index.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-element', 'wp-i18n'), 'version' => 'c4b5732492cf5bd52fa4');
+<?php return array('dependencies' => array('react', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-element', 'wp-i18n'), 'version' => '680993f12482fe87a034');
--- a/latepoint/blocks/build/customer-dashboard/index.asset.php
+++ b/latepoint/blocks/build/customer-dashboard/index.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-element'), 'version' => '33b6a428a617384d7197');
+<?php return array('dependencies' => array('react', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-element'), 'version' => 'e4f494ba44b3ef46bdde');
--- a/latepoint/blocks/build/customer-login/index.asset.php
+++ b/latepoint/blocks/build/customer-login/index.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-element'), 'version' => 'b7a63ba59fb4ba473459');
+<?php return array('dependencies' => array('react', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-element'), 'version' => 'db0a79e3d3d40dce9b07');
--- a/latepoint/blocks/build/list-of-resources/index.asset.php
+++ b/latepoint/blocks/build/list-of-resources/index.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => '0cd215311772506d571c');
+<?php return array('dependencies' => array('react', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => '96676eed537737402242');
--- a/latepoint/latepoint.php
+++ b/latepoint/latepoint.php
@@ -2,7 +2,7 @@
/**
* Plugin Name: LatePoint
* Description: Appointment Scheduling Software for WordPress
- * Version: 5.6.2
+ * Version: 5.6.3
* Author: LatePoint
* Author URI: https://latepoint.com
* Plugin URI: https://latepoint.com
@@ -29,8 +29,8 @@
* LatePoint version.
*
*/
- public $version = '5.6.2';
- public $db_version = '2.3.0';
+ public $version = '5.6.3';
+ public $db_version = '2.3.1';
/**
--- a/latepoint/lib/controllers/form_fields_controller.php
+++ b/latepoint/lib/controllers/form_fields_controller.php
@@ -33,6 +33,7 @@
}
public function update_default_fields() {
+ $this->check_nonce( 'update_default_fields' );
$updated_fields = $this->params['default_fields'];
$default_fields = OsSettingsHelper::get_default_fields_for_customer();
$fields_to_save = [];
--- a/latepoint/lib/controllers/razorpay_connect_controller.php
+++ b/latepoint/lib/controllers/razorpay_connect_controller.php
@@ -233,6 +233,7 @@
}
public function disconnect_connect_account() {
+ $this->check_nonce( 'razorpay_disconnect_account' );
$env = $this->get_env_from_params();
try {
$account_id = OsRazorpayConnectHelper::get_connect_account_id( $env );
--- a/latepoint/lib/controllers/stripe_connect_controller.php
+++ b/latepoint/lib/controllers/stripe_connect_controller.php
@@ -144,6 +144,7 @@
}
public function disconnect_connect_account() {
+ $this->check_nonce( 'stripe_disconnect_account' );
$env = $this->get_env_from_params();
try {
$path = 'server-tokens/' . OsStripeConnectHelper::get_server_token( $env ) . '/disconnect/';
--- a/latepoint/lib/controllers/wizard_controller.php
+++ b/latepoint/lib/controllers/wizard_controller.php
@@ -131,6 +131,7 @@
}
function next_step() {
+ $this->check_nonce( 'wizard_step' );
$this->show_prev_btn = true;
$this->show_next_btn = true;
@@ -176,6 +177,7 @@
}
function prev_step() {
+ $this->check_nonce( 'wizard_step' );
// For every back step there is next step.
$this->show_next_btn = true;
@@ -396,6 +398,7 @@
}
function skip_setup() {
+ $this->check_nonce( 'wizard_step' );
$current_step = isset( $this->params['current_step_code'] ) ? sanitize_text_field( $this->params['current_step_code'] ) : 'unknown';
$analytics = get_option( 'latepoint_onboarding_analytics', [] );
--- a/latepoint/lib/helpers/analytics_helper.php
+++ b/latepoint/lib/helpers/analytics_helper.php
@@ -26,8 +26,8 @@
require_once LATEPOINT_ABSPATH . 'lib/kit/bsf-analytics/class-bsf-analytics-loader.php';
}
- if ( ! class_exists( 'Astra_Notices' ) ) {
- require_once LATEPOINT_ABSPATH . 'lib/kit/astra-notices/class-astra-notices.php';
+ if ( ! class_exists( 'BSF_Admin_Notices' ) ) {
+ require_once LATEPOINT_ABSPATH . 'lib/kit/astra-notices/class-bsf-admin-notices.php';
}
$bsf_analytics = BSF_Analytics_Loader::get_instance();
--- a/latepoint/lib/helpers/booking_helper.php
+++ b/latepoint/lib/helpers/booking_helper.php
@@ -336,7 +336,7 @@
'end_time' => ( $day->format( 'Y-m-d' ) < $block_end_datetime->format( 'Y-m-d' ) ) ? 24 * 60 : OsTimeHelper::convert_datetime_to_minutes( $block_end_datetime ),
'start_date' => $day->format( 'Y-m-d' ),
'end_date' => $day->format( 'Y-m-d' ),
- ]
+ ]
);
}
}
@@ -359,12 +359,12 @@
'end_time' => 24 * 60,
'start_date' => $day->format( 'Y-m-d' ),
'end_date' => $day->format( 'Y-m-d' ),
- ]
+ ]
);
}
}
}
- }
+ }
}
$grouped_blocked_periods = apply_filters( 'latepoint_blocked_periods_for_range', $grouped_blocked_periods, $filter );
@@ -448,7 +448,7 @@
[
'start_date >=' => $filter->date_from,
'start_date <=' => $filter->date_to,
- ]
+ ]
);
} else {
# only start_date provided - means it's a specific date requested
@@ -510,7 +510,7 @@
'dtend' => $booking->format_end_date_and_time_for_google(),
'summary' => $booking_description,
'url' => get_site_url(),
- )
+ )
);
return $ics->to_string();
@@ -664,6 +664,18 @@
$bundles = $bundles_model->should_be_active()->should_not_be_hidden()->get_results_as_models();
}
+ /**
+ * Filters the list of bundles shown on the services step of the booking form.
+ *
+ * @since 5.6.3
+ * @hook latepoint_booking_generate_bundles_folder
+ *
+ * @param {OsBundleModel[]} $bundles List of active, non-hidden bundles to be displayed
+ *
+ * @returns {OsBundleModel[]} Filtered list of bundles to display
+ */
+ $bundles = apply_filters( 'latepoint_booking_generate_bundles_folder', $bundles );
+
if ( $bundles ) {
/**
* Whether to skip the "Bundle & Save" folder header and render bundle
@@ -718,12 +730,34 @@
<?php } ?>
</span>
- <?php if ( $bundle->charge_amount > 0 ) { ?>
+ <?php if ( $bundle->price_min > 0 ) { ?>
<span class="os-item-price-w">
- <span class="os-item-price">
- <?php echo esc_html( OsMoneyHelper::format_price( $bundle->charge_amount ) ); ?>
- </span>
- </span>
+ <span class="os-item-price">
+ <?php
+ /**
+ * Filters the display price value shown on the bundle tile on a booking form
+ *
+ * @since 5.1.94
+ * @hook latepoint_booking_form_display_bundle_price
+ *
+ * @param {string} $price displayed price that will be outputted
+ * @param {OsBundleModel} $bundle Bundle that the price is displayed for
+ *
+ * @returns {string} Filtered displayed price
+ */
+ $display_price = apply_filters( 'latepoint_booking_form_display_bundle_price', $bundle->price_min_formatted, $bundle );
+ echo esc_html( $display_price ); ?>
+ </span>
+ <?php if ( $bundle->price_min != $bundle->price_max ) { ?>
+ <span class="os-item-price-label"><?php esc_html_e( 'Starts From', 'latepoint' ); ?></span>
+ <?php } ?>
+ </span>
+ <?php } elseif ( $bundle->charge_amount > 0 ) { ?>
+ <span class="os-item-price-w">
+ <span class="os-item-price">
+ <?php echo esc_html( OsMoneyHelper::format_price( $bundle->charge_amount ) ); ?>
+ </span>
+ </span>
<?php } ?>
</div>
</div>
@@ -835,7 +869,7 @@
' NOT IN' => $settings['show_service_categories_arr'],
],
],
- ]
+ ]
);
}
}
@@ -938,8 +972,8 @@
}
public static function group_booking_btn_html( $booking_id = false ) {
- $html = 'data-os-params="' . esc_attr( http_build_query( [ 'booking_id' => $booking_id ] ) ) . '"
- data-os-action="' . esc_attr( OsRouterHelper::build_route_name( 'bookings', 'grouped_bookings_quick_view' ) ) . '"
+ $html = 'data-os-params="' . esc_attr( http_build_query( [ 'booking_id' => $booking_id ] ) ) . '"
+ data-os-action="' . esc_attr( OsRouterHelper::build_route_name( 'bookings', 'grouped_bookings_quick_view' ) ) . '"
data-os-output-target="lightbox"
data-os-lightbox-classes="width-500"
data-os-after-call="latepoint_init_grouped_bookings_form"';
@@ -955,8 +989,8 @@
$route = OsRouterHelper::build_route_name( 'orders', 'quick_edit' );
$params_str = http_build_query( $params );
- $html = 'data-os-params="' . esc_attr( $params_str ) . '"
- data-os-action="' . esc_attr( $route ) . '"
+ $html = 'data-os-params="' . esc_attr( $params_str ) . '"
+ data-os-action="' . esc_attr( $route ) . '"
data-os-output-target="side-panel"
data-os-after-call="latepoint_init_quick_order_form"';
@@ -1520,7 +1554,7 @@
[
'created_at >=' => $date_from->format( 'Y-m-d' ),
'created_at <=' => $date_to->format( 'Y-m-d' ),
- ]
+ ]
)->count();
}
@@ -1553,7 +1587,7 @@
[
'start_date >=' => $date_from,
'start_date <= ' => $date_to,
- ]
+ ]
);
if ( $filter->service_id ) {
@@ -1587,7 +1621,7 @@
[
'start_date >=' => $date_from,
'start_date <=' => $date_to,
- ]
+ ]
)
->where( [ 'status NOT IN' => OsCalendarHelper::get_booking_statuses_hidden_from_calendar() ] );
if ( $filter->service_id ) {
@@ -1952,7 +1986,7 @@
'theme' => 'bordered',
'style' => 'width: 60px;',
'class' => 'os-table-filter',
- ]
+ ]
) . '</th>';
case 'service':
@@ -1964,7 +1998,7 @@
[
'placeholder' => __( 'All Services', 'latepoint' ),
'class' => 'os-table-filter',
- ]
+ ]
) . '</th>';
case 'datetime':
@@ -1996,7 +2030,7 @@
[
'placeholder' => __( 'Show All', 'latepoint' ),
'class' => 'os-table-filter',
- ]
+ ]
) . '</th>';
case 'agent':
@@ -2008,7 +2042,7 @@
[
'placeholder' => __( 'All Agents', 'latepoint' ),
'class' => 'os-table-filter',
- ]
+ ]
) . '</th>';
case 'location':
@@ -2020,7 +2054,7 @@
[
'placeholder' => __( 'All Locations', 'latepoint' ),
'class' => 'os-table-filter',
- ]
+ ]
) . '</th>';
case 'customer':
@@ -2032,7 +2066,7 @@
'class' => 'os-table-filter',
'theme' => 'bordered',
'placeholder' => __( 'Search by Customer', 'latepoint' ),
- ]
+ ]
) . '</th>';
case 'status':
@@ -2044,7 +2078,7 @@
[
'placeholder' => __( 'Show All', 'latepoint' ),
'class' => 'os-table-filter',
- ]
+ ]
) . '</th>';
case 'payment_status':
@@ -2056,7 +2090,7 @@
[
'placeholder' => __( 'Show All', 'latepoint' ),
'class' => 'os-table-filter',
- ]
+ ]
) . '</th>';
case 'created_on':
@@ -2096,7 +2130,7 @@
'class' => 'os-table-filter',
'theme' => 'bordered',
'placeholder' => $extra_label,
- ]
+ ]
) . '</th>';
}
}
@@ -2154,8 +2188,8 @@
[
'agent_id' => $booking->agent_id,
'booking_id' => $booking->id,
- ]
- )
+ ]
+ )
) . '">' .
'<i class="latepoint-icon latepoint-icon-more-horizontal"></i>' .
'</div>' .
@@ -2177,8 +2211,8 @@
[
'customer_id' => $booking->customer_id,
'booking_id' => $booking->id,
- ]
- )
+ ]
+ )
) . '">' .
'<i class="latepoint-icon latepoint-icon-more-horizontal"></i>' .
'</div>' .
--- a/latepoint/lib/helpers/database_helper.php
+++ b/latepoint/lib/helpers/database_helper.php
@@ -661,6 +661,8 @@
short_description text,
charge_amount decimal(20,4),
deposit_amount decimal(20,4),
+ price_min decimal(20,4),
+ price_max decimal(20,4),
status varchar(20) NOT NULL,
visibility varchar(20) NOT NULL,
order_number int(11),
--- a/latepoint/lib/helpers/razorpay_connect_helper.php
+++ b/latepoint/lib/helpers/razorpay_connect_helper.php
@@ -466,7 +466,7 @@
data-os-pass-this="yes"
data-os-before-after="none"
data-os-after-call="latepointRazorpayConnectAdmin.reload_connect_status_wrapper"
- data-os-params="' . esc_attr( OsUtilHelper::build_os_params( [ 'env' => $env ] ) ) . '"
+ data-os-params="' . esc_attr( OsUtilHelper::build_os_params( [ 'env' => $env ], 'razorpay_disconnect_account' ) ) . '"
data-os-action="' . OsRouterHelper::build_route_name( 'razorpay_connect', 'disconnect_connect_account' ) . '"
><i class="latepoint-icon latepoint-icon-x"></i><span>' . __( 'disconnect', 'latepoint' ) . '</span></a>';
if ( $charges_enabled ) {
--- a/latepoint/lib/helpers/settings_helper.php
+++ b/latepoint/lib/helpers/settings_helper.php
@@ -865,6 +865,7 @@
?>
<div class="os-default-fields" data-route="<?php echo esc_attr( OsRouterHelper::build_route_name( 'form_fields', 'update_default_fields' ) ); ?>">
<form>
+ <?php wp_nonce_field( 'update_default_fields' ); ?>
<?php foreach ( $default_fields as $name => $default_field ) {
$atts = [];
?>
--- a/latepoint/lib/helpers/steps_helper.php
+++ b/latepoint/lib/helpers/steps_helper.php
@@ -1046,9 +1046,13 @@
self::$presets['selected_agent'] = $presets['selected_agent'];
}
- // preselected service
+ // preselected service — reject hidden services so they can never be preset-selected
if ( isset( $presets['selected_service'] ) && is_numeric( $presets['selected_service'] ) ) {
self::$presets['selected_service'] = $presets['selected_service'];
+ // $preset_service = new OsServiceModel( $presets['selected_service'] );
+ // if ( $preset_service->id && $preset_service->is_active() && ! $preset_service->is_hidden() ) {
+ // self::$presets['selected_service'] = $presets['selected_service'];
+ // }
}
// preselected bundle
@@ -1092,7 +1096,8 @@
* @hook latepoint_set_presets
*
*/
- return apply_filters( 'latepoint_set_presets', self::$presets, $presets );
+ self::$presets = apply_filters( 'latepoint_set_presets', self::$presets, $presets );
+ return self::$presets;
}
@@ -1184,6 +1189,21 @@
self::$customer_object = OsAuthHelper::get_logged_in_customer();
}
}
+
+ /**
+ * Filters the customer object after it has been set from submitted params or session.
+ * Addons may use this hook to pre-populate empty fields (e.g. from URL parameters)
+ * without overriding values already present on the object.
+ *
+ * @param {OsCustomerModel} $customer_object The customer object.
+ * @param {array} $customer_params The raw customer params used to build the object.
+ *
+ * @returns {OsCustomerModel} Filtered customer object.
+ * @since 5.2.0
+ * @hook latepoint_set_customer_object
+ *
+ */
+ self::$customer_object = apply_filters( 'latepoint_set_customer_object', self::$customer_object, $customer_params );
return self::$customer_object;
}
@@ -1679,6 +1699,21 @@
public static function process_step_booking() {
+ // A hidden service must never advance through any booking step.
+ // This runs before every booking__* sub-step (services, locations, agents, datepicker).
+ if ( ! empty( self::$booking_object->service_id ) ) {
+ $service = ( self::$booking_object->service instanceof OsServiceModel && self::$booking_object->service->id )
+ ? self::$booking_object->service
+ : new OsServiceModel( self::$booking_object->service_id );
+ if ( $service->id && $service->is_hidden() ) {
+ return new WP_Error(
+ 'service_not_available',
+ __( 'Selected service is not available.', 'latepoint' ),
+ [ 'send_to_step' => 'booking__services' ]
+ );
+ }
+ }
+
if ( ! self::is_bundle_scheduling() ) {
// check if we are processing the last step of a booking sequence
$booking_steps = [];
@@ -1708,6 +1743,7 @@
// SERVICES
public static function process_step_booking__services() {
+ // Hidden-service guard runs in the parent process_step_booking() before every booking__* sub-step.
}
public static function prepare_step_booking__services() {
--- a/latepoint/lib/helpers/stripe_connect_helper.php
+++ b/latepoint/lib/helpers/stripe_connect_helper.php
@@ -437,7 +437,7 @@
data-os-pass-this="yes"
data-os-before-after="none"
data-os-after-call="latepointStripeConnectAdmin.reload_connect_status_wrapper"
- data-os-params="' . OsUtilHelper::build_os_params( [ 'env' => $env ] ) . '"
+ data-os-params="' . esc_attr( OsUtilHelper::build_os_params( [ 'env' => $env ], 'stripe_disconnect_account' ) ) . '"
data-os-action="' . OsRouterHelper::build_route_name( 'stripe_connect', 'disconnect_connect_account' ) . '"
><i class="latepoint-icon latepoint-icon-x"></i><span>' . __( 'disconnect', 'latepoint' ) . '</span></a>';
if ( $charges_enabled ) {
--- a/latepoint/lib/kit/astra-notices/class-astra-notices.php
+++ b/latepoint/lib/kit/astra-notices/class-astra-notices.php
@@ -1,417 +0,0 @@
-<?php
-/**
- * Astra Notices
- *
- * An easy to use PHP Library to add dismissible admin notices in the WordPress admin.
- *
- * @package Astra Notices
- * @since 1.0.0
- */
-
-if ( ! defined( 'ABSPATH' ) ) {
- exit; // Exit if accessed directly.
-}
-
-if ( ! class_exists( 'Astra_Notices' ) ) :
-
- /**
- * Astra_Notices
- *
- * @since 1.0.0
- */
- class Astra_Notices {
-
- /**
- * Notices
- *
- * @access private
- * @var array Notices.
- * @since 1.0.0
- */
- private static $version = '1.1.16';
-
- /**
- * Notices
- *
- * @access private
- * @var array Notices.
- * @since 1.0.0
- */
- private static $notices = array();
-
- /**
- * Instance
- *
- * @access private
- * @var object Class object.
- * @since 1.0.0
- */
- private static $instance;
-
- /**
- * Initiator
- *
- * @since 1.0.0
- * @return object initialized object of class.
- */
- public static function get_instance() {
- if ( ! isset( self::$instance ) ) {
- self::$instance = new self();
- }
- return self::$instance;
- }
-
- /**
- * Constructor
- *
- * @since 1.0.0
- */
- public function __construct() {
- add_action( 'admin_notices', array( $this, 'show_notices' ), 30 );
- add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
- add_action( 'wp_ajax_astra-notice-dismiss', array( $this, 'dismiss_notice' ) );
- add_filter( 'wp_kses_allowed_html', array( $this, 'add_data_attributes' ), 10, 2 );
- }
-
- /**
- * Filters and Returns a list of allowed tags and attributes for a given context.
- *
- * @param array $allowedposttags array of allowed tags.
- * @param string $context Context type (explicit).
- * @since 1.0.0
- * @return array
- */
- public function add_data_attributes( $allowedposttags, $context ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
- $allowedposttags['a']['data-repeat-notice-after'] = true;
-
- return $allowedposttags;
- }
-
- /**
- * Add Notice.
- *
- * @since 1.0.0
- * @param array $args Notice arguments.
- * @return void
- */
- public static function add_notice( $args = array() ) {
- self::$notices[] = $args;
-
- if ( ! isset( $args['id'] ) ) {
- return;
- }
-
- $notice_id = sanitize_key( $args['id'] ); // Notice ID.
- $notices = get_option( 'allowed_astra_notices', array() );
- if ( ! in_array( $notice_id, $notices, true ) ) {
- $notices[] = $notice_id; // Add notice id to the array.
- update_option( 'allowed_astra_notices', $notices ); // Update the option.
- }
- }
-
- /**
- * Dismiss Notice.
- *
- * @since 1.0.0
- * @return void
- */
- public function dismiss_notice() {
- check_ajax_referer( 'astra-notices', 'nonce' );
-
- $notice_id = ( isset( $_POST['notice_id'] ) ) ? sanitize_key( wp_unslash( $_POST['notice_id'] ) ) : '';
- $repeat_notice_after = ( isset( $_POST['repeat_notice_after'] ) ) ? absint( $_POST['repeat_notice_after'] ) : '';
- $notice = $this->get_notice_by_id( $notice_id );
- $capability = isset( $notice['capability'] ) ? $notice['capability'] : 'manage_options';
-
- if ( ! apply_filters( 'astra_notices_user_cap_check', current_user_can( $capability ) ) ) {
- return;
- }
-
- $allowed_notices = get_option( 'allowed_astra_notices', array() ); // Get allowed notices.
-
- // Define restricted user meta keys.
- $wp_default_meta_keys = array(
- 'wp_capabilities',
- 'wp_user_level',
- 'wp_user-settings',
- 'account_status',
- 'session_tokens',
- );
-
- // if $notice_id does not start with astra-notices-id and notice_id is not from the allowed notices, then return.
- if ( strpos( $notice_id, 'astra-notices-id-' ) !== 0 && ( ! in_array( $notice_id, $allowed_notices, true ) ) ) {
- return;
- }
-
- // Valid inputs?
- if ( ! empty( $notice_id ) ) {
-
- if ( in_array( $notice_id, $wp_default_meta_keys, true ) ) {
- wp_send_json_error( esc_html__( 'Invalid notice ID.', 'astra-notices' ) );
- }
-
- if ( ! empty( $repeat_notice_after ) ) {
- set_transient( $notice_id, true, $repeat_notice_after );
- } else {
- update_user_meta( get_current_user_id(), $notice_id, 'notice-dismissed' );
- }
-
- wp_send_json_success();
- }
-
- wp_send_json_error();
- }
-
- /**
- * Enqueue Scripts.
- *
- * @since 1.0.0
- * @return void
- */
- public function enqueue_scripts() {
- wp_register_style( 'astra-notices', self::get_url() . 'notices.css', array(), self::$version );
- wp_register_script( 'astra-notices', self::get_url() . 'notices.js', array( 'jquery' ), self::$version, true );
- wp_localize_script(
- 'astra-notices',
- 'astraNotices',
- array(
- '_notice_nonce' => wp_create_nonce( 'astra-notices' ),
- )
- );
- }
-
- /**
- * Sort the notices based on the given priority of the notice.
- * This function is called from usort()
- *
- * @since 1.5.2
- * @param array $notice_1 First notice.
- * @param array $notice_2 Second Notice.
- * @return array
- */
- public function sort_notices( $notice_1, $notice_2 ) {
- if ( ! isset( $notice_1['priority'] ) ) {
- $notice_1['priority'] = 10;
- }
- if ( ! isset( $notice_2['priority'] ) ) {
- $notice_2['priority'] = 10;
- }
-
- return $notice_1['priority'] - $notice_2['priority'];
- }
-
- /**
- * Get all registered notices.
- * Since v1.1.8 it is recommended to register the notices on
- *
- * @return array|null
- */
- private function get_notices() {
- usort( self::$notices, array( $this, 'sort_notices' ) );
-
- return self::$notices;
- }
-
- /**
- * Get notice by notice_id
- *
- * @param string $notice_id Notice id.
- *
- * @return array notice based on the notice id.
- */
- private function get_notice_by_id( $notice_id ) {
- if ( empty( $notice_id ) ) {
- return array();
- }
-
- $notices = $this->get_notices();
- $notice = wp_list_filter(
- $notices,
- array(
- 'id' => $notice_id,
- )
- );
-
- return ( ! empty( $notice ) && isset( $notice[0] ) ) ? $notice[0] : array();
- }
-
- /**
- * Display the notices in the WordPress admin.
- *
- * @since 1.0.0
- * @return void
- */
- public function show_notices() {
- $defaults = array(
- 'id' => '', // Optional, Notice ID. If empty it set `astra-notices-id-<$array-index>`.
- 'type' => 'info', // Optional, Notice type. Default `info`. Expected [info, warning, notice, error].
- 'message' => '', // Optional, Message.
- 'show_if' => true, // Optional, Show notice on custom condition. E.g. 'show_if' => if( is_admin() ) ? true, false, .
- 'repeat-notice-after' => '', // Optional, Dismiss-able notice time. It'll auto show after given time.
- 'display-notice-after' => false, // Optional, Dismiss-able notice time. It'll auto show after given time.
- 'class' => '', // Optional, Additional notice wrapper class.
- 'priority' => 10, // Priority of the notice.
- 'display-with-other-notices' => true, // Should the notice be displayed if other notices are being displayed from Astra_Notices.
- 'is_dismissible' => true,
- 'capability' => 'manage_options', // User capability - This capability is required for the current user to see this notice.
- );
-
- // Count for the notices that are rendered.
- $notices_displayed = 0;
- $notices = $this->get_notices();
-
- foreach ( $notices as $key => $notice ) {
- $notice = wp_parse_args( $notice, $defaults );
-
- // Show notices only for users with `manage_options` cap.
- if ( ! current_user_can( $notice['capability'] ) ) {
- continue;
- }
-
- $notice['id'] = self::get_notice_id( $notice, $key );
- $notice['classes'] = self::get_wrap_classes( $notice );
-
- // Notices visible after transient expire.
- if ( isset( $notice['show_if'] ) && true === $notice['show_if'] ) {
-
- // don't display the notice if it is not supposed to be displayed with other notices.
- if ( 0 !== $notices_displayed && false === $notice['display-with-other-notices'] ) {
- continue;
- }
-
- if ( self::is_expired( $notice ) ) {
-
- self::markup( $notice );
- ++$notices_displayed;
- }
- }
- }
- }
-
- /**
- * Render a notice.
- *
- * @since 1.0.0
- * @param array $notice Notice markup.
- * @return void
- */
- public static function markup( $notice = array() ) {
- wp_enqueue_script( 'astra-notices' );
- wp_enqueue_style( 'astra-notices' );
-
- do_action( 'astra_notice_before_markup' );
-
- do_action( "astra_notice_before_markup_{$notice['id']}" );
-
- ?>
- <div id="<?php echo esc_attr( $notice['id'] ); ?>" class="<?php echo 'astra-notice-wrapper ' . esc_attr( $notice['classes'] ); ?>" data-repeat-notice-after="<?php echo esc_attr( $notice['repeat-notice-after'] ); ?>">
- <div class="astra-notice-container">
- <?php do_action( "astra_notice_inside_markup_{$notice['id']}" ); ?>
- <?php echo wp_kses_post( $notice['message'] ); ?>
- </div>
- </div>
- <?php
-
- do_action( "astra_notice_after_markup_{$notice['id']}" );
-
- do_action( 'astra_notice_after_markup' );
- }
-
- /**
- * Get wrapper classes for a notice.
- *
- * @since 1.0.0
- *
- * @param array $notice Notice arguments.
- * @return array Notice wrapper classes.
- */
- private static function get_wrap_classes( $notice ) {
- $classes = array( 'astra-notice', 'notice' );
-
- if ( $notice['is_dismissible'] ) {
- $classes[] = 'is-dismissible';
- }
-
- $classes[] = $notice['class'];
- if ( isset( $notice['type'] ) && '' !== $notice['type'] ) {
- $classes[] = 'notice-' . $notice['type'];
- }
-
- return esc_attr( implode( ' ', $classes ) );
- }
-
- /**
- * Get HTML ID for a given notice.
- *
- * @since 1.0.0
- *
- * @param array $notice Notice arguments.
- * @param int $key Notice array index.
- * @return string HTML if for the notice.
- */
- private static function get_notice_id( $notice, $key ) {
- if ( isset( $notice['id'] ) && ! empty( $notice['id'] ) ) {
- return $notice['id'];
- }
-
- return 'astra-notices-id-' . $key;
- }
-
- /**
- * Check if the notice is expires.
- *
- * @since 1.0.0
- *
- * @param array $notice Notice arguments.
- * @return boolean
- */
- private static function is_expired( $notice ) {
- $transient_status = get_transient( $notice['id'] );
-
- if ( false === $transient_status ) {
-
- if ( isset( $notice['display-notice-after'] ) && false !== $notice['display-notice-after'] ) {
-
- if ( 'delayed-notice' !== get_user_meta( get_current_user_id(), $notice['id'], true ) &&
- 'notice-dismissed' !== get_user_meta( get_current_user_id(), $notice['id'], true ) ) {
- set_transient( $notice['id'], 'delayed-notice', $notice['display-notice-after'] );
- update_user_meta( get_current_user_id(), $notice['id'], 'delayed-notice' );
-
- return false;
- }
- }
-
- // Check the user meta status if current notice is dismissed or delay completed.
- $meta_status = get_user_meta( get_current_user_id(), $notice['id'], true );
-
- if ( empty( $meta_status ) || 'delayed-notice' === $meta_status ) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Get base URL for the astra-notices.
- *
- * @return mixed URL.
- */
- public static function get_url() {
- $path = wp_normalize_path( dirname( __FILE__ ) ); // phpcs:ignore Modernize.FunctionCalls.Dirname.FileConstant
- $theme_dir = wp_normalize_path( get_template_directory() );
-
- if ( strpos( $path, $theme_dir ) !== false ) {
- return trailingslashit( get_template_directory_uri() . str_replace( $theme_dir, '', $path ) );
- } else {
- return plugin_dir_url( __FILE__ );
- }
- }
- }
-
- /**
- * Kicking this off by calling 'get_instance()' method
- */
- Astra_Notices::get_instance();
-
-endif;
--- a/latepoint/lib/kit/astra-notices/class-bsf-admin-notices.php
+++ b/latepoint/lib/kit/astra-notices/class-bsf-admin-notices.php
@@ -0,0 +1,463 @@
+<?php
+/**
+ * BSF Admin Notices
+ *
+ * An easy to use PHP Library to add dismissible admin notices in the WordPress admin.
+ *
+ * @package BSF Admin Notices
+ * @since 1.2.0
+ */
+
+if ( ! defined( 'ABSPATH' ) ) {
+ exit; // Exit if accessed directly.
+}
+
+if ( ! class_exists( 'BSF_Admin_Notices' ) ) :
+
+ /**
+ * BSF_Admin_Notices
+ *
+ * Renamed from Astra_Notices. All runtime strings (AJAX action, nonce,
+ * script handles, JS globals, CSS classes, option keys, ID prefixes) are
+ * intentionally frozen at their original values so old plugin JS/CSS that
+ * is already shipped continues to work without updates.
+ *
+ * @since 1.2.0
+ */
+ class BSF_Admin_Notices {
+
+ /**
+ * Library version.
+ *
+ * @access private
+ * @var string
+ * @since 1.2.0
+ */
+ private static $version = '1.2.3';
+
+ /**
+ * Registered notices.
+ *
+ * @access private
+ * @var array
+ * @since 1.2.0
+ */
+ private static $notices = array();
+
+ /**
+ * Instance.
+ *
+ * @access private
+ * @var object Class object.
+ * @since 1.2.0
+ */
+ private static $instance;
+
+ /**
+ * Initiator.
+ *
+ * @since 1.2.0
+ * @return object initialized object of class.
+ */
+ public static function get_instance() {
+ if ( ! isset( self::$instance ) ) {
+ self::$instance = new self();
+ }
+ return self::$instance;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @since 1.2.0
+ */
+ public function __construct() {
+ $this->maybe_migrate_notices_option();
+ add_action( 'admin_notices', array( $this, 'show_notices' ), 30 );
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
+ add_action( 'wp_ajax_astra-notice-dismiss', array( $this, 'dismiss_notice' ) );
+ add_filter( 'wp_kses_allowed_html', array( $this, 'add_data_attributes' ), 10, 2 );
+ }
+
+ /**
+ * Migrate allowed_astra_notices → astra_notices_allowed (one-time, on first load).
+ *
+ * @since 1.2.2
+ * @return void
+ */
+ private function maybe_migrate_notices_option() {
+ $old = get_option( 'allowed_astra_notices', false );
+ if ( false !== $old ) {
+ update_option( 'astra_notices_allowed', $old );
+ delete_option( 'allowed_astra_notices' );
+ }
+ }
+
+ /**
+ * Filters and returns a list of allowed tags and attributes for a given context.
+ *
+ * @param array $allowedposttags array of allowed tags.
+ * @param string $context Context type (explicit).
+ * @since 1.2.0
+ * @return array
+ */
+ public function add_data_attributes( $allowedposttags, $context ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
+ $allowedposttags['a']['data-repeat-notice-after'] = true;
+
+ return $allowedposttags;
+ }
+
+ /**
+ * Add Notice.
+ *
+ * @since 1.2.0
+ * @param array $args Notice arguments.
+ * @return void
+ */
+ public static function add_notice( $args = array() ) {
+ self::$notices[] = $args;
+
+ if ( ! isset( $args['id'] ) ) {
+ return;
+ }
+
+ $notice_id = sanitize_key( $args['id'] ); // Notice ID.
+ $notices = get_option( 'astra_notices_allowed', array() );
+ if ( ! in_array( $notice_id, $notices, true ) ) {
+ $notices[] = $notice_id; // Add notice id to the array.
+ update_option( 'astra_notices_allowed', $notices ); // Update the option.
+ }
+ }
+
+ /**
+ * Dismiss Notice.
+ *
+ * @since 1.2.0
+ * @return void
+ */
+ public function dismiss_notice() {
+ check_ajax_referer( 'astra-notices', 'nonce' );
+
+ $notice_id = ( isset( $_POST['notice_id'] ) ) ? sanitize_key( wp_unslash( $_POST['notice_id'] ) ) : '';
+ $repeat_notice_after = ( isset( $_POST['repeat_notice_after'] ) ) ? absint( wp_unslash( $_POST['repeat_notice_after'] ) ) : 0;
+ $notice = $this->get_notice_by_id( $notice_id );
+ $capability = isset( $notice['capability'] ) ? $notice['capability'] : 'manage_options';
+
+ $has_cap = current_user_can( $capability );
+
+ /**
+ * Filters whether the current user passes the capability check for notice dismissal.
+ *
+ * Both the legacy and new filter names are fired for backward compatibility.
+ * Filters can only restrict access (return false), never grant it — if the
+ * underlying current_user_can() check fails, filters cannot override to true.
+ */
+ $cap_check = apply_filters( 'astra_notices_user_cap_check', $has_cap );
+ $cap_check = apply_filters( 'bsf_admin_notices_user_cap_check', $cap_check );
+
+ if ( ! $has_cap || ! $cap_check ) {
+ wp_send_json_error( esc_html__( 'Permission denied.', 'astra-notices' ) );
+ }
+
+ $allowed_notices = get_option( 'astra_notices_allowed', array() ); // Get allowed notices.
+
+ // Define restricted user meta keys using the dynamic table prefix.
+ global $wpdb;
+ $wp_default_meta_keys = array(
+ $wpdb->prefix . 'capabilities',
+ $wpdb->prefix . 'user_level',
+ $wpdb->prefix . 'user-settings',
+ 'account_status',
+ 'session_tokens',
+ );
+
+ // if $notice_id does not start with astra-notices-id and notice_id is not from the allowed notices, then return.
+ if ( 0 !== strpos( $notice_id, 'astra-notices-id-' ) && ( ! in_array( $notice_id, $allowed_notices, true ) ) ) {
+ wp_send_json_error( esc_html__( 'Invalid notice ID.', 'astra-notices' ) );
+ }
+
+ // Valid inputs?
+ if ( ! empty( $notice_id ) ) {
+
+ if ( in_array( $notice_id, $wp_default_meta_keys, true ) ) {
+ wp_send_json_error( esc_html__( 'Invalid notice ID.', 'astra-notices' ) );
+ }
+
+ if ( ! empty( $repeat_notice_after ) ) {
+ set_transient( $notice_id, true, $repeat_notice_after );
+ } else {
+ update_user_meta( get_current_user_id(), $notice_id, 'notice-dismissed' );
+ }
+
+ wp_send_json_success();
+ }
+
+ wp_send_json_error();
+ }
+
+ /**
+ * Enqueue Scripts.
+ *
+ * @since 1.2.0
+ * @return void
+ */
+ public function enqueue_scripts() {
+ wp_register_style( 'bsf-astra-notices', self::get_url() . 'notices.css', array(), self::$version );
+ wp_register_script( 'bsf-astra-notices', self::get_url() . 'notices.js', array( 'jquery' ), self::$version, true );
+ wp_localize_script(
+ 'bsf-astra-notices',
+ 'bsfAstraNotices',
+ array(
+ '_notice_nonce' => wp_create_nonce( 'astra-notices' ),
+ )
+ );
+ }
+
+ /**
+ * Sort the notices based on the given priority of the notice.
+ * This function is called from usort()
+ *
+ * @since 1.2.0
+ * @param array $notice_1 First notice.
+ * @param array $notice_2 Second Notice.
+ * @return array
+ */
+ public function sort_notices( $notice_1, $notice_2 ) {
+ if ( ! isset( $notice_1['priority'] ) ) {
+ $notice_1['priority'] = 10;
+ }
+ if ( ! isset( $notice_2['priority'] ) ) {
+ $notice_2['priority'] = 10;
+ }
+
+ return $notice_1['priority'] - $notice_2['priority'];
+ }
+
+ /**
+ * Get all registered notices.
+ *
+ * @return array|null
+ */
+ private function get_notices() {
+ usort( self::$notices, array( $this, 'sort_notices' ) );
+
+ return self::$notices;
+ }
+
+ /**
+ * Get notice by notice_id.
+ *
+ * @param string $notice_id Notice id.
+ *
+ * @return array notice based on the notice id.
+ */
+ private function get_notice_by_id( $notice_id ) {
+ if ( empty( $notice_id ) ) {
+ return array();
+ }
+
+ $notices = $this->get_notices();
+ $notice = wp_list_filter(
+ $notices,
+ array(
+ 'id' => $notice_id,
+ )
+ );
+
+ return ( ! empty( $notice ) && isset( $notice[0] ) ) ? $notice[0] : array();
+ }
+
+ /**
+ * Display the notices in the WordPress admin.
+ *
+ * @since 1.2.0
+ * @return void
+ */
+ public function show_notices() {
+ $defaults = array(
+ 'id' => '', // Optional, Notice ID. If empty it set `astra-notices-id-<$array-index>`.
+ 'type' => 'info', // Optional, Notice type. Default `info`. Expected [info, warning, notice, error].
+ 'message' => '', // Optional, Message.
+ 'show_if' => true, // Optional, Show notice on custom condition. E.g. 'show_if' => if( is_admin() ) ? true, false, .
+ 'repeat-notice-after' => '', // Optional, Dismiss-able notice time. It'll auto show after given time.
+ 'display-notice-after' => false, // Optional, Dismiss-able notice time. It'll auto show after given time.
+ 'class' => '', // Optional, Additional notice wrapper class.
+ 'priority' => 10, // Priority of the notice.
+ 'display-with-other-notices' => true, // Should the notice be displayed if other notices are being displayed from BSF_Admin_Notices.
+ 'is_dismissible' => true,
+ 'capability' => 'manage_options', // User capability - This capability is required for the current user to see this notice.
+ );
+
+ // Count for the notices that are rendered.
+ $notices_displayed = 0;
+ $notices = $this->get_notices();
+
+ foreach ( $notices as $key => $notice ) {
+ $notice = wp_parse_args( $notice, $defaults );
+
+ // Show notices only for users with `manage_options` cap.
+ if ( ! current_user_can( $notice['capability'] ) ) {
+ continue;
+ }
+
+ $notice['id'] = self::get_notice_id( $notice, $key );
+ $notice['classes'] = self::get_wrap_classes( $notice );
+
+ // Notices visible after transient expire.
+ if ( isset( $notice['show_if'] ) && true === $notice['show_if'] ) {
+
+ // don't display the notice if it is not supposed to be displayed with other notices.
+ if ( 0 !== $notices_displayed && false === $notice['display-with-other-notices'] ) {
+ continue;
+ }
+
+ if ( self::is_expired( $notice ) ) {
+
+ self::markup( $notice );
+ ++$notices_displayed;
+ }
+ }
+ }
+ }
+
+ /**
+ * Render a notice.
+ *
+ * @since 1.2.0
+ * @param array $notice Notice markup.
+ * @return void
+ */
+ public static function markup( $notice = array() ) {
+ wp_enqueue_script( 'bsf-astra-notices' );
+ wp_enqueue_style( 'bsf-astra-notices' );
+
+ // Dual-emit: legacy (astra_notice_*) + new (bsf_admin_notice_*) hooks for backward compat.
+ // Note: consumers hooking BOTH names for the same event will be called twice.
+ do_action( 'astra_notice_before_markup' );
+ do_action( 'bsf_admin_notice_before_markup' );
+
+ do_action( "astra_notice_before_markup_{$notice['id']}" );
+ do_action( "bsf_admin_notice_before_markup_{$notice['id']}" );
+
+ ?>
+ <div id="<?php echo esc_attr( $notice['id'] ); ?>" class="<?php echo esc_attr( 'astra-notice-wrapper ' . $notice['classes'] ); ?>" data-repeat-notice-after="<?php echo esc_attr( $notice['repeat-notice-after'] ); ?>">
+ <div class="astra-notice-container">
+ <?php do_action( "astra_notice_inside_markup_{$notice['id']}" ); ?>
+ <?php do_action( "bsf_admin_notice_inside_markup_{$notice['id']}" ); ?>
+ <?php echo wp_kses_post( $notice['message'] ); ?>
+ </div>
+ </div>
+ <?php
+
+ do_action( "astra_notice_after_markup_{$notice['id']}" );
+ do_action( "bsf_admin_notice_after_markup_{$notice['id']}" );
+
+ do_action( 'astra_notice_after_markup' );
+ do_action( 'bsf_admin_notice_after_markup' );
+ }
+
+ /**
+ * Get wrapper classes for a notice.
+ *
+ * @since 1.2.0
+ *
+ * @param array $notice Notice arguments.
+ * @return array Notice wrapper classes.
+ */
+ private static function get_wrap_classes( $notice ) {
+ $classes = array( 'astra-notice', 'notice' );
+
+ if ( $notice['is_dismissible'] ) {
+ $classes[] = 'is-dismissible';
+ }
+
+ $classes[] = $notice['class'];
+ if ( isset( $notice['type'] ) && '' !== $notice['type'] ) {
+ $classes[] = 'notice-' . $notice['type'];
+ }
+
+ return esc_attr( implode( ' ', $classes ) );
+ }
+
+ /**
+ * Get HTML ID for a given notice.
+ *
+ * @since 1.2.0
+ *
+ * @param array $notice Notice arguments.
+ * @param int $key Notice array index.
+ * @return string HTML if for the notice.
+ */
+ private static function get_notice_id( $notice, $key ) {
+ if ( isset( $notice['id'] ) && ! empty( $notice['id'] ) ) {
+ return $notice['id'];
+ }
+
+ return 'astra-notices-id-' . $key;
+ }
+
+ /**
+ * Check if the notice is expires.
+ *
+ * @since 1.2.0
+ *
+ * @param array $notice Notice arguments.
+ * @return boolean
+ */
+ private static function is_expired( $notice ) {
+ $transient_status = get_transient( $notice['id'] );
+
+ if ( false === $transient_status ) {
+
+ if ( isset( $notice['display-notice-after'] ) && false !== $notice['display-notice-after'] ) {
+
+ if ( 'delayed-notice' !== get_user_meta( get_current_user_id(), $notice['id'], true ) &&
+ 'notice-dismissed' !== get_user_meta( get_current_user_id(), $notice['id'], true ) ) {
+ set_transient( $notice['id'], 'delayed-notice', $notice['display-notice-after'] );
+ update_user_meta( get_current_user_id(), $notice['id'], 'delayed-notice' );
+
+ return false;
+ }
+ }
+
+ // Check the user meta status if current notice is dismissed or delay completed.
+ $meta_status = get_user_meta( get_current_user_id(), $notice['id'], true );
+
+ if ( empty( $meta_status ) || 'delayed-notice' === $meta_status ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get base URL for the library assets.
+ *
+ * @return mixed URL.
+ */
+ public static function get_url() {
+ $path = wp_normalize_path( dirname( __FILE__ ) ); // phpcs:ignore Modernize.FunctionCalls.Dirname.FileConstant
+ $theme_dir = wp_normalize_path( get_template_directory() );
+
+ if ( false !== strpos( $path, $theme_dir ) ) {
+ return trailingslashit( get_template_directory_uri() . str_replace( $theme_dir, '', $path ) );
+ } else {
+ return plugin_dir_url( __FILE__ );
+ }
+ }
+ }
+
+ /**
+ * Kicking this off by calling 'get_instance()' method
+ */
+ BSF_Admin_Notices::get_instance();
+
+endif;
+
+// Backward compatibility alias for bsf-analytics library and third-party plugins
+// that still reference the old class name. Safe to remove once all consumers
+// are updated.
+if ( ! class_exists( 'Astra_Notices' ) ) {
+ class_alias( 'BSF_Admin_Notices', 'Astra_Notices' ); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.class_aliasFound
+}
--- a/latepoint/lib/kit/bsf-analytics/class-bsf-analytics.php
+++ b/latepoint/lib/kit/bsf-analytics/class-bsf-analytics.php
@@ -93,7 +93,7 @@
public function set_actions() {
foreach ( $this->entities as $key => $data ) {
- add_action( 'astra_notice_before_markup_' . $key . '-optin-notice', array( $this, 'enqueue_assets' ) );
+ add_action( 'bsf_admin_notice_before_markup_' . $key . '-optin-notice', array( $this, 'enqueue_assets' ) );
add_action( 'update_option_' . $key . '_usage_optin', array( $this, 'update_analytics_option_callback' ), 10, 3 );
add_action( 'add_option_' . $key . '_usage_optin', array( $this, 'add_analytics_option_callback' ), 10, 2 );
}
@@ -235,6 +235,9 @@
* @since 1.0.0
*/
public function option_notice() {
+ if ( ! class_exists( 'BSF_Admin_Notices' ) ) {
+ return;
+ }
if ( ! current_user_can( 'manage_options' ) ) {
return;
@@ -279,7 +282,7 @@
$language_dir = is_rtl() ? 'rtl' : 'ltr';
- Astra_Notices::add_notice(
+ BSF_Admin_Notices::add_notice(
array(
'id' => $key . '-optin-notice',
'type' => '',
--- a/latepoint/lib/kit/bsf-analytics/modules/deactivation-survey/classes/class-deactivation-survey-feedback.php
+++ b/latepoint/lib/kit/bsf-analytics/modules/deactivation-survey/classes/class-deactivation-survey-feedback.php
@@ -207,6 +207,7 @@
wp_enqueue_style( 'uds-feedback-style', $dir_path . 'assets/css/feedback' . $file_ext . '.css', array(), BSF_ANALYTICS_VERSION );
wp_style_add_data( 'uds-feedback-style', 'rtl', 'replace' );
+ wp_style_add_data( 'uds-feedback-style', 'suffix', $file_ext );
}
/**
--- a/latepoint/lib/kit/bsf-analytics/modules/utm-analytics.php
+++ b/latepoint/lib/kit/bsf-analytics/modules/utm-analytics.php
@@ -49,6 +49,7 @@
'surecontact',
'surecookie',
'suredash',
+ 'suredonation',
'sureforms',
'suremails',
'surerank',
--- a/latepoint/lib/kit/nps-survey/classes/nps-survey-script.php
+++ b/latepoint/lib/kit/nps-survey/classes/nps-survey-script.php
@@ -104,26 +104,6 @@
}
/**
- * Generate and return the Google fonts url.
- *
- * @since 1.0.2
- * @return string
- */
- public static function google_fonts_url() {
-
- $font_families = array(
- 'Figtree:400,500,600,700',
- );
-
- $query_args = array(
- 'family' => rawurlencode( implode( '|', $font_families ) ),
- 'subset' => rawurlencode( 'latin,latin-ext' ),
- );
-
- return add_query_arg( $query_args, '//fonts.googleapis.com/css' );
- }
-
- /**
* Load script.
*
* @param array<string> $show_on_screens An array of screen IDs where the scripts should be loaded.
@@ -187,7 +167,7 @@
wp_enqueue_style( 'nps-survey-style', $build_url . '/style-main.css', array(), NPS_SURVEY_VER );
wp_style_add_data( 'nps-survey-style', 'rtl', 'replace' );
- wp_enqueue_style( 'nps-survey-google-fonts', self::google_fonts_url(), array(), 'all' );
+ wp_enqueue_style( 'nps-survey-fonts', NPS_SURVEY_URL . 'assets/fonts/figtree.css', array(), NPS_SURVEY_VER );
}
/**
@@ -210,7 +190,7 @@
'nps_id' => array(
'type' => 'string',
'required' => true,
- 'sanitize_callback' => 'sanitize_text_field',
+ 'sanitize_callback' => 'sanitize_key',
),
'rating' => array(
'type' => 'integer',
@@ -229,7 +209,7 @@
'plugin_slug' => array(
'type' => 'string',
'required' => true,
- 'sanitize_callback' => 'sanitize_text_field',
+ 'sanitize_callback' => 'sanitize_key',
),
),
),
@@ -248,12 +228,12 @@
'nps_id' => array(
'type' => 'string',
'required' => true,
- 'sanitize_callback' => 'sanitize_text_field',
+ 'sanitize_callback' => 'sanitize_key',
),
'plugin_slug' => array(
'type' => 'string',
'required' => true,
- 'sanitize_callback' => 'sanitize_text_field',
+ 'sanitize_callback' => 'sanitize_key',
),
'dismiss_timespan' => array(
'type' => 'integer',
@@ -372,9 +352,8 @@
/**
* Submit Ratings.
*
- * @param WP_REST_Request $request Request object.
- * @return void
- * @phpstan-ignore-next-line
+ * @param WP_REST_Request<array<string,mixed>> $request Request object.
+ * @return WP_REST_Response|WP_Error
*/
public static function submit_rating( $request ) {
@@ -382,17 +361,22 @@
// Verify the nonce.
if ( ! wp_verify_nonce( sanitize_text_field( (string) $nonce ), 'wp_rest' ) ) {
- wp_send_json_error(
- array(
- 'data' => __( 'Nonce verification failed.', 'nps-survey' ),
- 'status' => false,
-
- )
+ return new WP_Error(
+ 'nonce_verification_failed',
+ __( 'Nonce verification failed.', 'nps-survey' ),
+ array( 'status' => 403 )
);
}
$current_user = wp_get_current_user();
- $nps_id = $request->get_param( 'nps_id' ) ?? '';
+ $raw_nps_id = $request->get_param( 'nps_id' );
+ $raw_rating = $request->get_param( 'rating' );
+ $raw_comment = $request->get_param( 'comment' );
+ $raw_slug = $request->get_param( 'plugin_slug' );
+ $nps_id = sanitize_key( is_string( $raw_nps_id ) ? $raw_nps_id : '' );
+ $rating = absint( is_numeric( $raw_rating ) ? $raw_rating : 0 );
+ $comment = sanitize_text_field( is_string( $raw_comment ) ? $raw_comment : '' );
+ $plugin_slug = sanitize_key( is_string( $raw_slug ) ? $raw_slug : '' );
/**
* Filter the post data.
@@ -405,13 +389,13 @@
$post_data = apply_filters(
'nps_survey_post_data',
array(
- 'rating' => ! empty( $request['rating'] ) ? sanitize_text_field( strval( $request['rating'] ) ) : '',
- 'comment' => ! empty( $request['comment'] ) ? sanitize_text_field( strval( $request['comment'] ) ) : '',
+ 'rating' => $rating,
+ 'comment' => $comment,
'email' => $current_user->user_email,
'first_name' => ! empty( $current_user->first_name ) ? $current_user->first_name : $current_user->display_name,
'last_name' => ! empty( $current_user->last_name ) ? $current_user->last_name : '',
- 'source' => ! empty( $request['plugin_slug'] ) ? sanitize_text_field( strval( $request['plugin_slug'] ) ) : '',
- 'plugin_slug' => ! empty( $request['plugin_slug'] ) ? sanitize_text_field( strval( $request['plugin_slug'] ) ) : '',
+ 'source' => $plugin_slug,
+ 'plugin_slug' => $plugin_slug,
),
$nps_id
);
@@ -442,12 +426,10 @@
$response = wp_safe_remote_post( $api_endpoint, $request_args );
if ( is_wp_error( $response ) ) {
- // There was an error in the request.
- wp_send_json_error(
- array(
- 'data' => 'Failed ' . $response->get_error_message(),
- 'status' => false,
- )
+ return new WP_Error(
+ 'remote_request_failed',
+ __( 'Remote request failed.', 'nps-survey' ),
+ array( 'status' => 500 )
);
}
@@ -457,7 +439,7 @@
// If the status update should be skipped, return success.
if ( self::should_skip_status_update( $nps_id, 'submit', $post_data ) ) {
- wp_send_json_success(
+ return rest_ensure_response(
array(
'status' => true,
)
@@ -470,19 +452,19 @@
'dismiss_step' => '',
);
- update_option( self::get_nps_id( strval( $request['plugin_slug'] ) ), $nps_form_status, false );
+ update_option( self::get_nps_id( $plugin_slug ), $nps_form_status, false );
- wp_send_json_success(
+ return rest_ensure_response(
array(
'status' => true,
)
);
} else {
- wp_send_json_error(
- array(
- 'status' => false,
- )
+ return new WP_Error(
+ 'api_error',
+ __( 'Request failed.', 'nps-survey' ),
+ array( 'status' => 500 )
);
}
}
@@ -490,9 +472,8 @@
/**
* Dismiss NPS Survey.
*
- * @param WP_REST_Request $request Request object.
- * @return void
- * @phpstan-ignore-next-line
+ * @param WP_REST_Request<array<string,mixed>> $request Request object.
+ * @return WP_REST_Response|WP_Error
*/
public static function dismiss_nps_survey_panel( $request ) {
@@ -500,45 +481,51 @@
// Verify the nonce.
if ( ! wp_verify_nonce( sanitize_text_field( (string) $nonce ), 'wp_rest' ) ) {
- wp_send_json_error(
- array(
- 'data' => __( 'Nonce verification failed.', 'nps-survey' ),
- 'status' => false,
-
- )
+ return new WP_Error(
+ 'nonce_verification_failed',
+ __( 'Nonce verification failed.', 'nps-survey' ),
+ array( 'status' => 403 )
);
}
// If the status update should be skipped, return success.
- $nps_id = $request->get_param( 'nps_id' ) ?? '';
+ $raw_nps_id = $request->get_param( 'nps_id' );
+ $raw_slug = $request->get_param( 'plugin_slug' );
+ $raw_timespan = $request->get_param( 'dismiss_timespan' );
+ $raw_step = $request->get_param( 'current_step' );
+ $nps_id = sanitize_key( is_string( $raw_nps_id ) ? $raw_nps_id : '' );
+ $plugin_slug = sanitize_key( is_string( $raw_slug ) ? $raw_slug : '' );
+ $dismiss_timespan = absint( is_numeric( $raw_timespan ) ? $raw_timespan : 0 );
+ $current_step = sanitize_text_field( is_string( $raw_step ) ? $raw_step : '' );
+
if ( self::should_skip_status_update( $nps_id, 'dismiss' ) ) {
- wp_send_json_success(
+ return rest_ensure_response(
array(
'status' => true,
)
);
}
- $nps_form_status = self::get_nps_survey_dismiss_status( strval( $request['plugin_slug'] ) );
+ $nps_form_status = self::get_nps_survey_dismiss_status( $plugin_slug );
// Add dismiss timespan.
- $nps_form_status['dismiss_timespan'] = absint( $request['dismiss_timespan'] );
+ $nps_form_status['dismiss_timespan'] = $dismiss_timespan;
// Add dismiss date.
$nps_form_status['dismiss_time'] = time();
// Update dismiss count.
$nps_form_status['dismiss_count'] += 1;
- $nps_form_status['dismiss_step'] = sanitize_text_field( strval( $request['current_step'] ) );
+ $nps_form_status['dismiss_step'] = $current_step;
// Dismiss Permanantly.
if ( $nps_form_status['dismiss_count'] >= 2 ) {
$nps_form_status['dismiss_permanently'] = true;
}
- update_option( self::get_nps_id( strval( $request['plugin_slug'] ) ), $nps_form_status );
+ update_option( self::get_nps_id( $plugin_slug ), $nps_form_status, false );
- wp_send_json_success(
+ return rest_ensure_response(
array(
'status' => true,
)
--- a/latepoint/lib/kit/nps-survey/dist/main.asset.php
+++ b/latepoint/lib/kit/nps-survey/dist/main.asset.php
@@ -2,4 +2,4 @@
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
-return array('dependencies' => array('react', 'wp-api-fetch', 'wp-element', 'wp-i18n'), 'version' => '3bf0d82f8476657f8546');
+return array('dependencies' => array('react', 'wp-api-fetch', 'wp-element', 'wp-i18n'), 'version' => '4e3b3dbec4d5ae4d2355');
--- a/latepoint/lib/kit/nps-survey/nps-survey-plugin-loader.php
+++ b/latepoint/lib/kit/nps-survey/nps-survey-plugin-loader.php
@@ -2,8 +2,8 @@
/**
* Plugin Loader.
*
- * @package {{package}}
- * @since {{since}}
+ * @package NPS_Survey
+ * @since 1.0.0
*/
namespace NPS_Survey;
--- a/latepoint/lib/kit/nps-survey/nps-survey.php
+++ b/latepoint/lib/kit/nps-survey/nps-survey.php
@@ -3,11 +3,12 @@
* Plugin Name: NPS Survey
* Description: It is a nps survey library.
* Author: Brainstorm Force
- * Version: 1.0.21
+ * Version: 1.0.24
* License: GPL v2
+ * License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: nps-survey
*
- * @package {{package}}
+ * @package NPS_Survey
*/
if ( ! defined( 'ABSPATH' ) ) {
@@ -23,5 +24,5 @@
define( 'NPS_SURVEY_BASE', plugin_basename( NPS_SURVEY_FILE ) );
define( 'NPS_SURVEY_DIR', plugin_dir_path( NPS_SURVEY_FILE ) );
define( 'NPS_SURVEY_URL', plugins_url( '/', NPS_SURVEY_FILE ) );
-define( 'NPS_SURVEY_VER', '1.0.21' );
+define( 'NPS_SURVEY_VER', '1.0.24' );
require_once 'nps-survey-plugin-loader.php';
--- a/latepoint/lib/models/booking_model.php
+++ b/latepoint/lib/models/booking_model.php
@@ -274,7 +274,7 @@
[
'start_datetime_utc' => $this->start_datetime_utc,
'end_datetime_utc' => $this->end_datetime_utc,
- ]
+ ]
);
}
}
@@ -292,7 +292,7 @@
[
'object_id' => $id,
'object_model_type' => 'booking',
- ]
+ ]
);
@@ -370,13 +370,13 @@
'manage_booking_by_key',
'ical_download',
],
- [ 'key' => $key ]
+ [ 'key' => $key ]
) : OsRouterHelper::build_admin_post_link(
[
'customer_cabinet',
'ical_download',
],
- [ 'latepoint_booking_id' => $this->id ]
+ [ 'latepoint_booking_id' => $this->id ]
);
}
@@ -511,6 +511,18 @@
$customer_requirement_satisfied = ( $this->customer_id && $customer && $customer->id && ( $this->customer_id == $customer->id ) );
}
+ // A hidden service is never bookable through the public booking flow, regardless of how
+ // the booking object was populated. Admins/agents book hidden services via the backend panel.
+ if ( $this->service_id ) {
+ $service = ( $this->service instanceof OsServiceModel && $this->service->id )
+ ? $this->service
+ : new OsServiceModel( $this->service_id );
+ if ( $service->is_hidden() ) {
+ $this->add_error( 'send_to_step', __( 'Selected service is not available.', 'latepoint' ), 'booking__service' );
+ return false;
+ }
+ }
+
// agent, service and customer should be set
if ( $this->service_id && $this->agent_id && $customer_requirement_satisfied ) {
@@ -523,7 +535,7 @@
'service_id' => $this->service_id,
LATEPOINT_TABLE_AGENTS . '.status' => LATEPOINT_AGENT_STATUS_ACTIVE,
LATEPOINT_TABLE_LOCATIONS . '.status' => LATEPOINT_LOCATION_STATUS_ACTIVE,
- ]
+ ]