=' ) ); } } /** * Is pro activated */ function hawo_is_pro_activated() { $activated = false; if ( ! function_exists( 'is_plugin_active' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } if ( is_plugin_active( 'advanced-autocomplete-orders-for-woocommerce-premium/advanced-autocomplete-orders-for-woocommerce-premium.php' ) ) { $activated = true; } return $activated; } /** * Whether the store may change Pro settings. * * A site that already has the premium plugin stays unlocked. * Saved settings are never deleted when this is false. * * @return bool */ function hawo_can_edit_pro_settings() { if ( hawo_is_pro_activated() ) { return true; } if ( function_exists( 'hawo_fs' ) ) { $fs = hawo_fs(); if ( is_object( $fs ) && method_exists( $fs, 'can_use_premium_code' ) && $fs->can_use_premium_code() ) { return true; } } return false; } /** * Whether a saved order type allows this order to complete. * * An empty type, "all_orders", or an unknown value does not block completion. * * @param string $order_type Saved order type. * @param bool[] $virtual_flags One entry per product line. True when that product is virtual. * @param bool[] $downloadable_flags One entry per product line. True when that product is downloadable. * @return bool */ function hawo_order_type_matches( $order_type, array $virtual_flags, array $downloadable_flags ) { $order_type = is_string( $order_type ) ? $order_type : ''; if ( '' === $order_type || 'all_orders' === $order_type ) { return true; } $specific_types = array( 'only_virtual_all', 'has_virtual', 'has_downloadable', 'only_downloadable_all', 'mixed_cart', ); if ( ! in_array( $order_type, $specific_types, true ) ) { return true; } if ( empty( $virtual_flags ) ) { return false; } if ( 'only_virtual_all' === $order_type ) { return ! in_array( false, $virtual_flags, true ); } if ( 'has_virtual' === $order_type ) { return in_array( true, $virtual_flags, true ); } if ( 'has_downloadable' === $order_type ) { return in_array( true, $downloadable_flags, true ); } if ( 'only_downloadable_all' === $order_type ) { return ! in_array( false, $downloadable_flags, true ); } if ( 'mixed_cart' === $order_type ) { return in_array( true, $virtual_flags, true ) && in_array( false, $virtual_flags, true ); } return true; } /** * Whether the saved order total allows this order amount. * * Old settings store one amount as min or max. New settings may store both * a lowest and a highest amount. An empty bound is ignored. * * @param mixed $setting Saved order_total value. * @param mixed $total Order amount. * @return bool */ function hawo_order_total_allows( $setting, $total ) { if ( ! is_array( $setting ) ) { return true; } $mode = ( isset( $setting['mode'] ) && 'exclude' === $setting['mode'] ) ? 'exclude' : 'include'; $value = ( isset( $setting['value'] ) && is_array( $setting['value'] ) ) ? $setting['value'] : array(); $total = (float) $total; $uses_new = array_key_exists( 'min', $value ) || array_key_exists( 'max', $value ); $is_in_range = true; if ( $uses_new ) { $min = hawo_total_bound_number( $value['min'] ?? '' ); $max = hawo_total_bound_number( $value['max'] ?? '' ); if ( $min > 0 && $total < $min ) { $is_in_range = false; } if ( $max > 0 && $total > $max ) { $is_in_range = false; } } else { $type = isset( $value['type'] ) ? (string) $value['type'] : ''; $amount = hawo_total_bound_number( $value['amount'] ?? '' ); if ( 'min' === $type && $amount > 0 && $total < $amount ) { $is_in_range = false; } if ( 'max' === $type && $amount > 0 && $total > $amount ) { $is_in_range = false; } } if ( 'include' === $mode && ! $is_in_range ) { return false; } if ( 'exclude' === $mode && $is_in_range ) { return false; } return true; } /** * Turn a saved amount into a number. Empty or zero means "no bound". * * @param mixed $amount Saved amount. * @return float */ function hawo_total_bound_number( $amount ) { if ( ! is_scalar( $amount ) || ! is_numeric( $amount ) ) { return 0.0; } return (float) $amount; } /** * Order note that says why the order was completed. * * Stores with no extra rules keep the original note. * * @param array $settings Plugin settings. * @return string */ function hawo_autocomplete_note( $settings ) { $note = __( 'Order auto-completed.', 'advanced-autocomplete-orders-for-woocommerce' ); $reasons = array(); if ( ! is_array( $settings ) ) { return $note; } $type_labels = array( 'only_virtual_all' => __( 'every product is virtual', 'advanced-autocomplete-orders-for-woocommerce' ), 'has_virtual' => __( 'the order has a virtual product', 'advanced-autocomplete-orders-for-woocommerce' ), 'has_downloadable' => __( 'the order has a downloadable product', 'advanced-autocomplete-orders-for-woocommerce' ), 'only_downloadable_all' => __( 'every product is downloadable', 'advanced-autocomplete-orders-for-woocommerce' ), 'mixed_cart' => __( 'the cart has both virtual and physical products', 'advanced-autocomplete-orders-for-woocommerce' ), ); $order_type = isset( $settings['order_type'] ) ? (string) $settings['order_type'] : ''; if ( isset( $type_labels[ $order_type ] ) ) { $reasons[] = sprintf( /* translators: %s: order type in plain words */ __( 'Order type: %s.', 'advanced-autocomplete-orders-for-woocommerce' ), $type_labels[ $order_type ] ); } if ( hawo_order_total_has_bound( $settings['order_total'] ?? null ) ) { $reasons[] = __( 'Order total matched.', 'advanced-autocomplete-orders-for-woocommerce' ); } foreach ( array( 'shipping_methods' => __( 'Shipping method matched.', 'advanced-autocomplete-orders-for-woocommerce' ), 'payment_methods' => __( 'Payment method matched.', 'advanced-autocomplete-orders-for-woocommerce' ), 'products' => __( 'Product matched.', 'advanced-autocomplete-orders-for-woocommerce' ), 'categories' => __( 'Product category matched.', 'advanced-autocomplete-orders-for-woocommerce' ), 'tags' => __( 'Product tag matched.', 'advanced-autocomplete-orders-for-woocommerce' ), 'brands' => __( 'Product brand matched.', 'advanced-autocomplete-orders-for-woocommerce' ), 'types' => __( 'Product type matched.', 'advanced-autocomplete-orders-for-woocommerce' ), 'user_roles' => __( 'Customer role matched.', 'advanced-autocomplete-orders-for-woocommerce' ), 'order_status' => __( 'Order status matched.', 'advanced-autocomplete-orders-for-woocommerce' ), ) as $key => $label ) { if ( hawo_setting_has_values( $settings, $key ) ) { $reasons[] = $label; } } if ( ! empty( $settings['paid_orders_only'] ) ) { $reasons[] = __( 'Only paid orders are completed.', 'advanced-autocomplete-orders-for-woocommerce' ); } if ( function_exists( 'hawo_phase3_note_reasons' ) ) { $reasons = array_merge( $reasons, hawo_phase3_note_reasons( $settings ) ); } if ( empty( $reasons ) ) { return $note; } return $note . ' ' . implode( ' ', $reasons ); } /** * Whether a list setting has at least one chosen value. * * @param array $settings Plugin settings. * @param string $key Setting key. * @return bool */ function hawo_setting_has_values( $settings, $key ) { if ( ! is_array( $settings ) || empty( $settings[ $key ]['value'] ) || ! is_array( $settings[ $key ]['value'] ) ) { return false; } foreach ( $settings[ $key ]['value'] as $item ) { if ( '' !== $item && null !== $item ) { return true; } } return false; } /** * Whether an order total setting has a lowest or highest amount above zero. * * @param mixed $setting Saved order_total value. * @return bool */ function hawo_order_total_has_bound( $setting ) { if ( ! is_array( $setting ) || empty( $setting['value'] ) || ! is_array( $setting['value'] ) ) { return false; } $value = $setting['value']; if ( array_key_exists( 'min', $value ) || array_key_exists( 'max', $value ) ) { return hawo_total_bound_number( $value['min'] ?? '' ) > 0 || hawo_total_bound_number( $value['max'] ?? '' ) > 0; } return hawo_total_bound_number( $value['amount'] ?? '' ) > 0; } /** * Clean settings on save without dropping keys a store already has. * * @param mixed $input Posted settings. * @return array */ function hawo_sanitize_settings( $input ) { $existing = get_option( 'hawo_settings', array() ); if ( ! is_array( $existing ) ) { $existing = array(); } if ( ! is_array( $input ) ) { return $existing; } $clean = array(); $is_settings_form = array_key_exists( 'order_type', $input ) || array_key_exists( 'order_total', $input ); $allowed_types = array( 'all_orders', 'only_virtual_all', 'has_virtual', 'has_downloadable', 'only_downloadable_all', 'mixed_cart' ); $pro_id_keys = array( 'products', 'categories', 'tags', 'brands' ); $pro_text_keys = array( 'types', 'user_roles', 'order_status' ); $known_keys = array_merge( array( 'enable', 'order_type', 'order_total', 'shipping_methods', 'payment_methods', 'complete_when_paid', 'paid_orders_only', 'product_match', 'customers', 'coupons', 'shipping_instances', 'billing_countries', 'billing_states', 'billing_postcodes', 'shipping_countries', 'shipping_states', 'shipping_postcodes', 'extra_rules', 'extra_rules_form', 'attributes', 'item_count', 'line_count', 'product_count', 'backorder', 'buyer_type', 'past_orders', 'amount_spent', 'order_dates', 'days_of_week', 'days_of_week_form', 'hours', 'currency_totals', 'currency_totals_form', 'complete_after_amount', 'complete_after_unit', 'complete_status', 'email_mode', 'custom_email', 'custom_email_subject', 'custom_email_message', 'special_orders', 'fraud_hold', 'shipment_tracking', 'also_complete', 'also_complete_form', 'webhook_url' ), $pro_id_keys, $pro_text_keys ); if ( $is_settings_form || array_key_exists( 'enable', $input ) ) { $clean['enable'] = empty( $input['enable'] ) ? 0 : 1; } if ( array_key_exists( 'order_type', $input ) ) { $posted_type = is_string( $input['order_type'] ) ? $input['order_type'] : ''; if ( in_array( $posted_type, $allowed_types, true ) ) { $clean['order_type'] = $posted_type; } elseif ( isset( $existing['order_type'] ) && in_array( $existing['order_type'], $allowed_types, true ) ) { $clean['order_type'] = $existing['order_type']; } } if ( array_key_exists( 'order_total', $input ) ) { $clean['order_total'] = hawo_sanitize_order_total( $input['order_total'], $existing['order_total'] ?? null ); } if ( $is_settings_form ) { $clean['complete_when_paid'] = empty( $input['complete_when_paid'] ) ? 0 : 1; $clean['paid_orders_only'] = empty( $input['paid_orders_only'] ) ? 0 : 1; } else { foreach ( array( 'complete_when_paid', 'paid_orders_only' ) as $switch_key ) { if ( array_key_exists( $switch_key, $input ) ) { $clean[ $switch_key ] = empty( $input[ $switch_key ] ) ? 0 : 1; } } } foreach ( array( 'shipping_methods', 'payment_methods' ) as $key ) { if ( array_key_exists( $key, $input ) ) { $clean[ $key ] = hawo_sanitize_mode_list( $input[ $key ], 'text' ); } } $pro_active = function_exists( 'hawo_can_edit_pro_settings' ) && hawo_can_edit_pro_settings(); foreach ( array_merge( $pro_id_keys, $pro_text_keys ) as $key ) { if ( $pro_active && array_key_exists( $key, $input ) ) { $value_type = in_array( $key, $pro_id_keys, true ) ? 'id' : 'text'; $clean[ $key ] = hawo_sanitize_mode_list( $input[ $key ], $value_type ); } } if ( $pro_active ) { if ( array_key_exists( 'product_match', $input ) ) { $clean['product_match'] = hawo_sanitize_product_match( $input['product_match'] ); } foreach ( array( 'customers' ) as $customer_key ) { if ( array_key_exists( $customer_key, $input ) ) { $clean[ $customer_key ] = hawo_sanitize_customers( $input[ $customer_key ] ); } } foreach ( array( 'coupons', 'shipping_instances', 'billing_countries', 'shipping_countries' ) as $list_key ) { if ( array_key_exists( $list_key, $input ) ) { $clean[ $list_key ] = hawo_sanitize_mode_list( $input[ $list_key ], 'text' ); } } foreach ( array( 'billing_states', 'billing_postcodes', 'shipping_states', 'shipping_postcodes' ) as $text_key ) { if ( array_key_exists( $text_key, $input ) ) { $clean[ $text_key ] = hawo_sanitize_text_list_setting( $input[ $text_key ] ); } } if ( ! empty( $input['extra_rules_form'] ) ) { $clean['extra_rules'] = hawo_sanitize_extra_rules( $input['extra_rules'] ?? array() ); } $clean = array_merge( $clean, hawo_sanitize_phase3_input( $input ), hawo_sanitize_phase4_input( $input ), hawo_sanitize_phase5_input( $input ) ); } foreach ( $existing as $key => $value ) { if ( ! array_key_exists( $key, $clean ) ) { $clean[ $key ] = $value; } } foreach ( $input as $key => $value ) { if ( array_key_exists( $key, $clean ) || in_array( $key, $known_keys, true ) ) { continue; } $clean[ $key ] = hawo_sanitize_extra_setting( $value ); } return $clean; } /** * Clean the order total setting. Keeps a saved amount. * * The old single amount is kept. Lowest and highest amounts are added beside it. * * @param mixed $value Posted order total. * @param mixed $existing Order total already saved for this store. * @return array */ function hawo_sanitize_order_total( $value, $existing = null ) { $mode = 'include'; $posted = array(); $existing_value = array(); if ( is_array( $value ) ) { if ( isset( $value['mode'] ) && 'exclude' === $value['mode'] ) { $mode = 'exclude'; } if ( isset( $value['value'] ) && is_array( $value['value'] ) ) { $posted = $value['value']; } } if ( is_array( $existing ) && isset( $existing['value'] ) && is_array( $existing['value'] ) ) { $existing_value = $existing['value']; } $type = ( isset( $existing_value['type'] ) && 'max' === $existing_value['type'] ) ? 'max' : 'min'; $amount = hawo_sanitize_amount( $existing_value['amount'] ?? '' ); if ( array_key_exists( 'min', $posted ) || array_key_exists( 'max', $posted ) ) { return array( 'mode' => $mode, 'value' => array( 'type' => $type, 'amount' => $amount, 'min' => hawo_sanitize_amount( $posted['min'] ?? '' ), 'max' => hawo_sanitize_amount( $posted['max'] ?? '' ), ), ); } if ( isset( $posted['type'] ) && 'max' === $posted['type'] ) { $type = 'max'; } elseif ( isset( $posted['type'] ) && 'min' === $posted['type'] ) { $type = 'min'; } if ( array_key_exists( 'amount', $posted ) ) { $amount = hawo_sanitize_amount( $posted['amount'] ); } return array( 'mode' => $mode, 'value' => array( 'type' => $type, 'amount' => $amount, ), ); } /** * Keep a numeric amount as a string. Anything else becomes empty. * * @param mixed $amount Posted amount. * @return string */ function hawo_sanitize_amount( $amount ) { if ( ! is_scalar( $amount ) || ! is_numeric( $amount ) ) { return ''; } return (string) $amount; } /** * Clean an include/exclude list. * * @param mixed $value Posted list. * @param string $value_type "id" or "text". * @return array */ function hawo_sanitize_mode_list( $value, $value_type = 'text' ) { $mode = 'include'; $items = array(); if ( is_array( $value ) ) { if ( isset( $value['mode'] ) && 'exclude' === $value['mode'] ) { $mode = 'exclude'; } if ( isset( $value['value'] ) ) { if ( is_string( $value['value'] ) ) { $items = preg_split( '/[\s,]+/', $value['value'] ); } else { $items = (array) $value['value']; } } } $clean_items = array(); foreach ( $items as $item ) { if ( is_array( $item ) ) { continue; } if ( 'id' === $value_type ) { $item = absint( $item ); if ( $item > 0 ) { $clean_items[] = $item; } continue; } $item = sanitize_text_field( (string) $item ); if ( '' !== $item ) { $clean_items[] = $item; } } return array( 'mode' => $mode, 'value' => array_values( array_unique( $clean_items, SORT_REGULAR ) ), ); } /** * Clean an unknown saved value so it can be kept. * * @param mixed $value Extra setting value. * @return mixed */ function hawo_sanitize_extra_setting( $value ) { if ( is_array( $value ) ) { $clean = array(); foreach ( $value as $key => $item ) { $clean_key = is_string( $key ) ? sanitize_text_field( $key ) : $key; $clean[ $clean_key ] = hawo_sanitize_extra_setting( $item ); } return $clean; } if ( is_int( $value ) || is_float( $value ) ) { return $value; } if ( is_string( $value ) ) { return sanitize_text_field( $value ); } return ''; } if(!function_exists('hawo_pro_badge')) { function hawo_pro_badge() { return !hawo_is_pro_activated() ? ' ['. __('PRO', 'advanced-autocomplete-orders-for-woocommerce') .']' : ''; } } require_once __DIR__ . '/rules.php'; if ( defined( 'WP_CLI' ) && WP_CLI ) { require_once __DIR__ . '/cli.php'; }