' . esc_html__( 'Settings', '4a-loyalty' ) . ''
);
}
$links[] = '' . esc_html__( 'Upgrade to Pro', '4a-loyalty' ) . '';
return $links;
} );
register_activation_hook( __FILE__, static function () {
$defaults = array(
'enabled' => 'yes',
'theme' => 'clean',
'level_1_name' => 'Member',
'level_1_orders' => 5,
'level_1_discount' => 5,
'level_2_name' => 'Plus',
'level_2_orders' => 15,
'level_2_discount' => 7.5,
'exclude_sale' => 'yes',
'disable_with_coupons' => 'yes',
'excluded_roles' => "wholesale_customer\nwholesale\nb2b_customer",
'points_enabled' => 'yes',
'points_per_euro' => 1,
'point_value' => 0.01,
'min_redeem_points' => 100,
'voucher_expiry_days' => 365,
);
if ( ! get_option( '4a_loyalty_settings' ) ) {
add_option( '4a_loyalty_settings', $defaults );
}
add_rewrite_endpoint( '4a-loyalty', EP_ROOT | EP_PAGES );
flush_rewrite_rules();
} );
register_deactivation_hook( __FILE__, static function () {
flush_rewrite_rules();
} );
final class FourA_Loyalty_Free {
private static ?self $instance = null;
private array $settings = array();
private bool $coupon_sync_running = false;
private const AUTO_COUPON = '4a-loyalty-member';
public static function instance(): self {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
add_action( 'plugins_loaded', array( $this, 'boot' ) );
}
public function boot(): void {
if ( foura_loyalty_free_pro_is_active() ) {
return;
}
if ( ! class_exists( 'WooCommerce' ) ) {
add_action( 'admin_notices', array( $this, 'woocommerce_required_notice' ) );
return;
}
$this->settings = wp_parse_args( get_option( '4a_loyalty_settings', array() ), $this->defaults() );
add_action( 'init', array( $this, 'register_endpoint' ) );
add_filter( 'query_vars', array( $this, 'query_vars' ) );
add_filter( 'woocommerce_account_menu_items', array( $this, 'account_menu' ) );
add_action( 'woocommerce_account_4a-loyalty_endpoint', array( $this, 'render_account_page' ) );
add_shortcode( '4a_loyalty', array( $this, 'shortcode' ) );
add_shortcode( 'foura_loyalty', array( $this, 'shortcode' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
add_action( 'admin_menu', array( $this, 'admin_menu' ), 60 );
add_action( 'admin_notices', array( $this, 'coupon_setting_notice' ) );
add_action( 'admin_post_4a_loyalty_save_settings', array( $this, 'save_settings' ) );
add_action( 'admin_post_4a_loyalty_redeem_points', array( $this, 'redeem_points' ) );
add_filter( 'woocommerce_get_shop_coupon_data', array( $this, 'dynamic_coupon_data' ), 10, 2 );
add_filter( 'woocommerce_coupon_is_valid', array( $this, 'validate_auto_coupon' ), 10, 3 );
add_filter( 'woocommerce_cart_totals_coupon_label', array( $this, 'coupon_label' ), 10, 2 );
add_action( 'woocommerce_before_calculate_totals', array( $this, 'sync_auto_coupon' ), 5 );
add_action( 'woocommerce_cart_loaded_from_session', array( $this, 'sync_auto_coupon' ), 20 );
add_action( 'woocommerce_order_status_processing', array( $this, 'maybe_award_points' ) );
add_action( 'woocommerce_order_status_completed', array( $this, 'maybe_award_points' ) );
add_action( 'woocommerce_order_status_refunded', array( $this, 'maybe_reverse_points' ) );
add_action( 'woocommerce_order_status_cancelled', array( $this, 'maybe_reverse_points' ) );
add_action( 'woocommerce_admin_order_data_after_billing_address', array( $this, 'admin_order_loyalty_info' ) );
}
private function defaults(): array {
return array(
'enabled' => 'yes',
'theme' => 'clean',
'level_1_name' => 'Member',
'level_1_orders' => 5,
'level_1_discount' => 5,
'level_2_name' => 'Plus',
'level_2_orders' => 15,
'level_2_discount' => 7.5,
'exclude_sale' => 'yes',
'disable_with_coupons' => 'yes',
'excluded_roles' => "wholesale_customer\nwholesale\nb2b_customer",
'points_enabled' => 'yes',
'points_per_euro' => 1,
'point_value' => 0.01,
'min_redeem_points' => 100,
'voucher_expiry_days' => 365,
);
}
public function woocommerce_required_notice(): void {
if ( ! current_user_can( 'activate_plugins' ) ) {
return;
}
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
if ( ! $screen || 'plugins' !== $screen->id ) {
return;
}
echo '
' . esc_html__( '4A Loyalty', '4a-loyalty' ) . ' ' . esc_html__( 'requires WooCommerce to be active.', '4a-loyalty' ) . '
';
}
public function coupon_setting_notice(): void {
if ( ! current_user_can( 'manage_woocommerce' ) || ! function_exists( 'wc_coupons_enabled' ) ) {
return;
}
if ( 'yes' !== $this->settings['enabled'] || wc_coupons_enabled() ) {
return;
}
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
if ( ! $screen || 'woocommerce_page_4a-loyalty' !== $screen->id ) {
return;
}
echo '' . esc_html__( '4A Loyalty:', '4a-loyalty' ) . ' ' . esc_html__( 'WooCommerce coupons are disabled. Enable coupons in WooCommerce settings so automatic member discounts and loyalty vouchers can work.', '4a-loyalty' ) . '
';
}
public function register_endpoint(): void {
add_rewrite_endpoint( '4a-loyalty', EP_ROOT | EP_PAGES );
}
public function query_vars( array $vars ): array {
$vars[] = '4a-loyalty';
return $vars;
}
public function account_menu( array $items ): array {
if ( ! is_user_logged_in() || $this->is_excluded_user( get_current_user_id() ) ) {
return $items;
}
$new = array();
foreach ( $items as $key => $label ) {
$new[ $key ] = $label;
if ( 'orders' === $key ) {
$new['4a-loyalty'] = __( 'Loyalty', '4a-loyalty' );
}
}
if ( ! isset( $new['4a-loyalty'] ) ) {
$new['4a-loyalty'] = __( 'Loyalty', '4a-loyalty' );
}
return $new;
}
public function enqueue_assets(): void {
if ( is_account_page() || has_shortcode( (string) get_post_field( 'post_content', get_queried_object_id() ), '4a_loyalty' ) || has_shortcode( (string) get_post_field( 'post_content', get_queried_object_id() ), 'foura_loyalty' ) ) {
wp_enqueue_style( '4a-loyalty', FOURA_LOYALTY_URL . 'assets/loyalty.css', array(), FOURA_LOYALTY_VERSION );
}
}
public function shortcode(): string {
if ( ! is_user_logged_in() ) {
return '' . esc_html__( 'Sign in to view your loyalty status.', '4a-loyalty' ) . '
';
}
ob_start();
$this->render_loyalty_card( get_current_user_id() );
return (string) ob_get_clean();
}
public function render_account_page(): void {
if ( ! is_user_logged_in() ) {
return;
}
$this->render_loyalty_card( get_current_user_id() );
}
private function render_loyalty_card( int $user_id ): void {
if ( $this->is_excluded_user( $user_id ) ) {
echo '' . esc_html__( 'Loyalty is not available for this account.', '4a-loyalty' ) . '
';
return;
}
$orders = $this->get_qualifying_order_count( $user_id );
$tier = $this->get_tier_for_user( $user_id );
$points = $this->get_points( $user_id );
$theme = sanitize_html_class( $this->settings['theme'] );
$next = $this->get_next_tier( $orders );
$percentage = (float) $tier['discount'];
echo '';
echo '';
echo '
' . esc_html__( 'YOUR LOYALTY', '4a-loyalty' ) . '
';
echo '
' . esc_html( $tier['name'] ) . '
';
if ( $percentage > 0 ) {
echo '
' . esc_html( $this->format_percent( $percentage ) ) . '% ' . esc_html__( 'member discount', '4a-loyalty' ) . '
';
} else {
echo '
' . esc_html__( 'Your loyalty journey has started.', '4a-loyalty' ) . '
';
}
echo '
';
echo '
' . esc_html( (string) $orders ) . '' . esc_html__( 'qualifying orders', '4a-loyalty' ) . '
';
if ( 'yes' === $this->settings['points_enabled'] ) {
echo '
' . esc_html( (string) $points ) . '' . esc_html__( 'points', '4a-loyalty' ) . '
';
}
echo '
';
if ( $next ) {
$remaining = max( 0, (int) $next['orders'] - $orders );
$progress = min( 100, max( 0, ( $orders / max( 1, (int) $next['orders'] ) ) * 100 ) );
echo '
' . sprintf(
/* translators: 1: Number of remaining qualifying orders, 2: Name of the next loyalty level. */
esc_html( _n( '%1$d more order to unlock %2$s.', '%1$d more orders to unlock %2$s.', $remaining, '4a-loyalty' ) ),
esc_html( (string) $remaining ),
esc_html( $next['name'] )
) . '
';
echo '
';
} else {
echo '
' . esc_html__( 'You have reached the highest level.', '4a-loyalty' ) . '
';
}
echo '
';
if ( 'yes' === $this->settings['points_enabled'] ) {
$this->render_points_section( $user_id, $points );
}
$this->render_vouchers( $user_id );
echo '';
}
private function render_points_section( int $user_id, int $points ): void {
$min_points = max( 1, (int) $this->settings['min_redeem_points'] );
$value = (float) $this->settings['point_value'];
echo '';
echo '
' . esc_html__( 'Use your points', '4a-loyalty' ) . '
';
echo '
' . sprintf(
/* translators: 1: Minimum number of loyalty points, 2: Formatted monetary value of those points. */
esc_html__( '%1$d points are worth %2$s.', '4a-loyalty' ),
esc_html( $min_points ),
wp_kses_post( wc_price( $min_points * $value ) )
) . '
';
if ( isset( $_GET['4a-redeemed'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
echo '
' . esc_html__( 'Your voucher has been created.', '4a-loyalty' ) . '
';
}
if ( $points >= $min_points ) {
$max_amount = $points * $value;
echo '
' . esc_html__( 'Available value:', '4a-loyalty' ) . ' ' . wp_kses_post( wc_price( $max_amount ) ) . '
';
echo '
';
} else {
$needed = $min_points - $points;
echo '
' . sprintf(
/* translators: %d: Number of additional loyalty points required to create a voucher. */
esc_html( _n( 'Earn %d more point to create a voucher.', 'Earn %d more points to create a voucher.', $needed, '4a-loyalty' ) ),
esc_html( (string) $needed )
) . '
';
}
echo '
';
}
private function render_vouchers( int $user_id ): void {
$ids = get_user_meta( $user_id, '_4a_loyalty_vouchers', true );
if ( ! is_array( $ids ) || empty( $ids ) ) {
return;
}
$active = array();
foreach ( array_reverse( $ids ) as $coupon_id ) {
$coupon = new WC_Coupon( (int) $coupon_id );
if ( ! $coupon->get_id() || $coupon->get_usage_count() >= 1 ) {
continue;
}
$expires = $coupon->get_date_expires();
if ( $expires && $expires->getTimestamp() < time() ) {
continue;
}
$active[] = $coupon;
}
if ( empty( $active ) ) {
return;
}
echo '';
echo '
' . esc_html__( 'Your vouchers', '4a-loyalty' ) . '
';
foreach ( $active as $coupon ) {
echo '
';
echo '
' . esc_html__( 'Voucher', '4a-loyalty' ) . '' . esc_html( $coupon->get_code() ) . '
';
echo '
' . wp_kses_post( wc_price( (float) $coupon->get_amount() ) ) . '
';
echo '
';
}
echo '
';
}
public function redeem_points(): void {
if ( ! is_user_logged_in() ) {
wp_safe_redirect( wc_get_page_permalink( 'myaccount' ) );
exit;
}
if ( ! isset( $_POST['4a_loyalty_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['4a_loyalty_nonce'] ) ), '4a_loyalty_redeem_points' ) ) {
wp_die( esc_html__( 'Security check failed.', '4a-loyalty' ) );
}
$user_id = get_current_user_id();
if ( $this->is_excluded_user( $user_id ) || 'yes' !== $this->settings['points_enabled'] ) {
wp_die( esc_html__( 'Points redemption is not available for this account.', '4a-loyalty' ) );
}
$requested = isset( $_POST['points'] ) ? absint( $_POST['points'] ) : 0;
$balance = $this->get_points( $user_id );
$minimum = max( 1, (int) $this->settings['min_redeem_points'] );
if ( $requested < $minimum || $requested > $balance ) {
wc_add_notice( __( 'Please choose a valid number of points.', '4a-loyalty' ), 'error' );
wp_safe_redirect( wc_get_account_endpoint_url( '4a-loyalty' ) );
exit;
}
$amount = round( $requested * (float) $this->settings['point_value'], wc_get_price_decimals() );
if ( $amount <= 0 ) {
wc_add_notice( __( 'Voucher value is too low.', '4a-loyalty' ), 'error' );
wp_safe_redirect( wc_get_account_endpoint_url( '4a-loyalty' ) );
exit;
}
$user = get_userdata( $user_id );
$coupon = new WC_Coupon();
$code = '4a-' . strtolower( wp_generate_password( 10, false, false ) );
$coupon->set_code( $code );
$coupon->set_discount_type( 'fixed_cart' );
$coupon->set_amount( $amount );
$coupon->set_individual_use( true );
$coupon->set_usage_limit( 1 );
$coupon->set_usage_limit_per_user( 1 );
$coupon->set_description( sprintf( '4A Loyalty voucher – %d points', $requested ) );
if ( $user && $user->user_email ) {
$coupon->set_email_restrictions( array( $user->user_email ) );
}
$expiry_days = max( 0, (int) $this->settings['voucher_expiry_days'] );
if ( $expiry_days > 0 ) {
$coupon->set_date_expires( time() + ( DAY_IN_SECONDS * $expiry_days ) );
}
$coupon_id = $coupon->save();
if ( ! $coupon_id ) {
wc_add_notice( __( 'The voucher could not be created.', '4a-loyalty' ), 'error' );
wp_safe_redirect( wc_get_account_endpoint_url( '4a-loyalty' ) );
exit;
}
update_post_meta( $coupon_id, '_4a_loyalty_user_id', $user_id );
update_post_meta( $coupon_id, '_4a_loyalty_points_spent', $requested );
$this->set_points( $user_id, $balance - $requested );
$vouchers = get_user_meta( $user_id, '_4a_loyalty_vouchers', true );
$vouchers = is_array( $vouchers ) ? $vouchers : array();
$vouchers[] = $coupon_id;
update_user_meta( $user_id, '_4a_loyalty_vouchers', array_values( array_unique( array_map( 'absint', $vouchers ) ) ) );
wp_safe_redirect( add_query_arg( '4a-redeemed', '1', wc_get_account_endpoint_url( '4a-loyalty' ) ) );
exit;
}
public function dynamic_coupon_data( $data, string $code ) {
if ( self::AUTO_COUPON !== strtolower( $code ) ) {
return $data;
}
if ( ! $this->auto_discount_available() ) {
return false;
}
$tier = $this->get_tier_for_user( get_current_user_id() );
$discount = (float) $tier['discount'];
if ( $discount <= 0 ) {
return false;
}
return array(
'id' => 0,
'discount_type' => 'percent',
'amount' => $discount,
'individual_use' => false,
'product_ids' => array(),
'exclude_product_ids' => array(),
'usage_limit' => 0,
'usage_limit_per_user' => 0,
'limit_usage_to_x_items' => null,
'usage_count' => 0,
'date_expires' => null,
'free_shipping' => false,
'product_categories' => array(),
'exclude_product_categories' => array(),
'exclude_sale_items' => ( 'yes' === $this->settings['exclude_sale'] ),
'minimum_amount' => '',
'maximum_amount' => '',
'customer_email' => array(),
);
}
public function validate_auto_coupon( bool $valid, WC_Coupon $coupon, $discounts ): bool {
if ( self::AUTO_COUPON !== strtolower( $coupon->get_code() ) ) {
return $valid;
}
if ( ! $this->auto_discount_available() ) {
return false;
}
if ( 'yes' === $this->settings['disable_with_coupons'] && function_exists( 'WC' ) && WC()->cart ) {
$applied = array_map( 'strtolower', WC()->cart->get_applied_coupons() );
$other = array_values( array_diff( $applied, array( self::AUTO_COUPON ) ) );
if ( ! empty( $other ) ) {
return false;
}
}
return true;
}
public function coupon_label( string $label, WC_Coupon $coupon ): string {
if ( self::AUTO_COUPON === strtolower( $coupon->get_code() ) ) {
$tier = $this->get_tier_for_user( get_current_user_id() );
/* translators: %s: Loyalty discount percentage. */
return sprintf( __( '4A Loyalty – %s%%', '4a-loyalty' ), $this->format_percent( (float) $tier['discount'] ) );
}
return $label;
}
public function sync_auto_coupon(): void {
if ( $this->coupon_sync_running || ! function_exists( 'WC' ) || ! WC()->cart ) {
return;
}
if ( function_exists( 'wc_coupons_enabled' ) && ! wc_coupons_enabled() ) {
return;
}
$this->coupon_sync_running = true;
$applied = array_map( 'strtolower', WC()->cart->get_applied_coupons() );
$has_own = in_array( self::AUTO_COUPON, $applied, true );
$other = array_values( array_diff( $applied, array( self::AUTO_COUPON ) ) );
$eligible = $this->auto_discount_available();
if ( 'yes' === $this->settings['disable_with_coupons'] && ! empty( $other ) ) {
$eligible = false;
}
if ( $eligible && ! $has_own ) {
WC()->cart->apply_coupon( self::AUTO_COUPON );
} elseif ( ! $eligible && $has_own ) {
WC()->cart->remove_coupon( self::AUTO_COUPON );
}
$this->coupon_sync_running = false;
}
private function auto_discount_available(): bool {
if ( 'yes' !== $this->settings['enabled'] || ! is_user_logged_in() ) {
return false;
}
$user_id = get_current_user_id();
if ( $this->is_excluded_user( $user_id ) ) {
return false;
}
$tier = $this->get_tier_for_user( $user_id );
return (float) $tier['discount'] > 0;
}
public function maybe_award_points( int $order_id ): void {
if ( 'yes' !== $this->settings['points_enabled'] ) {
return;
}
$order = wc_get_order( $order_id );
if ( ! $order || 'yes' === $order->get_meta( '_4a_loyalty_points_awarded', true ) ) {
return;
}
$user_id = (int) $order->get_user_id();
if ( ! $user_id || $this->is_excluded_user( $user_id ) ) {
return;
}
$eligible_total = 0.0;
foreach ( $order->get_items( 'line_item' ) as $item ) {
$eligible_total += (float) $item->get_total();
}
$points = (int) floor( $eligible_total * (float) $this->settings['points_per_euro'] );
if ( $points <= 0 ) {
return;
}
$this->set_points( $user_id, $this->get_points( $user_id ) + $points );
$order->update_meta_data( '_4a_loyalty_points_awarded', 'yes' );
$order->update_meta_data( '_4a_loyalty_points_reversed', 'no' );
$order->update_meta_data( '_4a_loyalty_points_amount', $points );
$order->save();
}
public function maybe_reverse_points( int $order_id ): void {
$order = wc_get_order( $order_id );
if ( ! $order || 'yes' !== $order->get_meta( '_4a_loyalty_points_awarded', true ) || 'yes' === $order->get_meta( '_4a_loyalty_points_reversed', true ) ) {
return;
}
$user_id = (int) $order->get_user_id();
$points = (int) $order->get_meta( '_4a_loyalty_points_amount', true );
if ( ! $user_id || $points <= 0 ) {
return;
}
$this->set_points( $user_id, max( 0, $this->get_points( $user_id ) - $points ) );
$order->update_meta_data( '_4a_loyalty_points_awarded', 'no' );
$order->update_meta_data( '_4a_loyalty_points_reversed', 'yes' );
$order->save();
}
private function get_points( int $user_id ): int {
return max( 0, (int) get_user_meta( $user_id, '_4a_loyalty_points', true ) );
}
private function set_points( int $user_id, int $points ): void {
update_user_meta( $user_id, '_4a_loyalty_points', max( 0, $points ) );
}
private function get_qualifying_order_count( int $user_id ): int {
if ( ! $user_id ) {
return 0;
}
$results = wc_get_orders(
array(
'customer_id' => $user_id,
'status' => array( 'wc-processing', 'wc-completed' ),
'limit' => 1,
'paginate' => true,
'return' => 'ids',
)
);
return is_object( $results ) && isset( $results->total ) ? absint( $results->total ) : 0;
}
private function get_tier_for_user( int $user_id ): array {
$orders = $this->get_qualifying_order_count( $user_id );
$l1 = max( 0, (int) $this->settings['level_1_orders'] );
$l2 = max( $l1, (int) $this->settings['level_2_orders'] );
if ( $l2 > 0 && $orders >= $l2 ) {
return array(
'name' => (string) $this->settings['level_2_name'],
'discount' => (float) $this->settings['level_2_discount'],
'orders' => $l2,
);
}
if ( $l1 > 0 && $orders >= $l1 ) {
return array(
'name' => (string) $this->settings['level_1_name'],
'discount' => (float) $this->settings['level_1_discount'],
'orders' => $l1,
);
}
return array(
'name' => __( 'Starter', '4a-loyalty' ),
'discount' => 0,
'orders' => 0,
);
}
private function get_next_tier( int $orders ): ?array {
$l1 = max( 0, (int) $this->settings['level_1_orders'] );
$l2 = max( $l1, (int) $this->settings['level_2_orders'] );
if ( $orders < $l1 ) {
return array( 'orders' => $l1, 'name' => (string) $this->settings['level_1_name'] );
}
if ( $orders < $l2 ) {
return array( 'orders' => $l2, 'name' => (string) $this->settings['level_2_name'] );
}
return null;
}
private function is_excluded_user( int $user_id ): bool {
if ( ! $user_id ) {
return true;
}
$user = get_userdata( $user_id );
if ( ! $user ) {
return true;
}
$excluded = preg_split( '/[\r\n,]+/', (string) $this->settings['excluded_roles'] );
$excluded = array_filter( array_map( 'sanitize_key', (array) $excluded ) );
return (bool) array_intersect( $excluded, (array) $user->roles );
}
private function format_percent( float $value ): string {
return rtrim( rtrim( number_format( $value, 2, '.', '' ), '0' ), '.' );
}
public function admin_menu(): void {
add_submenu_page(
'woocommerce',
__( '4A Loyalty', '4a-loyalty' ),
__( '4A Loyalty', '4a-loyalty' ),
'manage_woocommerce',
'4a-loyalty',
array( $this, 'settings_page' )
);
}
public function settings_page(): void {
if ( ! current_user_can( 'manage_woocommerce' ) ) {
return;
}
$s = wp_parse_args( get_option( '4a_loyalty_settings', array() ), $this->defaults() );
?>
isset( $_POST['enabled'] ) ? 'yes' : 'no',
'theme' => $theme,
'level_1_name' => sanitize_text_field( wp_unslash( $_POST['level_1_name'] ?? 'Member' ) ),
'level_1_orders' => max( 1, absint( wp_unslash( $_POST['level_1_orders'] ?? 5 ) ) ),
'level_1_discount' => min( 100, max( 0, (float) sanitize_text_field( wp_unslash( $_POST['level_1_discount'] ?? '5' ) ) ) ),
'level_2_name' => sanitize_text_field( wp_unslash( $_POST['level_2_name'] ?? 'Plus' ) ),
'level_2_orders' => max( 1, absint( wp_unslash( $_POST['level_2_orders'] ?? 15 ) ) ),
'level_2_discount' => min( 100, max( 0, (float) sanitize_text_field( wp_unslash( $_POST['level_2_discount'] ?? '7.5' ) ) ) ),
'exclude_sale' => isset( $_POST['exclude_sale'] ) ? 'yes' : 'no',
'disable_with_coupons' => isset( $_POST['disable_with_coupons'] ) ? 'yes' : 'no',
'excluded_roles' => sanitize_textarea_field( wp_unslash( $_POST['excluded_roles'] ?? '' ) ),
'points_enabled' => isset( $_POST['points_enabled'] ) ? 'yes' : 'no',
'points_per_euro' => max( 0, (float) sanitize_text_field( wp_unslash( $_POST['points_per_euro'] ?? '1' ) ) ),
'point_value' => max( 0, (float) sanitize_text_field( wp_unslash( $_POST['point_value'] ?? '0.01' ) ) ),
'min_redeem_points' => max( 1, absint( wp_unslash( $_POST['min_redeem_points'] ?? 100 ) ) ),
'voucher_expiry_days' => max( 0, absint( wp_unslash( $_POST['voucher_expiry_days'] ?? 365 ) ) ),
);
if ( $settings['level_2_orders'] <= $settings['level_1_orders'] ) {
$settings['level_2_orders'] = $settings['level_1_orders'] + 1;
}
update_option( '4a_loyalty_settings', $settings );
$this->settings = wp_parse_args( $settings, $this->defaults() );
wp_safe_redirect( add_query_arg( array( 'page' => '4a-loyalty', 'updated' => '1' ), admin_url( 'admin.php' ) ) );
exit;
}
public function admin_order_loyalty_info( WC_Order $order ): void {
$points = (int) $order->get_meta( '_4a_loyalty_points_amount', true );
if ( $points > 0 ) {
echo '' . esc_html__( '4A Loyalty points:', '4a-loyalty' ) . ' ' . esc_html( (string) $points ) . '
';
}
}
}
FourA_Loyalty_Free::instance();