Plugin Directory

Changeset 3371141


Ignore:
Timestamp:
10/01/2025 01:52:59 PM (5 months ago)
Author:
boxodev
Message:

Update to version 0.0.61 from GitHub

Location:
boxo-return
Files:
6 added
6 deleted
30 edited
1 copied

Legend:

Unmodified
Added
Removed
  • boxo-return/tags/0.0.61/admin/admin.php

    r3276747 r3371141  
    1313if (!class_exists('Boxo_Admin')) {
    1414    class Boxo_Admin {
     15        /**
     16         * @return void
     17         */
    1518        public static function add_menu_item() {
    1619            if (!current_user_can('manage_woocommerce')) {
     
    2831        }
    2932
     33        /**
     34         * @return void
     35         */
    3036        public static function add_options_page() {
    3137            if (!current_user_can('manage_woocommerce')) {
     
    5359        }
    5460
     61        /**
     62         * @param mixed $links
     63         * @return mixed
     64         */
    5565        public static function add_settings_link($links) {
     66            // If filter arg type is incorrect due to external code, just get out of the way and return the arg.
     67            if (!is_array($links)) {
     68                return $links;
     69            }
    5670            $url = admin_url('admin.php?page=boxo-return');
    5771            $action_links = array(
  • boxo-return/tags/0.0.61/admin/order.php

    r3243704 r3371141  
    3131if (!class_exists('Boxo_Order')) {
    3232    class Boxo_Order {
     33        /**
     34         * @return void
     35         */
    3336        public static function init() {
    3437            wp_enqueue_style('boxo_order', plugins_url('order.css', __FILE__));
    3538        }
    3639
     40        /**
     41         * @param mixed $columns
     42         * @return mixed
     43         */
    3744        public static function order_list_add_packaging_column($columns) {
     45            // If filter arg type is incorrect due to external code, just get out of the way and return the arg.
     46            if (!is_array($columns)) {
     47                return $columns;
     48            }
    3849            $columns['boxo_packaging'] = esc_html__('Packaging', 'boxo-return');
    3950            return $columns;
    4051        }
    4152
     53        /**
     54         * @param mixed $column_name
     55         * @param mixed $order
     56         * @return void
     57         */
    4258        public static function order_list_populate_column_hpos($column_name, $order) {
    43             if ($column_name === 'boxo_packaging') {
    44                 self::populate_column($order);
    45             }
    46         }
    47 
     59            if ($column_name === 'boxo_packaging' && $order instanceof WC_Order) {
     60                $packaging = $order->get_meta('boxo_packaging', true);
     61                if (!is_string($packaging)) {
     62                    return;
     63                }
     64                self::populate_column($packaging);
     65            }
     66        }
     67
     68        /**
     69         * @param mixed $column_name
     70         * @param mixed $order_id
     71         * @return void
     72         */
    4873        public static function order_list_populate_column_cpt($column_name, $order_id) {
    4974            $order = wc_get_order($order_id);
     75            if (is_bool($order)) {
     76                return;
     77            }
    5078            if ($column_name === 'boxo_packaging') {
    51                 self::populate_column($order);
    52             }
    53         }
    54 
    55         public static function populate_column($order) {
    56             $packaging = $order->get_meta('boxo_packaging');
    57             $cell = "";
     79                $packaging = $order->get_meta('boxo_packaging', true);
     80                if (!is_string($packaging)) {
     81                    return;
     82                }
     83                self::populate_column($packaging);
     84            }
     85        }
     86
     87        /**
     88         * @param string $packaging
     89         * @return void
     90         */
     91        public static function populate_column($packaging) {
    5892            switch ($packaging) {
    5993                case Boxo_Constants::PACKAGING_REUSABLE:
    60                     $cell = esc_html__('Reusable', 'boxo-return');
    61                     break;
     94                    echo esc_html__('Reusable', 'boxo-return');
     95                    return;
    6296                case Boxo_Constants::PACKAGING_DISPOSABLE:
    63                     $cell = esc_html__('Disposable', 'boxo-return');
    64                     break;
     97                    echo esc_html__('Disposable', 'boxo-return');
     98                    return;
    6599                default:
    66100                    // If the order was placed when BOXO Return was not active, leave packaging field empty.
    67                     break;
    68             }
    69             echo $cell;
    70         }
    71 
     101                    return;
     102            }
     103        }
     104
     105        /**
     106         * @param mixed $data
     107         * @param mixed $order
     108         * @return mixed
     109         */
    72110        public static function order_preview_add_packaging_data($data, $order) {
    73             if ($packaging = $order->get_meta('boxo_packaging')) {
    74                 $data['boxo_packaging'] = $packaging;
    75             }
     111            // If filter arg type is incorrect due to external code, just get out of the way and return the arg.
     112            if (!is_array($data) || !($order instanceof WC_Order)) {
     113                return $data;
     114            }
     115            $packaging = $order->get_meta('boxo_packaging', true);
     116            if (!is_string($packaging)) {
     117                return $data;
     118            }
     119            $data['boxo_packaging'] = $packaging;
    76120            return $data;
    77121        }
    78122
     123        /**
     124         * @return void
     125         */
    79126        public static function order_preview_add_packaging() {
    80127            $title_safe = esc_html__('Packaging', 'boxo-return');
     
    101148        }
    102149
     150        /**
     151         * @param mixed $order
     152         * @return void
     153         */
    103154        public static function order_detail_add_packaging($order) {
     155            if (!($order instanceof WC_Order)) {
     156                return;
     157            }
    104158            $packaging = $order->get_meta('boxo_packaging', true);
    105159            $title_safe = esc_html__('Packaging', 'boxo-return');
     
    124178        }
    125179
     180        /**
     181         * @return string
     182         */
    126183        private static function reusable_label() {
    127184            $heart_icon_safe = file_get_contents(WP_PLUGIN_DIR . '/boxo-return/icons/heart.svg');
     
    135192        }
    136193
     194        /**
     195         * @return string
     196         */
    137197        private static function disposable_label() {
    138198            return esc_html__('Disposable packaging', 'boxo-return');
  • boxo-return/tags/0.0.61/admin/product-picker.php

    r3276747 r3371141  
    2323            foreach ($ids as $id) {
    2424                $product = wc_get_product($id);
    25                 if (!$product) {
     25                if (!($product instanceof WC_Product)) {
    2626                    continue;
    2727                }
    28 
    29                 $image = wp_get_attachment_image_src(
    30                     $product->get_image_id(),
     28                $image_data = wp_get_attachment_image_src(
     29                    intval($product->get_image_id()),
    3130                    [30, 30],
    3231                );
     
    3433                    'id' => $id,
    3534                    'title' => $product->get_title(),
    36                     'image' => $image ? $image[0] : null,
     35                    'image' => is_array($image_data) ? $image_data[0] : null,
    3736                ]);
    3837            }
  • boxo-return/tags/0.0.61/admin/settings.php

    r3310810 r3371141  
    1212
    1313// When saving, make sure checkbox input is saved as boolean.
    14 add_filter('pre_update_option_boxo_options', function ($val) {
    15     $val['disposable_fee_enabled'] = isset($val['disposable_fee_enabled']) ? true : false;
    16     $val['cart_limit_enabled'] = isset($val['cart_limit_enabled']) ? true : false;
    17     return $val;
     14add_filter('pre_update_option_boxo_options', function ($vals) {
     15    // If filter arg type is incorrect due to external code, just get out of the way and return the arg.
     16    if (!is_array($vals)) {
     17        return $vals;
     18    }
     19    $vals['disposable_fee_enabled'] = isset($vals['disposable_fee_enabled']) ? true : false;
     20    $vals['cart_limit_enabled'] = isset($vals['cart_limit_enabled']) ? true : false;
     21    return $vals;
    1822}, 10, 1);
    1923
    2024if (!class_exists('Boxo_Settings')) {
    2125    class Boxo_Settings {
     26        /**
     27         * @return void
     28         */
    2229        public static function init() {
    2330            if (!current_user_can('manage_woocommerce')) {
     
    127134        }
    128135
     136        /**
     137         * @return void
     138         */
    129139        public static function add_section_header() {
    130140            // Empty but required.
    131141        }
    132142
     143        /**
     144         * @param mixed $hook_suffix
     145         * @return void
     146         */
    133147        public static function enqueue_scripts($hook_suffix) {
    134             // Only load on boxo-return admin page.
    135             if (!str_contains($hook_suffix, 'boxo-return')) {
     148            // Only load on admin page for boxo-return.
     149            if (!is_string($hook_suffix) || strpos($hook_suffix, 'boxo-return') === false) {
    136150                return;
    137151            }
    138             wp_enqueue_script('alpine', 'https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js', null, '3.14.5', true);
     152            wp_enqueue_script('alpine', 'https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js', [], '3.14.5', true);
    139153            wp_enqueue_script('boxo_product_picker', plugins_url('product-picker.js', __FILE__));
    140154        }
    141155
     156        /**
     157         * @return void
     158         */
    142159        public static function add_api_key_field() {
    143160            $value_safe = esc_attr(Boxo_Options::api_key());
     
    157174        }
    158175
     176        /**
     177         * @return void
     178         */
    159179        public static function add_deposit_cents_field() {
    160             $value_safe = esc_attr(Boxo_Options::deposit_cents());
     180            $value_safe = esc_attr(strval(Boxo_Options::deposit_cents()));
    161181            echo <<<HTML
    162182            <input id="boxo_deposit-cents-input" name="boxo_options[deposit_cents]" type="number" min="0" max="10000" value="$value_safe">
     
    164184        }
    165185
     186        /**
     187         * @return void
     188         */
    166189        public static function add_disposable_fee_enabled_field() {
    167190            $enabled = Boxo_Options::disposable_fee_enabled();
     
    170193            $label_safe = esc_html__('Enable disposable packaging fee', 'boxo-return');
    171194            $description_safe = esc_html__('If enabled, a fee is charged when the customer does not select reusable packaging.', 'boxo-return');
    172             $fee_safe = esc_attr(Boxo_Options::disposable_fee_cents());
     195            $fee_safe = esc_attr(strval(Boxo_Options::disposable_fee_cents()));
    173196            $cents_label_safe = esc_html__('Fee in cents:', 'boxo-return');
    174197            echo <<<HTML
     
    203226        }
    204227
     228        /**
     229         * @return void
     230         */
    205231        public static function add_selection_mode_field() {
    206232            $selection_mode = Boxo_Options::selection_mode();
     
    212238            $disposable_safe = Boxo_Options::DEFAULT_PACKAGING_DISPOSABLE;
    213239
    214             $choice_selected_attb_safe = $selection_mode == Boxo_Options::SELECTION_MODE_CHOICE ? 'selected' : '';
    215             $force_reusable_selected_attb_safe = $selection_mode == Boxo_Options::SELECTION_MODE_FORCE_REUSABLE ? 'selected' : '';
    216             $reusable_selected_attb_safe = $default_packaging == Boxo_Options::DEFAULT_PACKAGING_REUSABLE ? 'selected' : '';
    217             $disposable_selected_attb_safe = $default_packaging == Boxo_Options::DEFAULT_PACKAGING_DISPOSABLE ? 'selected' : '';
     240            $choice_selected_attb_safe = $selection_mode === Boxo_Options::SELECTION_MODE_CHOICE ? 'selected' : '';
     241            $force_reusable_selected_attb_safe = $selection_mode === Boxo_Options::SELECTION_MODE_FORCE_REUSABLE ? 'selected' : '';
     242            $reusable_selected_attb_safe = $default_packaging === Boxo_Options::DEFAULT_PACKAGING_REUSABLE ? 'selected' : '';
     243            $disposable_selected_attb_safe = $default_packaging === Boxo_Options::DEFAULT_PACKAGING_DISPOSABLE ? 'selected' : '';
    218244
    219245            $default_packaging_label_safe = esc_html__('Default selection:', 'boxo-return');
     
    246272        }
    247273
     274        /**
     275         * @return void
     276         */
    248277        public static function add_info_url_field() {
    249278            $value_safe = esc_attr(Boxo_Options::info_url());
     
    262291        }
    263292
     293        /**
     294         * @return void
     295         */
    264296        public static function add_product_allow_mode_field() {
    265297            $allow_mode = Boxo_Options::product_allow_mode();
     
    269301            $included_only_safe = Boxo_Options::PRODUCT_ALLOW_MODE_INCLUDED_ONLY;
    270302
    271             $all_selected_attb_safe = $allow_mode == Boxo_Options::PRODUCT_ALLOW_MODE_ALL ? 'selected' : '';
    272             $all_except_excluded_selected_attb_safe = $allow_mode == Boxo_Options::PRODUCT_ALLOW_MODE_ALL_EXCEPT_EXCLUDED ? 'selected' : '';
    273             $included_only_selected_attb_safe = $allow_mode == Boxo_Options::PRODUCT_ALLOW_MODE_INCLUDED_ONLY ? 'selected' : '';
     303            $all_selected_attb_safe = $allow_mode === Boxo_Options::PRODUCT_ALLOW_MODE_ALL ? 'selected' : '';
     304            $all_except_excluded_selected_attb_safe = $allow_mode === Boxo_Options::PRODUCT_ALLOW_MODE_ALL_EXCEPT_EXCLUDED ? 'selected' : '';
     305            $included_only_selected_attb_safe = $allow_mode === Boxo_Options::PRODUCT_ALLOW_MODE_INCLUDED_ONLY ? 'selected' : '';
    274306
    275307            $all_label_safe = esc_html__('All products', 'boxo-return');
     
    311343        }
    312344
     345        /**
     346         * @return void
     347         */
    313348        public static function add_cart_limit_enabled_field() {
    314349            $enabled = Boxo_Options::cart_limit_enabled();
     
    317352            $label_safe = esc_html__('Enable cart limit', 'boxo-return');
    318353            $description_safe = esc_html__('If enabled, reusable packaging will not be available when the cart exceeds the set amount of items.', 'boxo-return');
    319             $limit_safe = esc_attr(Boxo_Options::cart_limit());
     354            $limit_safe = esc_attr(strval(Boxo_Options::cart_limit()));
    320355            $limit_label_safe = esc_html__('Max items:', 'boxo-return');
    321356            echo <<<HTML
  • boxo-return/tags/0.0.61/boxo-return.php

    r3349705 r3371141  
    22/*
    33* Plugin Name: BOXO Return
    4 * Version: 0.0.60
     4* Version: 0.0.61
     5* Requires at least: 6.5
     6* Requires PHP: 7.1
    57* Description: Allows customers to select reusable packaging during checkout.
    68* Author: BOXO
     
    2022}
    2123
    22 const BOXO_RETURN_PLUGIN_VERSION = "0.0.60";
     24const BOXO_RETURN_PLUGIN_VERSION = "0.0.61";
    2325
    2426include plugin_dir_path(__FILE__) . 'includes/constants.php';
     27include plugin_dir_path(__FILE__) . 'includes/setup.php';
    2528include plugin_dir_path(__FILE__) . 'includes/options.php';
    26 include plugin_dir_path(__FILE__) . 'includes/api.php';
     29include plugin_dir_path(__FILE__) . 'includes/postal-code.php';
     30include plugin_dir_path(__FILE__) . 'includes/available.php';
    2731include plugin_dir_path(__FILE__) . 'includes/ajax.php';
    28 include plugin_dir_path(__FILE__) . 'includes/logger.php';
    2932if (is_admin()) {
    3033    include plugin_dir_path(__FILE__) . 'admin/admin.php';
     
    3437}
    3538include plugin_dir_path(__FILE__) . 'checkout/checkout.php';
     39
     40register_activation_hook(__FILE__, 'Boxo_Setup::handle_activation');
     41register_deactivation_hook(__FILE__, 'Boxo_Setup::handle_deactivation');
    3642
    3743// BOXO Return plugin is incompatible with Checkout Blocks.
     
    4450add_action('init', 'boxo_load_textdomain');
    4551
     52/**
     53 * @return void
     54 */
    4655function boxo_load_textdomain() {
    4756    load_plugin_textdomain('boxo-return', false, dirname(plugin_basename(__FILE__)) . '/languages');
  • boxo-return/tags/0.0.61/checkout/boxo-checkout.js

    r3317144 r3371141  
    1 import {parsePostalCode} from '../utils/utils.js'
    2 
    3 const CHECK_AVAILABLE_TIMEOUT_MS = 5000
    4 
    51if (document.readyState !== 'loading') {
    62  init()
     
    106
    117async function init() {
    12   const pluginData = getPluginData()
    13   console.info('[BOXO Return] ' + pluginData.boxoVersion)
    14 
    158  // Must use jQuery for event handlers because propagation of certain events appears to be blocked.
    169  jQuery(function ($) {
     10    // AJAX update checkout (incl. total price) on change in relevant data or packaging selection.
    1711    $(document.body).on(
    1812      'change',
    19       '#billing_country, #billing_postcode, #shipping_country, #shipping_postcode, #ship-to-different-address-checkbox',
     13      '#billing_country, #billing_postcode, #shipping_country, #shipping_postcode, #ship-to-different-address-checkbox, [name=boxo_packaging]',
    2014      async () => {
    21         await handleChange()
    2215        document.body.dispatchEvent(new Event('update_checkout'))
    2316      }
    2417    )
    25 
    26     // AJAX update checkout (incl. total price) on packaging change.
    27     $(document.body).on('change', '[name=boxo_packaging]', (e) => {
    28       document.body.dispatchEvent(new Event('update_checkout'))
    29     })
    3018  })
    31 
    32   // Check Boxo availability on initial load, in case postal code was pre-filled.
    33   await handleChange()
    34   document.body.dispatchEvent(new Event('update_checkout'))
    3519
    3620  console.info('[BOXO Return] Ready')
    3721}
    38 
    39 /**
    40  * Data added to the page by the plugin.
    41  * @typedef {Object} PluginData
    42  * @property {string} boxoVersion - The version of the Boxo Return plugin.
    43  * @property {string} boxoAvailableUrl - The URL of the endpoint where Boxo availability can be checked.
    44  * @property {string} cartProducts - A comma-separated list of IDs of the products currently in cart.
    45  * @property {string} cartItemCount - The amount of items currently in cart.
    46  * @property {string} shopCountry - The base country of the shop.
    47  */
    48 
    49 function getPluginData() {
    50   // PluginData is assumed to have been added to the window object.
    51   /** @type {Window & { BoxoReturn?: PluginData }} */
    52   const win = window
    53   const pluginData = win.BoxoReturn
    54   if (!pluginData) {
    55     throw new Error('[BOXO Return] Could not get plugin data')
    56   }
    57   return pluginData
    58 }
    59 
    60 /**
    61  * Check whether Boxo can be used and show or hide the packaging input accordingly.
    62  */
    63 async function handleChange() {
    64   const pluginData = getPluginData()
    65 
    66   // If shipping address is different from billing address, use shipping address.
    67   // Otherwise, use billing address.
    68   const shippingAddressCheckbox = document.getElementById('ship-to-different-address-checkbox')
    69   const hasShippingAddress =
    70     shippingAddressCheckbox instanceof HTMLInputElement && shippingAddressCheckbox.checked
    71 
    72   const countryInput = hasShippingAddress
    73     ? document.getElementById('shipping_country')
    74     : document.getElementById('billing_country')
    75 
    76   // Shipping/billing countries can come from a <select> (multiple countries), a hidden <input> (single country),
    77   // or can be removed from the page entirely, in which case the shop country is used.
    78   const country =
    79     countryInput instanceof HTMLSelectElement || countryInput instanceof HTMLInputElement
    80       ? countryInput.value
    81       : pluginData.shopCountry
    82   if (country !== 'NL') {
    83     setBoxoUnavailable()
    84     return
    85   }
    86 
    87   const postalCodeInput = hasShippingAddress
    88     ? document.getElementById('shipping_postcode')
    89     : document.getElementById('billing_postcode')
    90   if (!(postalCodeInput instanceof HTMLInputElement)) {
    91     console.error('[BOXO Return] Could not get postal code input')
    92     setBoxoUnavailable()
    93     return
    94   }
    95 
    96   const postalCode = parsePostalCode(postalCodeInput.value)
    97   if (!postalCode) {
    98     setBoxoUnavailable()
    99     return
    100   }
    101 
    102   if (await checkBoxoAvailable(postalCode)) {
    103     setBoxoAvailable()
    104     return
    105   }
    106   setBoxoUnavailable()
    107 }
    108 
    109 /**
    110  * Check whether Boxo is available for the current cart contents and a postal code.
    111  * @param {string} postalCode
    112  */
    113 async function checkBoxoAvailable(postalCode) {
    114   try {
    115     const pluginData = getPluginData()
    116     const url = new URL(pluginData.boxoAvailableUrl)
    117     url.searchParams.set('postal_code', postalCode)
    118     url.searchParams.set('products', pluginData.cartProducts)
    119     url.searchParams.set('item_count', pluginData.cartItemCount)
    120 
    121     const ac = new AbortController()
    122     setTimeout(() => ac.abort('Timeout'), CHECK_AVAILABLE_TIMEOUT_MS)
    123     const res = await fetch(url.toString(), {signal: ac.signal})
    124     if (!res.ok) {
    125       const body = await res.json()
    126       console.error(`[BOXO Return] ${res.status} ${body.error}`)
    127       return false
    128     }
    129 
    130     const {available, reason} = await res.json()
    131     if (typeof available !== 'boolean') {
    132       console.error('[BOXO Return] Unexpected response')
    133       return false
    134     }
    135 
    136     if (!available) {
    137       console.info('[BOXO Return] ' + reason)
    138       return false
    139     }
    140 
    141     console.info('[BOXO Return] Available')
    142     return true
    143   } catch (err) {
    144     console.error('[BOXO Return]', err)
    145     return false
    146   }
    147 }
    148 
    149 function setBoxoAvailable() {
    150   const availableInput = document.querySelector('input[name=boxo_available]')
    151   if (!(availableInput instanceof HTMLInputElement)) {
    152     throw new Error('[BOXO Return] Could not get available input')
    153   }
    154   availableInput.value = 'true'
    155 }
    156 
    157 function setBoxoUnavailable() {
    158   const availableInput = document.querySelector('input[name=boxo_available]')
    159   if (!(availableInput instanceof HTMLInputElement)) {
    160     throw new Error('[BOXO Return] Could not get available input')
    161   }
    162   availableInput.value = 'false'
    163 }
  • boxo-return/tags/0.0.61/checkout/checkout.php

    r3349705 r3371141  
    66
    77// Add Boxo features to checkout page after WooCommerce has loaded.
    8 add_action('woocommerce_init', 'Boxo_Checkout::init', 10);
     8add_action('woocommerce_init', 'Boxo_Checkout::init');
    99
    1010if (!class_exists('Boxo_Checkout')) {
     
    1212        private const DEFAULT_INFO_URL = 'https://www.boxo.nu';
    1313
     14        /**
     15         * @return void
     16         */
    1417        public static function init() {
    1518            // Add css and javascript.
    1619            add_action('wp_enqueue_scripts', 'Boxo_Checkout::enqueue_scripts');
    1720
    18             // Add plugin data: add to head to prevent possible interference from checkout page customizations.
    19             add_action('wp_head', 'Boxo_Checkout::add_plugin_data');
    20 
    21             // Add boxo available field which is updated by the client and read by the server in POST requests.
    22             add_action('woocommerce_checkout_before_customer_details', 'Boxo_Checkout::add_boxo_available_field');
     21            // Log plugin version to browser console.
     22            add_action('wp_head', 'Boxo_Checkout::log_plugin_version');
    2323
    2424            // Add packaging field.
     
    3232        }
    3333
     34        /**
     35         * @return void
     36         */
    3437        public static function enqueue_scripts() {
    3538            if (!Boxo_Checkout::should_load()) {
     
    4043        }
    4144
    42         public static function add_plugin_data() {
     45        /**
     46         * @return void
     47         */
     48        public static function log_plugin_version() {
    4349            $boxo_version_safe = BOXO_RETURN_PLUGIN_VERSION;
    44             $boxo_available_url_safe = esc_url(rest_url('boxo/available'));
    45             $cart_products_safe = esc_attr(implode(',', array_map(fn($item) => $item['product_id'], WC()->cart->cart_contents)));
    46             $cart_item_count_safe = esc_attr(WC()->cart->get_cart_contents_count());
    47             $shop_country_safe = esc_attr(WC()->countries->get_base_country());
    4850            echo <<<HTML
    4951            <script>
    50                 window.BoxoReturn = {
    51                     boxoVersion: "$boxo_version_safe",
    52                     boxoAvailableUrl: "$boxo_available_url_safe",
    53                     cartProducts: "$cart_products_safe",
    54                     cartItemCount: "$cart_item_count_safe",
    55                     shopCountry: "$shop_country_safe",
    56                 }
     52                console.info('[BOXO Return] $boxo_version_safe')
    5753            </script>
    5854            HTML;
    5955        }
    6056
    61         public static function add_boxo_available_field() {
    62             // Wrapping with div to prevent automatically added p tags.
    63             echo <<<HTML
    64             <div style="display: contents;">
    65                 <input name="boxo_available" type="hidden" value="false">
    66             </div>
    67             HTML;
    68         }
    69 
     57        /**
     58         * @return void
     59         */
    7060        public static function add_packaging_field() {
    7161            if (!Boxo_Checkout::should_load()) {
     
    7363            }
    7464
    75             // Only run on POST (particularly AJAX ?wc-ajax=update_order_review),
    76             // because boxo_available is not yet known on page load.
    77             $post_data = self::get_post_data();
    78             if (
    79                 !$post_data ||
    80                 !isset($post_data['boxo_available']) ||
    81                 $post_data['boxo_available'] !== 'true'
    82             ) {
    83                 return;
    84             }
     65            $result = self::boxo_available_for_request();
     66            if (is_string($result['message'])) {
     67                $message_safe = esc_js($result['message']);
     68                echo <<<HTML
     69                <script>console.info('[BOXO Return] $message_safe')</script>
     70                HTML;
     71            }
     72            if (!$result['available']) {
     73                return;
     74            }
     75
     76            $title_html_safe = esc_html__('Packaging', 'boxo-return');
     77            $title_attr_safe = esc_attr__('Packaging', 'boxo-return');
     78
     79            $inputs = Boxo_Options::selection_mode() === Boxo_Options::SELECTION_MODE_FORCE_REUSABLE ?
     80                self::inputs_force_reusable()
     81                : self::inputs_regular();
     82
     83            // Based on the HTML for standard shipping inputs in WooCommerce.
     84            echo <<<HTML
     85            <tr id="boxo_container" class="woocommerce-shipping-totals shipping">
     86                <th class="boxo_header">$title_html_safe</th>
     87                <td data-title="$title_attr_safe">
     88                    $inputs
     89                </td>
     90            </tr>
     91            HTML;
     92        }
     93
     94        /**
     95         * @return string
     96         */
     97        private static function inputs_regular() {
     98            // Persist current selection or use default.
     99            $selected = self::get_selected_packaging();
     100            $checked = is_string($selected) ? $selected : Boxo_Options::default_packaging();
    85101
    86102            $reusable_attr_safe = esc_attr(Boxo_Constants::PACKAGING_REUSABLE);
    87103            $disposable_attr_safe = esc_attr(Boxo_Constants::PACKAGING_DISPOSABLE);
    88             $title_html_safe = esc_html__('Packaging', 'boxo-return');
    89             $title_attr_safe = esc_attr__('Packaging', 'boxo-return');
    90104            $reusable_label_safe = self::reusable_label_safe();
    91             $disposable_label_safe = self::disposable_label_safe();
    92 
    93             $force_reusable_inputs = <<<HTML
    94             $reusable_label_safe
    95             <input name="boxo_packaging" value="$reusable_attr_safe" hidden />
    96             HTML;
    97 
    98             // Persist current selection or use default.
    99             $selected = isset($post_data['boxo_packaging']) ? $post_data['boxo_packaging'] : Boxo_Options::default_packaging();
    100             $reusable_checked_attr_safe = $selected === Boxo_Constants::PACKAGING_REUSABLE ? 'checked' : '';
    101             $disposable_checked_attr_safe = $selected === Boxo_Constants::PACKAGING_DISPOSABLE ? 'checked' : '';
    102 
    103             $regular_inputs = <<<HTML
     105            $disposable_safe = esc_html__('Disposable', 'boxo-return');
     106            $disposable_fee_formatted_safe = Boxo_Options::disposable_fee_enabled()
     107                ? esc_html('+ €' . number_format(Boxo_Options::disposable_fee_cents() / 100, 2, ',', '.'))
     108                : '';
     109            $trash_icon_safe = file_get_contents(WP_PLUGIN_DIR . '/boxo-return/icons/trash.svg');
     110            $reusable_checked_attr_safe = $checked === Boxo_Constants::PACKAGING_REUSABLE ? 'checked' : '';
     111            $disposable_checked_attr_safe = $checked === Boxo_Constants::PACKAGING_DISPOSABLE ? 'checked' : '';
     112
     113            return <<<HTML
    104114            <ul id="shipping_method" class="woocommerce-shipping-methods">
    105115                <li class="boxo_list-item-reusable">
     
    126136                    />
    127137                    <label for="boxo_packaging-disposable">
    128                         $disposable_label_safe
     138                        <span class="boxo_option-icon">$trash_icon_safe</span>
     139                        $disposable_safe
     140                        $disposable_fee_formatted_safe
    129141                    </label>
    130142                </li>
    131143            </ul>
    132144            HTML;
    133 
    134             $inputs = Boxo_Options::selection_mode() === Boxo_Options::SELECTION_MODE_FORCE_REUSABLE ?
    135                 $force_reusable_inputs
    136                 : $regular_inputs;
    137 
    138             // Based on the HTML for standard shipping inputs in WooCommerce.
    139             echo <<<HTML
    140             <tr id="boxo_container" class="woocommerce-shipping-totals shipping">
    141                 <th class="boxo_header">$title_html_safe</th>
    142                 <td data-title="$title_attr_safe">
    143                     $inputs
    144                 </td>
    145             </tr>
    146             HTML;
    147         }
    148 
     145        }
     146
     147        /**
     148         * @return string
     149         */
     150        private static function inputs_force_reusable() {
     151            $reusable_label_safe = self::reusable_label_safe();
     152            $reusable_attr_safe = esc_attr(Boxo_Constants::PACKAGING_REUSABLE);
     153
     154            return <<<HTML
     155            $reusable_label_safe
     156            <input name="boxo_packaging" value="$reusable_attr_safe" hidden />
     157            HTML;
     158        }
     159
     160        /**
     161         * @return string
     162         */
    149163        private static function reusable_label_safe() {
    150164            $heart_icon_safe = file_get_contents(WP_PLUGIN_DIR . '/boxo-return/icons/heart.svg');
    151165            $reusable_safe = esc_html__('Reusable (deposit)', 'boxo-return');
    152             $info_url_safe = esc_url(Boxo_Options::info_url() ?: self::DEFAULT_INFO_URL);
     166            $info_url_option = Boxo_Options::info_url();
     167            $info_url_safe = esc_url($info_url_option !== '' ? $info_url_option : self::DEFAULT_INFO_URL);
    153168            $info_icon_safe = file_get_contents(WP_PLUGIN_DIR . '/boxo-return/icons/info.svg');
    154169            $info_safe = sprintf(esc_html__('Nice, there is a return point near you! Return the packaging at a return point to get your deposit (€%s) back right away. Click for more information.', 'boxo-return'), number_format(Boxo_Options::deposit_cents() / 100, 2, ',', '.'));
     
    164179        }
    165180
    166         private static function disposable_label_safe() {
    167             $disposable_fee_formatted_safe = Boxo_Options::disposable_fee_enabled() ? esc_html('+ €' . number_format(Boxo_Options::disposable_fee_cents() / 100, 2, ',', '.')) : '';
    168             $trash_icon_safe = file_get_contents(WP_PLUGIN_DIR . '/boxo-return/icons/trash.svg');
    169             $disposable_safe = esc_html__('Disposable', 'boxo-return');
    170 
    171             return <<<HTML
    172             <span class="boxo_option-icon">$trash_icon_safe</span>
    173             $disposable_safe
    174             $disposable_fee_formatted_safe
    175             HTML;
    176         }
    177 
     181        /**
     182         * @return void
     183         */
    178184        public static function calculate_fees() {
    179185            if (!Boxo_Checkout::should_load()) {
     
    181187            }
    182188
    183             // Only run on POST (particularly AJAX ?wc-ajax=update_order_review and checkout submit),
    184             // because boxo_available is not yet known on page load.
    185189            // If Boxo is not available for postal code, do not add any fees.
    186             $post_data = self::get_post_data();
    187             if (
    188                 !$post_data ||
    189                 !isset($post_data['boxo_available']) ||
    190                 $post_data['boxo_available'] !== 'true'
    191             ) {
    192                 return;
    193             }
    194 
    195             $selected = isset($post_data['boxo_packaging'])
    196                 ? $post_data['boxo_packaging']
     190            if (!self::boxo_available_for_request()['available']) {
     191                return;
     192            }
     193
     194            $selected = self::get_selected_packaging();
     195            $packaging = is_string($selected)
     196                ? $selected
    197197                : Boxo_Options::default_packaging();
    198198
    199             if ($selected === Boxo_Constants::PACKAGING_REUSABLE) {
     199            if ($packaging === Boxo_Constants::PACKAGING_REUSABLE) {
    200200                WC()->cart->add_fee(
    201201                    __('Deposit', 'boxo-return'),
     
    219219        }
    220220
     221        /**
     222         * @param mixed $order_id
     223         * @return void
     224         */
    221225        public static function update_order_meta($order_id) {
    222226            if (!Boxo_Checkout::should_load()) {
     
    224228            }
    225229
    226             $post_data = self::get_post_data();
    227             $boxo_available = isset($post_data['boxo_available']) && $post_data['boxo_available'] === 'true';
    228             $packaging = $boxo_available ? sanitize_text_field($_POST['boxo_packaging']) : Boxo_Constants::PACKAGING_DISPOSABLE;
    229 
    230230            $order = wc_get_order($order_id);
     231            if (is_bool($order)) {
     232                return;
     233            }
     234
     235            $selected_packaging = self::get_selected_packaging();
     236            $packaging = self::boxo_available_for_request()['available'] && is_string($selected_packaging)
     237                ? sanitize_text_field($selected_packaging)
     238                : Boxo_Constants::PACKAGING_DISPOSABLE;
    231239            $order->update_meta_data('boxo_packaging', $packaging);
    232240            $order->save_meta_data();
    233241        }
    234242
     243        /**
     244         * @return bool
     245         */
    235246        public static function should_load() {
    236247            // Only load on the actual checkout page, not on the order received page.
     
    238249        }
    239250
    240         private static function get_post_data() {
    241             // AJAX requests.
    242             if (isset($_POST['post_data'])) {
     251        /**
     252         * @var array{available: bool, message: string|null}|null
     253         */
     254        private static $boxo_available_for_request_cached = null;
     255
     256        /**
     257         * Check the availability of BOXO for the current customer and cart.
     258         * Results are cached per incoming request.
     259         * @return array{available: bool, message: string|null}
     260         */
     261        private static function boxo_available_for_request() {
     262            if (is_array(self::$boxo_available_for_request_cached)) {
     263                return self::$boxo_available_for_request_cached;
     264            }
     265
     266            // WooCommerce handles choice between billing/shipping address internally:
     267            // if only billing address is supplied, this will be used as shipping address.
     268            $customer = WC()->customer;
     269            $postal_code = $customer->get_shipping_postcode();
     270            $country = $customer->get_shipping_country();
     271            $cart_contents_count = WC()->cart->get_cart_contents_count();
     272
     273            $cart_product_ids = [];
     274            foreach (WC()->cart->get_cart_contents() as $item) {
     275                if (!is_array($item) || !isset($item['product_id']) || !is_int($item['product_id'])) {
     276                    continue;
     277                }
     278                array_push($cart_product_ids, $item['product_id']);
     279            }
     280
     281            $res = Boxo_Available::boxo_available($postal_code, $country, $cart_contents_count, $cart_product_ids);
     282            self::$boxo_available_for_request_cached = $res;
     283            return $res;
     284        }
     285
     286        /**
     287         * @return string|null
     288         */
     289        private static function get_selected_packaging() {
     290            if (isset($_POST['post_data']) && is_string($_POST['post_data'])) {
     291                // AJAX requests.
    243292                parse_str($_POST['post_data'], $post_data);
    244                 return $post_data;
    245             }
    246             // Non-AJAX requests (eg. checkout submit).
    247             return $_POST;
     293            } else {
     294                // Non-AJAX requests (eg. checkout submit).
     295                $post_data = $_POST;
     296            }
     297            return isset($post_data['boxo_packaging']) && is_string($post_data['boxo_packaging'])
     298                ? $post_data['boxo_packaging']
     299                : null;
    248300        }
    249301    }
  • boxo-return/tags/0.0.61/includes/ajax.php

    r3258642 r3371141  
    1313         * - keyword: the keyword to search for. If not supplied, all products (within the page limit) will be returned.
    1414         * - skip: a comma-separated list of product IDs that should be skipped.
     15         *
     16         * @return void
    1517         */
    1618        public static function handle_product_search() {
     
    1820            check_ajax_referer('boxo');
    1921
    20             $keyword = $_GET['keyword'];
    21             $skip = $_GET['skip'];
     22            $keyword = is_string($_GET['keyword']) ? $_GET['keyword'] : '';
     23            $skip = is_string($_GET['skip']) && $_GET['skip'] !== '' ? $_GET['skip'] : null;
    2224
    2325            global $wpdb;
    24             $skip_ids = explode(',', $skip);
     26            if (!($wpdb instanceof wpdb)) {
     27                wp_send_json_error('Internal server error', 500);
     28            }
     29
     30            $skip_ids = is_string($skip) ? explode(',', $skip) : [];
    2531            $skip_ids_safe = implode(",", array_map(fn($id) => esc_sql($id), $skip_ids));
     32
    2633            $query = "SELECT ID FROM {$wpdb->prefix}posts
    2734                        WHERE post_type = 'product'"
    28                 . ($skip_ids_safe ? " AND ID NOT IN ($skip_ids_safe)" : "") .
     35                . ($skip_ids_safe === '' ? '' : " AND ID NOT IN ($skip_ids_safe)") .
    2936                " AND post_status != 'trash'
    3037                        AND post_status != 'auto-draft'
     
    3340                        LIMIT %d";
    3441            $rows = $wpdb->get_results($wpdb->prepare(
     42                // @phpstan-ignore argument.type (Since WordPress 6.2.0, this can be solved nicely with %i, but this solution is not used out of backwards compatibility.)
    3543                $query,
    3644                // If keyword is empty, the first items are returned.
     
    3846                self::MAX_ITEMS
    3947            ), ARRAY_A);
     48            if (!is_array($rows)) {
     49                wp_send_json_error('Internal server error', 500);
     50            }
    4051
    41             $products = array_map(function ($row) {
     52            $products = [];
     53            foreach ($rows as $row) {
    4254                $product = wc_get_product($row['ID']);
    43                 return [
     55                if (!($product instanceof WC_Product)) {
     56                    continue;
     57                }
     58                $image_data = wp_get_attachment_image_src(
     59                    intval($product->get_image_id()),
     60                    [30, 30],
     61                );
     62                array_push($products, [
    4463                    'id' => $product->get_id(),
    4564                    'title' => $product->get_title(),
    46                     'image' => wp_get_attachment_image_src(
    47                         $product->get_image_id(),
    48                         [30, 30],
    49                     )[0],
    50                 ];
    51             }, $rows);
     65                    'image' => is_array($image_data) ? $image_data[0] : null,
     66                ]);
     67            }
    5268
    5369            wp_send_json($products);
    54             wp_die();
    5570        }
    5671    }
  • boxo-return/tags/0.0.61/includes/options.php

    r3310810 r3371141  
    3030        ];
    3131
     32        /**
     33         * @param string $key
     34         * @return mixed
     35         */
    3236        private static function option($key) {
    3337            $options = get_option('boxo_options', self::DEFAULT_OPTIONS);
    34             if (isset($options[$key])) {
    35                 return $options[$key];
     38            if (!is_array($options) || !isset($options[$key])) {
     39                return self::DEFAULT_OPTIONS[$key];
    3640            }
    37             return self::DEFAULT_OPTIONS[$key];
     41            return $options[$key];
    3842        }
    3943
     
    4246         */
    4347        public static function api_key() {
    44             return self::option("api_key");
     48            $val = self::option('api_key');
     49            if (!is_string($val)) {
     50                return self::DEFAULT_OPTIONS['api_key'];
     51            }
     52            return $val;
    4553        }
    4654
     
    4957         */
    5058        public static function deposit_cents() {
    51             return self::option("deposit_cents");
     59            $val = self::option('deposit_cents');
     60            if (!is_string($val) || !is_numeric($val)) {
     61                return self::DEFAULT_OPTIONS['deposit_cents'];
     62            }
     63            return intval($val);
    5264        }
    5365
     
    5668         */
    5769        public static function disposable_fee_enabled() {
    58             return self::option("disposable_fee_enabled");
     70            $val = self::option('disposable_fee_enabled');
     71            if (!is_bool($val)) {
     72                return self::DEFAULT_OPTIONS['disposable_fee_enabled'];
     73            }
     74            return $val;
    5975        }
    6076
    6177        /**
    62          * @return bool Optional fee for disposable packaging in cents. The fee is taxable and the amount is considered to include any taxes.
     78         * @return int Optional fee for disposable packaging in cents. The fee is taxable and the amount is considered to include any taxes.
    6379         */
    6480        public static function disposable_fee_cents() {
    65             return self::option("disposable_fee_cents");
     81            $val = self::option('disposable_fee_cents');
     82            if (!is_string($val) || !is_numeric($val)) {
     83                return self::DEFAULT_OPTIONS['disposable_fee_cents'];
     84            }
     85            return intval($val);
    6686        }
    6787
     
    7393         */
    7494        public static function selection_mode() {
    75             return self::option("selection_mode");
     95            $val = self::option('selection_mode');
     96            if (!is_string($val)) {
     97                return self::DEFAULT_OPTIONS['selection_mode'];
     98            }
     99            return $val;
    76100        }
    77101
     
    83107         */
    84108        public static function default_packaging() {
    85             return self::option("default_packaging");
     109            $val = self::option('default_packaging');
     110            if (!is_string($val)) {
     111                return self::DEFAULT_OPTIONS['default_packaging'];
     112            }
     113            return $val;
    86114        }
    87115
     
    90118         */
    91119        public static function info_url() {
    92             return self::option("info_url");
     120            $val = self::option('info_url');
     121            if (!is_string($val)) {
     122                return self::DEFAULT_OPTIONS['info_url'];
     123            }
     124            return $val;
    93125        }
    94126
     
    101133         */
    102134        public static function product_allow_mode() {
    103             return self::option("product_allow_mode");
     135            $val = self::option('product_allow_mode');
     136            if (!is_string($val)) {
     137                return self::DEFAULT_OPTIONS['product_allow_mode'];
     138            }
     139            return $val;
    104140        }
    105141
     
    108144         */
    109145        public static function excluded_product_ids() {
    110             return self::option("excluded_product_ids");
     146            $val = self::option('excluded_product_ids');
     147            if (!is_string($val)) {
     148                return self::DEFAULT_OPTIONS['excluded_product_ids'];
     149            }
     150            return $val;
    111151        }
    112152
     
    115155         */
    116156        public static function included_product_ids() {
    117             return self::option("included_product_ids");
     157            $val = self::option('included_product_ids');
     158            if (!is_string($val)) {
     159                return self::DEFAULT_OPTIONS['included_product_ids'];
     160            }
     161            return $val;
    118162        }
    119163
     
    123167         */
    124168        public static function cart_limit_enabled() {
    125             return self::option("cart_limit_enabled");
     169            $val = self::option('cart_limit_enabled');
     170            if (!is_bool($val)) {
     171                return self::DEFAULT_OPTIONS['cart_limit_enabled'];
     172            }
     173            return $val;
    126174        }
    127175
     
    131179         */
    132180        public static function cart_limit() {
    133             return self::option("cart_limit");
     181            $val = self::option('cart_limit');
     182            if (!is_string($val) || !is_numeric($val)) {
     183                return self::DEFAULT_OPTIONS['cart_limit'];
     184            }
     185            return intval($val);
    134186        }
    135187    }
  • boxo-return/tags/0.0.61/languages/boxo-return-nl.po

    r3317144 r3371141  
    3434msgstr "https://www.boxo.nu"
    3535
    36 #: admin/admin.php:58
    37 #: admin/settings.php:36
     36#: admin/admin.php:72
     37#: admin/settings.php:43
    3838msgid "Settings"
    3939msgstr "Instellingen"
    4040
    41 #: admin/settings.php:43
     41#: admin/settings.php:50
    4242msgid "API Key"
    4343msgstr "API-key"
    4444
    45 #: admin/settings.php:55
     45#: admin/settings.php:62
    4646msgid "Deposit amount in cents"
    4747msgstr "Statiegeld in aantal cent"
    4848
    49 #: admin/settings.php:67
     49#: admin/settings.php:74
    5050msgid "Disposable packaging fee"
    5151msgstr "Toeslag wegwerpverpakking"
    5252
    53 #: admin/settings.php:79
     53#: admin/settings.php:86
    5454msgid "Packaging selection"
    5555msgstr "Verpakking selecteren"
    5656
    57 #: admin/settings.php:91
     57#: admin/settings.php:98
    5858msgid "URL to information page (optional)"
    5959msgstr "Informatiepagina (optioneel)"
    6060
    61 #: admin/settings.php:103
     61#: admin/settings.php:110
    6262msgid "Allowed in reusable packaging"
    6363msgstr "Beschikbaar in herbruikbare verpakking"
    6464
    65 #: admin/settings.php:144
     65#: admin/settings.php:161
    6666msgid "Enter the API key provided by BOXO Return."
    6767msgstr "Voer de API-key in die je hebt ontvangen van BOXO Return."
    6868
    69 #: admin/admin.php:36
     69#: admin/admin.php:42
    7070msgid "Settings saved"
    7171msgstr "Instellingen bijgewerkt"
    7272
    73 #: admin/admin.php:48
     73#: admin/admin.php:54
    7474msgid "Save"
    7575msgstr "Opslaan"
    7676
    77 #: admin/product-picker.php:48
     77#: admin/product-picker.php:47
    7878msgid "Add a product:"
    7979msgstr "Product toevoegen:"
    8080
    81 #: admin/product-picker.php:51
     81#: admin/product-picker.php:50
    8282msgid "No products found."
    8383msgstr "Geen producten gevonden."
    8484
    85 #: admin/product-picker.php:52
     85#: admin/product-picker.php:51
    8686msgid "Remove"
    8787msgstr "Verwijderen"
    8888
    89 #: admin/settings.php:170
     89#: admin/settings.php:193
    9090msgid "Enable disposable packaging fee"
    9191msgstr "Toeslag wegwerpverpakking inschakelen"
    9292
    93 #: admin/settings.php:171
     93#: admin/settings.php:194
    9494msgid "If enabled, a fee is charged when the customer does not select reusable packaging."
    9595msgstr "Wanneer deze optie is ingeschakeld wordt er een toeslag gerekend aan klanten die niet voor een herbruikbare verpakking kiezen."
    9696
    97 #: admin/settings.php:173
     97#: admin/settings.php:196
    9898msgid "Fee in cents:"
    9999msgstr "Toeslag in aantal cent:"
    100100
    101 #: admin/settings.php:219
     101#: admin/settings.php:245
    102102msgid "Default selection:"
    103103msgstr "Standaardselectie:"
    104104
    105 #: admin/settings.php:220
     105#: admin/settings.php:246
    106106msgid "Customer chooses packaging"
    107107msgstr "Klant kiest tussen herbruikbaar of wegwerp"
    108108
    109 #: admin/order.php:128
    110 #: admin/settings.php:222
     109#: admin/order.php:185
     110#: admin/settings.php:248
    111111msgid "Reusable packaging"
    112112msgstr "Herbruikbare verzendverpakking"
    113113
    114 #: admin/order.php:138
    115 #: admin/settings.php:223
    116 #: checkout/checkout.php:264
     114#: admin/order.php:198
     115#: admin/settings.php:249
     116#: checkout/checkout.php:214
    117117msgid "Disposable packaging"
    118118msgstr "Wegwerpverpakking"
    119119
    120 #: admin/settings.php:250
     120#: admin/settings.php:279
    121121msgid "Will open in a new tab when the customer clicks the information button. Defaults to BOXO Return homepage."
    122122msgstr "Wordt geopend in een nieuw tabblad wanneer de klant op de informatieknop klikt. Standaard wordt de BOXO Return-homepagina geopend."
    123123
    124 #: admin/settings.php:275
     124#: admin/settings.php:307
    125125msgid "All products"
    126126msgstr "Alle producten"
    127127
    128 #: admin/settings.php:276
     128#: admin/settings.php:308
    129129msgid "All products except:"
    130130msgstr "Alle producten behalve:"
    131131
    132 #: admin/settings.php:277
     132#: admin/settings.php:309
    133133msgid "Only these products:"
    134134msgstr "Alleen specifieke producten:"
    135135
    136 #: admin/settings.php:221
     136#: admin/settings.php:247
    137137msgid "Always use reusable packaging when possible"
    138138msgstr "Gebruik altijd herbruikbaar indien mogelijk"
    139139
    140 #: admin/product-picker.php:49
     140#: admin/product-picker.php:48
    141141msgid "Product name"
    142142msgstr "Productnaam"
    143143
    144 #: admin/order.php:38
    145 #: admin/order.php:80
    146 #: admin/order.php:105
    147 #: checkout/checkout.php:195
    148 #: checkout/checkout.php:196
     144#: admin/order.php:49
     145#: admin/order.php:127
     146#: admin/order.php:159
     147#: checkout/checkout.php:76
     148#: checkout/checkout.php:77
    149149msgid "Packaging"
    150150msgstr "Verzendverpakking"
    151151
    152 #: admin/order.php:60
     152#: admin/order.php:94
    153153msgid "Reusable"
    154154msgstr "Herbruikbaar"
    155155
    156 #: admin/order.php:63
    157 #: checkout/checkout.php:220
     156#: admin/order.php:97
     157#: checkout/checkout.php:105
    158158msgid "Disposable"
    159159msgstr "Wegwerp"
    160160
    161 #: checkout/checkout.php:252
     161#: checkout/checkout.php:201
    162162msgid "Deposit"
    163163msgstr "Statiegeld"
    164164
    165 #: checkout/checkout.php:289
    166 msgid "Select a packaging option."
    167 msgstr "Selecteer een verzendverpakking."
    168 
    169 #: admin/settings.php:115
     165#: admin/settings.php:122
    170166msgid "Cart limit"
    171167msgstr "Productenlimiet"
    172168
    173 #: admin/settings.php:317
     169#: admin/settings.php:352
    174170msgid "Enable cart limit"
    175171msgstr "Activeer productenlimiet"
    176172
    177 #: admin/settings.php:318
     173#: admin/settings.php:353
    178174msgid "If enabled, reusable packaging will not be available when the cart exceeds the set amount of items."
    179175msgstr "Wanneer deze optie is ingeschakeld wordt er geen herbruikbare verpakking aangeboden voor orders met meer dan dit aantal items."
    180176
    181 #: admin/settings.php:320
     177#: admin/settings.php:355
    182178msgid "Max items:"
    183179msgstr "Max aantal items:"
    184180
    185 #: checkout/checkout.php:202
     181#: checkout/checkout.php:165
    186182msgid "Reusable (deposit)"
    187183msgstr "Herbruikbaar (statiegeld)"
    188184
    189 #: checkout/checkout.php:205
     185#: checkout/checkout.php:169
    190186msgid "Nice, there is a return point near you! Return the packaging at a return point to get your deposit (€%s) back right away. Click for more information."
    191187msgstr "Yes, er is een inleverpunt in je buurt! Lever de verpakking in bij een inleverpunt en ontvang direct je statiegeld (€%s) terug. Klik voor meer informatie."
  • boxo-return/tags/0.0.61/languages/boxo-return.pot

    r3317144 r3371141  
    33msgid ""
    44msgstr ""
    5 "Project-Id-Version: BOXO Return 0.0.53\n"
     5"Project-Id-Version: BOXO Return 0.0.61\n"
    66"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/boxo-return\n"
    77"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
     
    1010"Content-Type: text/plain; charset=UTF-8\n"
    1111"Content-Transfer-Encoding: 8bit\n"
    12 "POT-Creation-Date: 2025-06-12T14:12:16+00:00\n"
     12"POT-Creation-Date: 2025-10-01T13:42:31+00:00\n"
    1313"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    1414"X-Generator: WP-CLI 2.11.0\n"
     
    3535msgstr ""
    3636
    37 #: admin/admin.php:36
     37#: admin/admin.php:42
    3838msgid "Settings saved"
    3939msgstr ""
    4040
    41 #: admin/admin.php:48
     41#: admin/admin.php:54
    4242msgid "Save"
    4343msgstr ""
    4444
    45 #: admin/admin.php:58
    46 #: admin/settings.php:36
     45#: admin/admin.php:72
     46#: admin/settings.php:43
    4747msgid "Settings"
    4848msgstr ""
    4949
    50 #: admin/order.php:38
    51 #: admin/order.php:80
    52 #: admin/order.php:105
    53 #: checkout/checkout.php:195
    54 #: checkout/checkout.php:196
     50#: admin/order.php:49
     51#: admin/order.php:127
     52#: admin/order.php:159
     53#: checkout/checkout.php:76
     54#: checkout/checkout.php:77
    5555msgid "Packaging"
    5656msgstr ""
    5757
    58 #: admin/order.php:60
     58#: admin/order.php:94
    5959msgid "Reusable"
    6060msgstr ""
    6161
    62 #: admin/order.php:63
    63 #: checkout/checkout.php:220
     62#: admin/order.php:97
     63#: checkout/checkout.php:105
    6464msgid "Disposable"
    6565msgstr ""
    6666
    67 #: admin/order.php:128
    68 #: admin/settings.php:222
     67#: admin/order.php:185
     68#: admin/settings.php:248
    6969msgid "Reusable packaging"
    7070msgstr ""
    7171
    72 #: admin/order.php:138
    73 #: admin/settings.php:223
    74 #: checkout/checkout.php:264
     72#: admin/order.php:198
     73#: admin/settings.php:249
     74#: checkout/checkout.php:214
    7575msgid "Disposable packaging"
    7676msgstr ""
    7777
    78 #: admin/product-picker.php:48
     78#: admin/product-picker.php:47
    7979msgid "Add a product:"
    8080msgstr ""
    8181
    82 #: admin/product-picker.php:49
     82#: admin/product-picker.php:48
    8383msgid "Product name"
    8484msgstr ""
    8585
    86 #: admin/product-picker.php:51
     86#: admin/product-picker.php:50
    8787msgid "No products found."
    8888msgstr ""
    8989
    90 #: admin/product-picker.php:52
     90#: admin/product-picker.php:51
    9191msgid "Remove"
    9292msgstr ""
    9393
    94 #: admin/settings.php:43
     94#: admin/settings.php:50
    9595msgid "API Key"
    9696msgstr ""
    9797
    98 #: admin/settings.php:55
     98#: admin/settings.php:62
    9999msgid "Deposit amount in cents"
    100100msgstr ""
    101101
    102 #: admin/settings.php:67
     102#: admin/settings.php:74
    103103msgid "Disposable packaging fee"
    104104msgstr ""
    105105
    106 #: admin/settings.php:79
     106#: admin/settings.php:86
    107107msgid "Packaging selection"
    108108msgstr ""
    109109
    110 #: admin/settings.php:91
     110#: admin/settings.php:98
    111111msgid "URL to information page (optional)"
    112112msgstr ""
    113113
    114 #: admin/settings.php:103
     114#: admin/settings.php:110
    115115msgid "Allowed in reusable packaging"
    116116msgstr ""
    117117
    118 #: admin/settings.php:115
     118#: admin/settings.php:122
    119119msgid "Cart limit"
    120120msgstr ""
    121121
    122 #: admin/settings.php:144
     122#: admin/settings.php:161
    123123msgid "Enter the API key provided by BOXO Return."
    124124msgstr ""
    125125
    126 #: admin/settings.php:170
     126#: admin/settings.php:193
    127127msgid "Enable disposable packaging fee"
    128128msgstr ""
    129129
    130 #: admin/settings.php:171
     130#: admin/settings.php:194
    131131msgid "If enabled, a fee is charged when the customer does not select reusable packaging."
    132132msgstr ""
    133133
    134 #: admin/settings.php:173
     134#: admin/settings.php:196
    135135msgid "Fee in cents:"
    136136msgstr ""
    137137
    138 #: admin/settings.php:219
     138#: admin/settings.php:245
    139139msgid "Default selection:"
    140140msgstr ""
    141141
    142 #: admin/settings.php:220
     142#: admin/settings.php:246
    143143msgid "Customer chooses packaging"
    144144msgstr ""
    145145
    146 #: admin/settings.php:221
     146#: admin/settings.php:247
    147147msgid "Always use reusable packaging when possible"
    148148msgstr ""
    149149
    150 #: admin/settings.php:250
     150#: admin/settings.php:279
    151151msgid "Will open in a new tab when the customer clicks the information button. Defaults to BOXO Return homepage."
    152152msgstr ""
    153153
    154 #: admin/settings.php:275
     154#: admin/settings.php:307
    155155msgid "All products"
    156156msgstr ""
    157157
    158 #: admin/settings.php:276
     158#: admin/settings.php:308
    159159msgid "All products except:"
    160160msgstr ""
    161161
    162 #: admin/settings.php:277
     162#: admin/settings.php:309
    163163msgid "Only these products:"
    164164msgstr ""
    165165
    166 #: admin/settings.php:317
     166#: admin/settings.php:352
    167167msgid "Enable cart limit"
    168168msgstr ""
    169169
    170 #: admin/settings.php:318
     170#: admin/settings.php:353
    171171msgid "If enabled, reusable packaging will not be available when the cart exceeds the set amount of items."
    172172msgstr ""
    173173
    174 #: admin/settings.php:320
     174#: admin/settings.php:355
    175175msgid "Max items:"
    176176msgstr ""
    177177
    178 #: checkout/checkout.php:202
     178#: checkout/checkout.php:165
    179179msgid "Reusable (deposit)"
    180180msgstr ""
    181181
    182 #: checkout/checkout.php:205
     182#: checkout/checkout.php:169
    183183msgid "Nice, there is a return point near you! Return the packaging at a return point to get your deposit (€%s) back right away. Click for more information."
    184184msgstr ""
    185185
    186 #: checkout/checkout.php:252
     186#: checkout/checkout.php:201
    187187msgid "Deposit"
    188188msgstr ""
    189 
    190 #: checkout/checkout.php:289
    191 msgid "Select a packaging option."
    192 msgstr ""
  • boxo-return/tags/0.0.61/readme.txt

    r3349705 r3371141  
    22Contributors: boxodev
    33Tags: shipping, packaging
    4 Requires at least: 4.7
     4Requires at least: 6.5
    55Tested up to: 6.5.3
    6 Stable tag: 0.0.60
    7 Requires PHP: 7.0
     6Stable tag: 0.0.61
     7Requires PHP: 7.1
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    2727== Changelog ==
    2828
     29= 0.0.61 =
     30Improve checkout performance and theme support.
     31
    2932= 0.0.60 =
    3033Remove packaging selection requirement in checkout, which was made redundant by default selection.
     34
     35= 0.0.58 =
     36Improve checkout performance.
    3137
    3238= 0.0.57 =
  • boxo-return/trunk/admin/admin.php

    r3276747 r3371141  
    1313if (!class_exists('Boxo_Admin')) {
    1414    class Boxo_Admin {
     15        /**
     16         * @return void
     17         */
    1518        public static function add_menu_item() {
    1619            if (!current_user_can('manage_woocommerce')) {
     
    2831        }
    2932
     33        /**
     34         * @return void
     35         */
    3036        public static function add_options_page() {
    3137            if (!current_user_can('manage_woocommerce')) {
     
    5359        }
    5460
     61        /**
     62         * @param mixed $links
     63         * @return mixed
     64         */
    5565        public static function add_settings_link($links) {
     66            // If filter arg type is incorrect due to external code, just get out of the way and return the arg.
     67            if (!is_array($links)) {
     68                return $links;
     69            }
    5670            $url = admin_url('admin.php?page=boxo-return');
    5771            $action_links = array(
  • boxo-return/trunk/admin/order.php

    r3243704 r3371141  
    3131if (!class_exists('Boxo_Order')) {
    3232    class Boxo_Order {
     33        /**
     34         * @return void
     35         */
    3336        public static function init() {
    3437            wp_enqueue_style('boxo_order', plugins_url('order.css', __FILE__));
    3538        }
    3639
     40        /**
     41         * @param mixed $columns
     42         * @return mixed
     43         */
    3744        public static function order_list_add_packaging_column($columns) {
     45            // If filter arg type is incorrect due to external code, just get out of the way and return the arg.
     46            if (!is_array($columns)) {
     47                return $columns;
     48            }
    3849            $columns['boxo_packaging'] = esc_html__('Packaging', 'boxo-return');
    3950            return $columns;
    4051        }
    4152
     53        /**
     54         * @param mixed $column_name
     55         * @param mixed $order
     56         * @return void
     57         */
    4258        public static function order_list_populate_column_hpos($column_name, $order) {
    43             if ($column_name === 'boxo_packaging') {
    44                 self::populate_column($order);
    45             }
    46         }
    47 
     59            if ($column_name === 'boxo_packaging' && $order instanceof WC_Order) {
     60                $packaging = $order->get_meta('boxo_packaging', true);
     61                if (!is_string($packaging)) {
     62                    return;
     63                }
     64                self::populate_column($packaging);
     65            }
     66        }
     67
     68        /**
     69         * @param mixed $column_name
     70         * @param mixed $order_id
     71         * @return void
     72         */
    4873        public static function order_list_populate_column_cpt($column_name, $order_id) {
    4974            $order = wc_get_order($order_id);
     75            if (is_bool($order)) {
     76                return;
     77            }
    5078            if ($column_name === 'boxo_packaging') {
    51                 self::populate_column($order);
    52             }
    53         }
    54 
    55         public static function populate_column($order) {
    56             $packaging = $order->get_meta('boxo_packaging');
    57             $cell = "";
     79                $packaging = $order->get_meta('boxo_packaging', true);
     80                if (!is_string($packaging)) {
     81                    return;
     82                }
     83                self::populate_column($packaging);
     84            }
     85        }
     86
     87        /**
     88         * @param string $packaging
     89         * @return void
     90         */
     91        public static function populate_column($packaging) {
    5892            switch ($packaging) {
    5993                case Boxo_Constants::PACKAGING_REUSABLE:
    60                     $cell = esc_html__('Reusable', 'boxo-return');
    61                     break;
     94                    echo esc_html__('Reusable', 'boxo-return');
     95                    return;
    6296                case Boxo_Constants::PACKAGING_DISPOSABLE:
    63                     $cell = esc_html__('Disposable', 'boxo-return');
    64                     break;
     97                    echo esc_html__('Disposable', 'boxo-return');
     98                    return;
    6599                default:
    66100                    // If the order was placed when BOXO Return was not active, leave packaging field empty.
    67                     break;
    68             }
    69             echo $cell;
    70         }
    71 
     101                    return;
     102            }
     103        }
     104
     105        /**
     106         * @param mixed $data
     107         * @param mixed $order
     108         * @return mixed
     109         */
    72110        public static function order_preview_add_packaging_data($data, $order) {
    73             if ($packaging = $order->get_meta('boxo_packaging')) {
    74                 $data['boxo_packaging'] = $packaging;
    75             }
     111            // If filter arg type is incorrect due to external code, just get out of the way and return the arg.
     112            if (!is_array($data) || !($order instanceof WC_Order)) {
     113                return $data;
     114            }
     115            $packaging = $order->get_meta('boxo_packaging', true);
     116            if (!is_string($packaging)) {
     117                return $data;
     118            }
     119            $data['boxo_packaging'] = $packaging;
    76120            return $data;
    77121        }
    78122
     123        /**
     124         * @return void
     125         */
    79126        public static function order_preview_add_packaging() {
    80127            $title_safe = esc_html__('Packaging', 'boxo-return');
     
    101148        }
    102149
     150        /**
     151         * @param mixed $order
     152         * @return void
     153         */
    103154        public static function order_detail_add_packaging($order) {
     155            if (!($order instanceof WC_Order)) {
     156                return;
     157            }
    104158            $packaging = $order->get_meta('boxo_packaging', true);
    105159            $title_safe = esc_html__('Packaging', 'boxo-return');
     
    124178        }
    125179
     180        /**
     181         * @return string
     182         */
    126183        private static function reusable_label() {
    127184            $heart_icon_safe = file_get_contents(WP_PLUGIN_DIR . '/boxo-return/icons/heart.svg');
     
    135192        }
    136193
     194        /**
     195         * @return string
     196         */
    137197        private static function disposable_label() {
    138198            return esc_html__('Disposable packaging', 'boxo-return');
  • boxo-return/trunk/admin/product-picker.php

    r3276747 r3371141  
    2323            foreach ($ids as $id) {
    2424                $product = wc_get_product($id);
    25                 if (!$product) {
     25                if (!($product instanceof WC_Product)) {
    2626                    continue;
    2727                }
    28 
    29                 $image = wp_get_attachment_image_src(
    30                     $product->get_image_id(),
     28                $image_data = wp_get_attachment_image_src(
     29                    intval($product->get_image_id()),
    3130                    [30, 30],
    3231                );
     
    3433                    'id' => $id,
    3534                    'title' => $product->get_title(),
    36                     'image' => $image ? $image[0] : null,
     35                    'image' => is_array($image_data) ? $image_data[0] : null,
    3736                ]);
    3837            }
  • boxo-return/trunk/admin/settings.php

    r3310810 r3371141  
    1212
    1313// When saving, make sure checkbox input is saved as boolean.
    14 add_filter('pre_update_option_boxo_options', function ($val) {
    15     $val['disposable_fee_enabled'] = isset($val['disposable_fee_enabled']) ? true : false;
    16     $val['cart_limit_enabled'] = isset($val['cart_limit_enabled']) ? true : false;
    17     return $val;
     14add_filter('pre_update_option_boxo_options', function ($vals) {
     15    // If filter arg type is incorrect due to external code, just get out of the way and return the arg.
     16    if (!is_array($vals)) {
     17        return $vals;
     18    }
     19    $vals['disposable_fee_enabled'] = isset($vals['disposable_fee_enabled']) ? true : false;
     20    $vals['cart_limit_enabled'] = isset($vals['cart_limit_enabled']) ? true : false;
     21    return $vals;
    1822}, 10, 1);
    1923
    2024if (!class_exists('Boxo_Settings')) {
    2125    class Boxo_Settings {
     26        /**
     27         * @return void
     28         */
    2229        public static function init() {
    2330            if (!current_user_can('manage_woocommerce')) {
     
    127134        }
    128135
     136        /**
     137         * @return void
     138         */
    129139        public static function add_section_header() {
    130140            // Empty but required.
    131141        }
    132142
     143        /**
     144         * @param mixed $hook_suffix
     145         * @return void
     146         */
    133147        public static function enqueue_scripts($hook_suffix) {
    134             // Only load on boxo-return admin page.
    135             if (!str_contains($hook_suffix, 'boxo-return')) {
     148            // Only load on admin page for boxo-return.
     149            if (!is_string($hook_suffix) || strpos($hook_suffix, 'boxo-return') === false) {
    136150                return;
    137151            }
    138             wp_enqueue_script('alpine', 'https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js', null, '3.14.5', true);
     152            wp_enqueue_script('alpine', 'https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js', [], '3.14.5', true);
    139153            wp_enqueue_script('boxo_product_picker', plugins_url('product-picker.js', __FILE__));
    140154        }
    141155
     156        /**
     157         * @return void
     158         */
    142159        public static function add_api_key_field() {
    143160            $value_safe = esc_attr(Boxo_Options::api_key());
     
    157174        }
    158175
     176        /**
     177         * @return void
     178         */
    159179        public static function add_deposit_cents_field() {
    160             $value_safe = esc_attr(Boxo_Options::deposit_cents());
     180            $value_safe = esc_attr(strval(Boxo_Options::deposit_cents()));
    161181            echo <<<HTML
    162182            <input id="boxo_deposit-cents-input" name="boxo_options[deposit_cents]" type="number" min="0" max="10000" value="$value_safe">
     
    164184        }
    165185
     186        /**
     187         * @return void
     188         */
    166189        public static function add_disposable_fee_enabled_field() {
    167190            $enabled = Boxo_Options::disposable_fee_enabled();
     
    170193            $label_safe = esc_html__('Enable disposable packaging fee', 'boxo-return');
    171194            $description_safe = esc_html__('If enabled, a fee is charged when the customer does not select reusable packaging.', 'boxo-return');
    172             $fee_safe = esc_attr(Boxo_Options::disposable_fee_cents());
     195            $fee_safe = esc_attr(strval(Boxo_Options::disposable_fee_cents()));
    173196            $cents_label_safe = esc_html__('Fee in cents:', 'boxo-return');
    174197            echo <<<HTML
     
    203226        }
    204227
     228        /**
     229         * @return void
     230         */
    205231        public static function add_selection_mode_field() {
    206232            $selection_mode = Boxo_Options::selection_mode();
     
    212238            $disposable_safe = Boxo_Options::DEFAULT_PACKAGING_DISPOSABLE;
    213239
    214             $choice_selected_attb_safe = $selection_mode == Boxo_Options::SELECTION_MODE_CHOICE ? 'selected' : '';
    215             $force_reusable_selected_attb_safe = $selection_mode == Boxo_Options::SELECTION_MODE_FORCE_REUSABLE ? 'selected' : '';
    216             $reusable_selected_attb_safe = $default_packaging == Boxo_Options::DEFAULT_PACKAGING_REUSABLE ? 'selected' : '';
    217             $disposable_selected_attb_safe = $default_packaging == Boxo_Options::DEFAULT_PACKAGING_DISPOSABLE ? 'selected' : '';
     240            $choice_selected_attb_safe = $selection_mode === Boxo_Options::SELECTION_MODE_CHOICE ? 'selected' : '';
     241            $force_reusable_selected_attb_safe = $selection_mode === Boxo_Options::SELECTION_MODE_FORCE_REUSABLE ? 'selected' : '';
     242            $reusable_selected_attb_safe = $default_packaging === Boxo_Options::DEFAULT_PACKAGING_REUSABLE ? 'selected' : '';
     243            $disposable_selected_attb_safe = $default_packaging === Boxo_Options::DEFAULT_PACKAGING_DISPOSABLE ? 'selected' : '';
    218244
    219245            $default_packaging_label_safe = esc_html__('Default selection:', 'boxo-return');
     
    246272        }
    247273
     274        /**
     275         * @return void
     276         */
    248277        public static function add_info_url_field() {
    249278            $value_safe = esc_attr(Boxo_Options::info_url());
     
    262291        }
    263292
     293        /**
     294         * @return void
     295         */
    264296        public static function add_product_allow_mode_field() {
    265297            $allow_mode = Boxo_Options::product_allow_mode();
     
    269301            $included_only_safe = Boxo_Options::PRODUCT_ALLOW_MODE_INCLUDED_ONLY;
    270302
    271             $all_selected_attb_safe = $allow_mode == Boxo_Options::PRODUCT_ALLOW_MODE_ALL ? 'selected' : '';
    272             $all_except_excluded_selected_attb_safe = $allow_mode == Boxo_Options::PRODUCT_ALLOW_MODE_ALL_EXCEPT_EXCLUDED ? 'selected' : '';
    273             $included_only_selected_attb_safe = $allow_mode == Boxo_Options::PRODUCT_ALLOW_MODE_INCLUDED_ONLY ? 'selected' : '';
     303            $all_selected_attb_safe = $allow_mode === Boxo_Options::PRODUCT_ALLOW_MODE_ALL ? 'selected' : '';
     304            $all_except_excluded_selected_attb_safe = $allow_mode === Boxo_Options::PRODUCT_ALLOW_MODE_ALL_EXCEPT_EXCLUDED ? 'selected' : '';
     305            $included_only_selected_attb_safe = $allow_mode === Boxo_Options::PRODUCT_ALLOW_MODE_INCLUDED_ONLY ? 'selected' : '';
    274306
    275307            $all_label_safe = esc_html__('All products', 'boxo-return');
     
    311343        }
    312344
     345        /**
     346         * @return void
     347         */
    313348        public static function add_cart_limit_enabled_field() {
    314349            $enabled = Boxo_Options::cart_limit_enabled();
     
    317352            $label_safe = esc_html__('Enable cart limit', 'boxo-return');
    318353            $description_safe = esc_html__('If enabled, reusable packaging will not be available when the cart exceeds the set amount of items.', 'boxo-return');
    319             $limit_safe = esc_attr(Boxo_Options::cart_limit());
     354            $limit_safe = esc_attr(strval(Boxo_Options::cart_limit()));
    320355            $limit_label_safe = esc_html__('Max items:', 'boxo-return');
    321356            echo <<<HTML
  • boxo-return/trunk/boxo-return.php

    r3349705 r3371141  
    22/*
    33* Plugin Name: BOXO Return
    4 * Version: 0.0.60
     4* Version: 0.0.61
     5* Requires at least: 6.5
     6* Requires PHP: 7.1
    57* Description: Allows customers to select reusable packaging during checkout.
    68* Author: BOXO
     
    2022}
    2123
    22 const BOXO_RETURN_PLUGIN_VERSION = "0.0.60";
     24const BOXO_RETURN_PLUGIN_VERSION = "0.0.61";
    2325
    2426include plugin_dir_path(__FILE__) . 'includes/constants.php';
     27include plugin_dir_path(__FILE__) . 'includes/setup.php';
    2528include plugin_dir_path(__FILE__) . 'includes/options.php';
    26 include plugin_dir_path(__FILE__) . 'includes/api.php';
     29include plugin_dir_path(__FILE__) . 'includes/postal-code.php';
     30include plugin_dir_path(__FILE__) . 'includes/available.php';
    2731include plugin_dir_path(__FILE__) . 'includes/ajax.php';
    28 include plugin_dir_path(__FILE__) . 'includes/logger.php';
    2932if (is_admin()) {
    3033    include plugin_dir_path(__FILE__) . 'admin/admin.php';
     
    3437}
    3538include plugin_dir_path(__FILE__) . 'checkout/checkout.php';
     39
     40register_activation_hook(__FILE__, 'Boxo_Setup::handle_activation');
     41register_deactivation_hook(__FILE__, 'Boxo_Setup::handle_deactivation');
    3642
    3743// BOXO Return plugin is incompatible with Checkout Blocks.
     
    4450add_action('init', 'boxo_load_textdomain');
    4551
     52/**
     53 * @return void
     54 */
    4655function boxo_load_textdomain() {
    4756    load_plugin_textdomain('boxo-return', false, dirname(plugin_basename(__FILE__)) . '/languages');
  • boxo-return/trunk/checkout/boxo-checkout.js

    r3317144 r3371141  
    1 import {parsePostalCode} from '../utils/utils.js'
    2 
    3 const CHECK_AVAILABLE_TIMEOUT_MS = 5000
    4 
    51if (document.readyState !== 'loading') {
    62  init()
     
    106
    117async function init() {
    12   const pluginData = getPluginData()
    13   console.info('[BOXO Return] ' + pluginData.boxoVersion)
    14 
    158  // Must use jQuery for event handlers because propagation of certain events appears to be blocked.
    169  jQuery(function ($) {
     10    // AJAX update checkout (incl. total price) on change in relevant data or packaging selection.
    1711    $(document.body).on(
    1812      'change',
    19       '#billing_country, #billing_postcode, #shipping_country, #shipping_postcode, #ship-to-different-address-checkbox',
     13      '#billing_country, #billing_postcode, #shipping_country, #shipping_postcode, #ship-to-different-address-checkbox, [name=boxo_packaging]',
    2014      async () => {
    21         await handleChange()
    2215        document.body.dispatchEvent(new Event('update_checkout'))
    2316      }
    2417    )
    25 
    26     // AJAX update checkout (incl. total price) on packaging change.
    27     $(document.body).on('change', '[name=boxo_packaging]', (e) => {
    28       document.body.dispatchEvent(new Event('update_checkout'))
    29     })
    3018  })
    31 
    32   // Check Boxo availability on initial load, in case postal code was pre-filled.
    33   await handleChange()
    34   document.body.dispatchEvent(new Event('update_checkout'))
    3519
    3620  console.info('[BOXO Return] Ready')
    3721}
    38 
    39 /**
    40  * Data added to the page by the plugin.
    41  * @typedef {Object} PluginData
    42  * @property {string} boxoVersion - The version of the Boxo Return plugin.
    43  * @property {string} boxoAvailableUrl - The URL of the endpoint where Boxo availability can be checked.
    44  * @property {string} cartProducts - A comma-separated list of IDs of the products currently in cart.
    45  * @property {string} cartItemCount - The amount of items currently in cart.
    46  * @property {string} shopCountry - The base country of the shop.
    47  */
    48 
    49 function getPluginData() {
    50   // PluginData is assumed to have been added to the window object.
    51   /** @type {Window & { BoxoReturn?: PluginData }} */
    52   const win = window
    53   const pluginData = win.BoxoReturn
    54   if (!pluginData) {
    55     throw new Error('[BOXO Return] Could not get plugin data')
    56   }
    57   return pluginData
    58 }
    59 
    60 /**
    61  * Check whether Boxo can be used and show or hide the packaging input accordingly.
    62  */
    63 async function handleChange() {
    64   const pluginData = getPluginData()
    65 
    66   // If shipping address is different from billing address, use shipping address.
    67   // Otherwise, use billing address.
    68   const shippingAddressCheckbox = document.getElementById('ship-to-different-address-checkbox')
    69   const hasShippingAddress =
    70     shippingAddressCheckbox instanceof HTMLInputElement && shippingAddressCheckbox.checked
    71 
    72   const countryInput = hasShippingAddress
    73     ? document.getElementById('shipping_country')
    74     : document.getElementById('billing_country')
    75 
    76   // Shipping/billing countries can come from a <select> (multiple countries), a hidden <input> (single country),
    77   // or can be removed from the page entirely, in which case the shop country is used.
    78   const country =
    79     countryInput instanceof HTMLSelectElement || countryInput instanceof HTMLInputElement
    80       ? countryInput.value
    81       : pluginData.shopCountry
    82   if (country !== 'NL') {
    83     setBoxoUnavailable()
    84     return
    85   }
    86 
    87   const postalCodeInput = hasShippingAddress
    88     ? document.getElementById('shipping_postcode')
    89     : document.getElementById('billing_postcode')
    90   if (!(postalCodeInput instanceof HTMLInputElement)) {
    91     console.error('[BOXO Return] Could not get postal code input')
    92     setBoxoUnavailable()
    93     return
    94   }
    95 
    96   const postalCode = parsePostalCode(postalCodeInput.value)
    97   if (!postalCode) {
    98     setBoxoUnavailable()
    99     return
    100   }
    101 
    102   if (await checkBoxoAvailable(postalCode)) {
    103     setBoxoAvailable()
    104     return
    105   }
    106   setBoxoUnavailable()
    107 }
    108 
    109 /**
    110  * Check whether Boxo is available for the current cart contents and a postal code.
    111  * @param {string} postalCode
    112  */
    113 async function checkBoxoAvailable(postalCode) {
    114   try {
    115     const pluginData = getPluginData()
    116     const url = new URL(pluginData.boxoAvailableUrl)
    117     url.searchParams.set('postal_code', postalCode)
    118     url.searchParams.set('products', pluginData.cartProducts)
    119     url.searchParams.set('item_count', pluginData.cartItemCount)
    120 
    121     const ac = new AbortController()
    122     setTimeout(() => ac.abort('Timeout'), CHECK_AVAILABLE_TIMEOUT_MS)
    123     const res = await fetch(url.toString(), {signal: ac.signal})
    124     if (!res.ok) {
    125       const body = await res.json()
    126       console.error(`[BOXO Return] ${res.status} ${body.error}`)
    127       return false
    128     }
    129 
    130     const {available, reason} = await res.json()
    131     if (typeof available !== 'boolean') {
    132       console.error('[BOXO Return] Unexpected response')
    133       return false
    134     }
    135 
    136     if (!available) {
    137       console.info('[BOXO Return] ' + reason)
    138       return false
    139     }
    140 
    141     console.info('[BOXO Return] Available')
    142     return true
    143   } catch (err) {
    144     console.error('[BOXO Return]', err)
    145     return false
    146   }
    147 }
    148 
    149 function setBoxoAvailable() {
    150   const availableInput = document.querySelector('input[name=boxo_available]')
    151   if (!(availableInput instanceof HTMLInputElement)) {
    152     throw new Error('[BOXO Return] Could not get available input')
    153   }
    154   availableInput.value = 'true'
    155 }
    156 
    157 function setBoxoUnavailable() {
    158   const availableInput = document.querySelector('input[name=boxo_available]')
    159   if (!(availableInput instanceof HTMLInputElement)) {
    160     throw new Error('[BOXO Return] Could not get available input')
    161   }
    162   availableInput.value = 'false'
    163 }
  • boxo-return/trunk/checkout/checkout.php

    r3349705 r3371141  
    66
    77// Add Boxo features to checkout page after WooCommerce has loaded.
    8 add_action('woocommerce_init', 'Boxo_Checkout::init', 10);
     8add_action('woocommerce_init', 'Boxo_Checkout::init');
    99
    1010if (!class_exists('Boxo_Checkout')) {
     
    1212        private const DEFAULT_INFO_URL = 'https://www.boxo.nu';
    1313
     14        /**
     15         * @return void
     16         */
    1417        public static function init() {
    1518            // Add css and javascript.
    1619            add_action('wp_enqueue_scripts', 'Boxo_Checkout::enqueue_scripts');
    1720
    18             // Add plugin data: add to head to prevent possible interference from checkout page customizations.
    19             add_action('wp_head', 'Boxo_Checkout::add_plugin_data');
    20 
    21             // Add boxo available field which is updated by the client and read by the server in POST requests.
    22             add_action('woocommerce_checkout_before_customer_details', 'Boxo_Checkout::add_boxo_available_field');
     21            // Log plugin version to browser console.
     22            add_action('wp_head', 'Boxo_Checkout::log_plugin_version');
    2323
    2424            // Add packaging field.
     
    3232        }
    3333
     34        /**
     35         * @return void
     36         */
    3437        public static function enqueue_scripts() {
    3538            if (!Boxo_Checkout::should_load()) {
     
    4043        }
    4144
    42         public static function add_plugin_data() {
     45        /**
     46         * @return void
     47         */
     48        public static function log_plugin_version() {
    4349            $boxo_version_safe = BOXO_RETURN_PLUGIN_VERSION;
    44             $boxo_available_url_safe = esc_url(rest_url('boxo/available'));
    45             $cart_products_safe = esc_attr(implode(',', array_map(fn($item) => $item['product_id'], WC()->cart->cart_contents)));
    46             $cart_item_count_safe = esc_attr(WC()->cart->get_cart_contents_count());
    47             $shop_country_safe = esc_attr(WC()->countries->get_base_country());
    4850            echo <<<HTML
    4951            <script>
    50                 window.BoxoReturn = {
    51                     boxoVersion: "$boxo_version_safe",
    52                     boxoAvailableUrl: "$boxo_available_url_safe",
    53                     cartProducts: "$cart_products_safe",
    54                     cartItemCount: "$cart_item_count_safe",
    55                     shopCountry: "$shop_country_safe",
    56                 }
     52                console.info('[BOXO Return] $boxo_version_safe')
    5753            </script>
    5854            HTML;
    5955        }
    6056
    61         public static function add_boxo_available_field() {
    62             // Wrapping with div to prevent automatically added p tags.
    63             echo <<<HTML
    64             <div style="display: contents;">
    65                 <input name="boxo_available" type="hidden" value="false">
    66             </div>
    67             HTML;
    68         }
    69 
     57        /**
     58         * @return void
     59         */
    7060        public static function add_packaging_field() {
    7161            if (!Boxo_Checkout::should_load()) {
     
    7363            }
    7464
    75             // Only run on POST (particularly AJAX ?wc-ajax=update_order_review),
    76             // because boxo_available is not yet known on page load.
    77             $post_data = self::get_post_data();
    78             if (
    79                 !$post_data ||
    80                 !isset($post_data['boxo_available']) ||
    81                 $post_data['boxo_available'] !== 'true'
    82             ) {
    83                 return;
    84             }
     65            $result = self::boxo_available_for_request();
     66            if (is_string($result['message'])) {
     67                $message_safe = esc_js($result['message']);
     68                echo <<<HTML
     69                <script>console.info('[BOXO Return] $message_safe')</script>
     70                HTML;
     71            }
     72            if (!$result['available']) {
     73                return;
     74            }
     75
     76            $title_html_safe = esc_html__('Packaging', 'boxo-return');
     77            $title_attr_safe = esc_attr__('Packaging', 'boxo-return');
     78
     79            $inputs = Boxo_Options::selection_mode() === Boxo_Options::SELECTION_MODE_FORCE_REUSABLE ?
     80                self::inputs_force_reusable()
     81                : self::inputs_regular();
     82
     83            // Based on the HTML for standard shipping inputs in WooCommerce.
     84            echo <<<HTML
     85            <tr id="boxo_container" class="woocommerce-shipping-totals shipping">
     86                <th class="boxo_header">$title_html_safe</th>
     87                <td data-title="$title_attr_safe">
     88                    $inputs
     89                </td>
     90            </tr>
     91            HTML;
     92        }
     93
     94        /**
     95         * @return string
     96         */
     97        private static function inputs_regular() {
     98            // Persist current selection or use default.
     99            $selected = self::get_selected_packaging();
     100            $checked = is_string($selected) ? $selected : Boxo_Options::default_packaging();
    85101
    86102            $reusable_attr_safe = esc_attr(Boxo_Constants::PACKAGING_REUSABLE);
    87103            $disposable_attr_safe = esc_attr(Boxo_Constants::PACKAGING_DISPOSABLE);
    88             $title_html_safe = esc_html__('Packaging', 'boxo-return');
    89             $title_attr_safe = esc_attr__('Packaging', 'boxo-return');
    90104            $reusable_label_safe = self::reusable_label_safe();
    91             $disposable_label_safe = self::disposable_label_safe();
    92 
    93             $force_reusable_inputs = <<<HTML
    94             $reusable_label_safe
    95             <input name="boxo_packaging" value="$reusable_attr_safe" hidden />
    96             HTML;
    97 
    98             // Persist current selection or use default.
    99             $selected = isset($post_data['boxo_packaging']) ? $post_data['boxo_packaging'] : Boxo_Options::default_packaging();
    100             $reusable_checked_attr_safe = $selected === Boxo_Constants::PACKAGING_REUSABLE ? 'checked' : '';
    101             $disposable_checked_attr_safe = $selected === Boxo_Constants::PACKAGING_DISPOSABLE ? 'checked' : '';
    102 
    103             $regular_inputs = <<<HTML
     105            $disposable_safe = esc_html__('Disposable', 'boxo-return');
     106            $disposable_fee_formatted_safe = Boxo_Options::disposable_fee_enabled()
     107                ? esc_html('+ €' . number_format(Boxo_Options::disposable_fee_cents() / 100, 2, ',', '.'))
     108                : '';
     109            $trash_icon_safe = file_get_contents(WP_PLUGIN_DIR . '/boxo-return/icons/trash.svg');
     110            $reusable_checked_attr_safe = $checked === Boxo_Constants::PACKAGING_REUSABLE ? 'checked' : '';
     111            $disposable_checked_attr_safe = $checked === Boxo_Constants::PACKAGING_DISPOSABLE ? 'checked' : '';
     112
     113            return <<<HTML
    104114            <ul id="shipping_method" class="woocommerce-shipping-methods">
    105115                <li class="boxo_list-item-reusable">
     
    126136                    />
    127137                    <label for="boxo_packaging-disposable">
    128                         $disposable_label_safe
     138                        <span class="boxo_option-icon">$trash_icon_safe</span>
     139                        $disposable_safe
     140                        $disposable_fee_formatted_safe
    129141                    </label>
    130142                </li>
    131143            </ul>
    132144            HTML;
    133 
    134             $inputs = Boxo_Options::selection_mode() === Boxo_Options::SELECTION_MODE_FORCE_REUSABLE ?
    135                 $force_reusable_inputs
    136                 : $regular_inputs;
    137 
    138             // Based on the HTML for standard shipping inputs in WooCommerce.
    139             echo <<<HTML
    140             <tr id="boxo_container" class="woocommerce-shipping-totals shipping">
    141                 <th class="boxo_header">$title_html_safe</th>
    142                 <td data-title="$title_attr_safe">
    143                     $inputs
    144                 </td>
    145             </tr>
    146             HTML;
    147         }
    148 
     145        }
     146
     147        /**
     148         * @return string
     149         */
     150        private static function inputs_force_reusable() {
     151            $reusable_label_safe = self::reusable_label_safe();
     152            $reusable_attr_safe = esc_attr(Boxo_Constants::PACKAGING_REUSABLE);
     153
     154            return <<<HTML
     155            $reusable_label_safe
     156            <input name="boxo_packaging" value="$reusable_attr_safe" hidden />
     157            HTML;
     158        }
     159
     160        /**
     161         * @return string
     162         */
    149163        private static function reusable_label_safe() {
    150164            $heart_icon_safe = file_get_contents(WP_PLUGIN_DIR . '/boxo-return/icons/heart.svg');
    151165            $reusable_safe = esc_html__('Reusable (deposit)', 'boxo-return');
    152             $info_url_safe = esc_url(Boxo_Options::info_url() ?: self::DEFAULT_INFO_URL);
     166            $info_url_option = Boxo_Options::info_url();
     167            $info_url_safe = esc_url($info_url_option !== '' ? $info_url_option : self::DEFAULT_INFO_URL);
    153168            $info_icon_safe = file_get_contents(WP_PLUGIN_DIR . '/boxo-return/icons/info.svg');
    154169            $info_safe = sprintf(esc_html__('Nice, there is a return point near you! Return the packaging at a return point to get your deposit (€%s) back right away. Click for more information.', 'boxo-return'), number_format(Boxo_Options::deposit_cents() / 100, 2, ',', '.'));
     
    164179        }
    165180
    166         private static function disposable_label_safe() {
    167             $disposable_fee_formatted_safe = Boxo_Options::disposable_fee_enabled() ? esc_html('+ €' . number_format(Boxo_Options::disposable_fee_cents() / 100, 2, ',', '.')) : '';
    168             $trash_icon_safe = file_get_contents(WP_PLUGIN_DIR . '/boxo-return/icons/trash.svg');
    169             $disposable_safe = esc_html__('Disposable', 'boxo-return');
    170 
    171             return <<<HTML
    172             <span class="boxo_option-icon">$trash_icon_safe</span>
    173             $disposable_safe
    174             $disposable_fee_formatted_safe
    175             HTML;
    176         }
    177 
     181        /**
     182         * @return void
     183         */
    178184        public static function calculate_fees() {
    179185            if (!Boxo_Checkout::should_load()) {
     
    181187            }
    182188
    183             // Only run on POST (particularly AJAX ?wc-ajax=update_order_review and checkout submit),
    184             // because boxo_available is not yet known on page load.
    185189            // If Boxo is not available for postal code, do not add any fees.
    186             $post_data = self::get_post_data();
    187             if (
    188                 !$post_data ||
    189                 !isset($post_data['boxo_available']) ||
    190                 $post_data['boxo_available'] !== 'true'
    191             ) {
    192                 return;
    193             }
    194 
    195             $selected = isset($post_data['boxo_packaging'])
    196                 ? $post_data['boxo_packaging']
     190            if (!self::boxo_available_for_request()['available']) {
     191                return;
     192            }
     193
     194            $selected = self::get_selected_packaging();
     195            $packaging = is_string($selected)
     196                ? $selected
    197197                : Boxo_Options::default_packaging();
    198198
    199             if ($selected === Boxo_Constants::PACKAGING_REUSABLE) {
     199            if ($packaging === Boxo_Constants::PACKAGING_REUSABLE) {
    200200                WC()->cart->add_fee(
    201201                    __('Deposit', 'boxo-return'),
     
    219219        }
    220220
     221        /**
     222         * @param mixed $order_id
     223         * @return void
     224         */
    221225        public static function update_order_meta($order_id) {
    222226            if (!Boxo_Checkout::should_load()) {
     
    224228            }
    225229
    226             $post_data = self::get_post_data();
    227             $boxo_available = isset($post_data['boxo_available']) && $post_data['boxo_available'] === 'true';
    228             $packaging = $boxo_available ? sanitize_text_field($_POST['boxo_packaging']) : Boxo_Constants::PACKAGING_DISPOSABLE;
    229 
    230230            $order = wc_get_order($order_id);
     231            if (is_bool($order)) {
     232                return;
     233            }
     234
     235            $selected_packaging = self::get_selected_packaging();
     236            $packaging = self::boxo_available_for_request()['available'] && is_string($selected_packaging)
     237                ? sanitize_text_field($selected_packaging)
     238                : Boxo_Constants::PACKAGING_DISPOSABLE;
    231239            $order->update_meta_data('boxo_packaging', $packaging);
    232240            $order->save_meta_data();
    233241        }
    234242
     243        /**
     244         * @return bool
     245         */
    235246        public static function should_load() {
    236247            // Only load on the actual checkout page, not on the order received page.
     
    238249        }
    239250
    240         private static function get_post_data() {
    241             // AJAX requests.
    242             if (isset($_POST['post_data'])) {
     251        /**
     252         * @var array{available: bool, message: string|null}|null
     253         */
     254        private static $boxo_available_for_request_cached = null;
     255
     256        /**
     257         * Check the availability of BOXO for the current customer and cart.
     258         * Results are cached per incoming request.
     259         * @return array{available: bool, message: string|null}
     260         */
     261        private static function boxo_available_for_request() {
     262            if (is_array(self::$boxo_available_for_request_cached)) {
     263                return self::$boxo_available_for_request_cached;
     264            }
     265
     266            // WooCommerce handles choice between billing/shipping address internally:
     267            // if only billing address is supplied, this will be used as shipping address.
     268            $customer = WC()->customer;
     269            $postal_code = $customer->get_shipping_postcode();
     270            $country = $customer->get_shipping_country();
     271            $cart_contents_count = WC()->cart->get_cart_contents_count();
     272
     273            $cart_product_ids = [];
     274            foreach (WC()->cart->get_cart_contents() as $item) {
     275                if (!is_array($item) || !isset($item['product_id']) || !is_int($item['product_id'])) {
     276                    continue;
     277                }
     278                array_push($cart_product_ids, $item['product_id']);
     279            }
     280
     281            $res = Boxo_Available::boxo_available($postal_code, $country, $cart_contents_count, $cart_product_ids);
     282            self::$boxo_available_for_request_cached = $res;
     283            return $res;
     284        }
     285
     286        /**
     287         * @return string|null
     288         */
     289        private static function get_selected_packaging() {
     290            if (isset($_POST['post_data']) && is_string($_POST['post_data'])) {
     291                // AJAX requests.
    243292                parse_str($_POST['post_data'], $post_data);
    244                 return $post_data;
    245             }
    246             // Non-AJAX requests (eg. checkout submit).
    247             return $_POST;
     293            } else {
     294                // Non-AJAX requests (eg. checkout submit).
     295                $post_data = $_POST;
     296            }
     297            return isset($post_data['boxo_packaging']) && is_string($post_data['boxo_packaging'])
     298                ? $post_data['boxo_packaging']
     299                : null;
    248300        }
    249301    }
  • boxo-return/trunk/includes/ajax.php

    r3258642 r3371141  
    1313         * - keyword: the keyword to search for. If not supplied, all products (within the page limit) will be returned.
    1414         * - skip: a comma-separated list of product IDs that should be skipped.
     15         *
     16         * @return void
    1517         */
    1618        public static function handle_product_search() {
     
    1820            check_ajax_referer('boxo');
    1921
    20             $keyword = $_GET['keyword'];
    21             $skip = $_GET['skip'];
     22            $keyword = is_string($_GET['keyword']) ? $_GET['keyword'] : '';
     23            $skip = is_string($_GET['skip']) && $_GET['skip'] !== '' ? $_GET['skip'] : null;
    2224
    2325            global $wpdb;
    24             $skip_ids = explode(',', $skip);
     26            if (!($wpdb instanceof wpdb)) {
     27                wp_send_json_error('Internal server error', 500);
     28            }
     29
     30            $skip_ids = is_string($skip) ? explode(',', $skip) : [];
    2531            $skip_ids_safe = implode(",", array_map(fn($id) => esc_sql($id), $skip_ids));
     32
    2633            $query = "SELECT ID FROM {$wpdb->prefix}posts
    2734                        WHERE post_type = 'product'"
    28                 . ($skip_ids_safe ? " AND ID NOT IN ($skip_ids_safe)" : "") .
     35                . ($skip_ids_safe === '' ? '' : " AND ID NOT IN ($skip_ids_safe)") .
    2936                " AND post_status != 'trash'
    3037                        AND post_status != 'auto-draft'
     
    3340                        LIMIT %d";
    3441            $rows = $wpdb->get_results($wpdb->prepare(
     42                // @phpstan-ignore argument.type (Since WordPress 6.2.0, this can be solved nicely with %i, but this solution is not used out of backwards compatibility.)
    3543                $query,
    3644                // If keyword is empty, the first items are returned.
     
    3846                self::MAX_ITEMS
    3947            ), ARRAY_A);
     48            if (!is_array($rows)) {
     49                wp_send_json_error('Internal server error', 500);
     50            }
    4051
    41             $products = array_map(function ($row) {
     52            $products = [];
     53            foreach ($rows as $row) {
    4254                $product = wc_get_product($row['ID']);
    43                 return [
     55                if (!($product instanceof WC_Product)) {
     56                    continue;
     57                }
     58                $image_data = wp_get_attachment_image_src(
     59                    intval($product->get_image_id()),
     60                    [30, 30],
     61                );
     62                array_push($products, [
    4463                    'id' => $product->get_id(),
    4564                    'title' => $product->get_title(),
    46                     'image' => wp_get_attachment_image_src(
    47                         $product->get_image_id(),
    48                         [30, 30],
    49                     )[0],
    50                 ];
    51             }, $rows);
     65                    'image' => is_array($image_data) ? $image_data[0] : null,
     66                ]);
     67            }
    5268
    5369            wp_send_json($products);
    54             wp_die();
    5570        }
    5671    }
  • boxo-return/trunk/includes/options.php

    r3310810 r3371141  
    3030        ];
    3131
     32        /**
     33         * @param string $key
     34         * @return mixed
     35         */
    3236        private static function option($key) {
    3337            $options = get_option('boxo_options', self::DEFAULT_OPTIONS);
    34             if (isset($options[$key])) {
    35                 return $options[$key];
     38            if (!is_array($options) || !isset($options[$key])) {
     39                return self::DEFAULT_OPTIONS[$key];
    3640            }
    37             return self::DEFAULT_OPTIONS[$key];
     41            return $options[$key];
    3842        }
    3943
     
    4246         */
    4347        public static function api_key() {
    44             return self::option("api_key");
     48            $val = self::option('api_key');
     49            if (!is_string($val)) {
     50                return self::DEFAULT_OPTIONS['api_key'];
     51            }
     52            return $val;
    4553        }
    4654
     
    4957         */
    5058        public static function deposit_cents() {
    51             return self::option("deposit_cents");
     59            $val = self::option('deposit_cents');
     60            if (!is_string($val) || !is_numeric($val)) {
     61                return self::DEFAULT_OPTIONS['deposit_cents'];
     62            }
     63            return intval($val);
    5264        }
    5365
     
    5668         */
    5769        public static function disposable_fee_enabled() {
    58             return self::option("disposable_fee_enabled");
     70            $val = self::option('disposable_fee_enabled');
     71            if (!is_bool($val)) {
     72                return self::DEFAULT_OPTIONS['disposable_fee_enabled'];
     73            }
     74            return $val;
    5975        }
    6076
    6177        /**
    62          * @return bool Optional fee for disposable packaging in cents. The fee is taxable and the amount is considered to include any taxes.
     78         * @return int Optional fee for disposable packaging in cents. The fee is taxable and the amount is considered to include any taxes.
    6379         */
    6480        public static function disposable_fee_cents() {
    65             return self::option("disposable_fee_cents");
     81            $val = self::option('disposable_fee_cents');
     82            if (!is_string($val) || !is_numeric($val)) {
     83                return self::DEFAULT_OPTIONS['disposable_fee_cents'];
     84            }
     85            return intval($val);
    6686        }
    6787
     
    7393         */
    7494        public static function selection_mode() {
    75             return self::option("selection_mode");
     95            $val = self::option('selection_mode');
     96            if (!is_string($val)) {
     97                return self::DEFAULT_OPTIONS['selection_mode'];
     98            }
     99            return $val;
    76100        }
    77101
     
    83107         */
    84108        public static function default_packaging() {
    85             return self::option("default_packaging");
     109            $val = self::option('default_packaging');
     110            if (!is_string($val)) {
     111                return self::DEFAULT_OPTIONS['default_packaging'];
     112            }
     113            return $val;
    86114        }
    87115
     
    90118         */
    91119        public static function info_url() {
    92             return self::option("info_url");
     120            $val = self::option('info_url');
     121            if (!is_string($val)) {
     122                return self::DEFAULT_OPTIONS['info_url'];
     123            }
     124            return $val;
    93125        }
    94126
     
    101133         */
    102134        public static function product_allow_mode() {
    103             return self::option("product_allow_mode");
     135            $val = self::option('product_allow_mode');
     136            if (!is_string($val)) {
     137                return self::DEFAULT_OPTIONS['product_allow_mode'];
     138            }
     139            return $val;
    104140        }
    105141
     
    108144         */
    109145        public static function excluded_product_ids() {
    110             return self::option("excluded_product_ids");
     146            $val = self::option('excluded_product_ids');
     147            if (!is_string($val)) {
     148                return self::DEFAULT_OPTIONS['excluded_product_ids'];
     149            }
     150            return $val;
    111151        }
    112152
     
    115155         */
    116156        public static function included_product_ids() {
    117             return self::option("included_product_ids");
     157            $val = self::option('included_product_ids');
     158            if (!is_string($val)) {
     159                return self::DEFAULT_OPTIONS['included_product_ids'];
     160            }
     161            return $val;
    118162        }
    119163
     
    123167         */
    124168        public static function cart_limit_enabled() {
    125             return self::option("cart_limit_enabled");
     169            $val = self::option('cart_limit_enabled');
     170            if (!is_bool($val)) {
     171                return self::DEFAULT_OPTIONS['cart_limit_enabled'];
     172            }
     173            return $val;
    126174        }
    127175
     
    131179         */
    132180        public static function cart_limit() {
    133             return self::option("cart_limit");
     181            $val = self::option('cart_limit');
     182            if (!is_string($val) || !is_numeric($val)) {
     183                return self::DEFAULT_OPTIONS['cart_limit'];
     184            }
     185            return intval($val);
    134186        }
    135187    }
  • boxo-return/trunk/languages/boxo-return-nl.po

    r3317144 r3371141  
    3434msgstr "https://www.boxo.nu"
    3535
    36 #: admin/admin.php:58
    37 #: admin/settings.php:36
     36#: admin/admin.php:72
     37#: admin/settings.php:43
    3838msgid "Settings"
    3939msgstr "Instellingen"
    4040
    41 #: admin/settings.php:43
     41#: admin/settings.php:50
    4242msgid "API Key"
    4343msgstr "API-key"
    4444
    45 #: admin/settings.php:55
     45#: admin/settings.php:62
    4646msgid "Deposit amount in cents"
    4747msgstr "Statiegeld in aantal cent"
    4848
    49 #: admin/settings.php:67
     49#: admin/settings.php:74
    5050msgid "Disposable packaging fee"
    5151msgstr "Toeslag wegwerpverpakking"
    5252
    53 #: admin/settings.php:79
     53#: admin/settings.php:86
    5454msgid "Packaging selection"
    5555msgstr "Verpakking selecteren"
    5656
    57 #: admin/settings.php:91
     57#: admin/settings.php:98
    5858msgid "URL to information page (optional)"
    5959msgstr "Informatiepagina (optioneel)"
    6060
    61 #: admin/settings.php:103
     61#: admin/settings.php:110
    6262msgid "Allowed in reusable packaging"
    6363msgstr "Beschikbaar in herbruikbare verpakking"
    6464
    65 #: admin/settings.php:144
     65#: admin/settings.php:161
    6666msgid "Enter the API key provided by BOXO Return."
    6767msgstr "Voer de API-key in die je hebt ontvangen van BOXO Return."
    6868
    69 #: admin/admin.php:36
     69#: admin/admin.php:42
    7070msgid "Settings saved"
    7171msgstr "Instellingen bijgewerkt"
    7272
    73 #: admin/admin.php:48
     73#: admin/admin.php:54
    7474msgid "Save"
    7575msgstr "Opslaan"
    7676
    77 #: admin/product-picker.php:48
     77#: admin/product-picker.php:47
    7878msgid "Add a product:"
    7979msgstr "Product toevoegen:"
    8080
    81 #: admin/product-picker.php:51
     81#: admin/product-picker.php:50
    8282msgid "No products found."
    8383msgstr "Geen producten gevonden."
    8484
    85 #: admin/product-picker.php:52
     85#: admin/product-picker.php:51
    8686msgid "Remove"
    8787msgstr "Verwijderen"
    8888
    89 #: admin/settings.php:170
     89#: admin/settings.php:193
    9090msgid "Enable disposable packaging fee"
    9191msgstr "Toeslag wegwerpverpakking inschakelen"
    9292
    93 #: admin/settings.php:171
     93#: admin/settings.php:194
    9494msgid "If enabled, a fee is charged when the customer does not select reusable packaging."
    9595msgstr "Wanneer deze optie is ingeschakeld wordt er een toeslag gerekend aan klanten die niet voor een herbruikbare verpakking kiezen."
    9696
    97 #: admin/settings.php:173
     97#: admin/settings.php:196
    9898msgid "Fee in cents:"
    9999msgstr "Toeslag in aantal cent:"
    100100
    101 #: admin/settings.php:219
     101#: admin/settings.php:245
    102102msgid "Default selection:"
    103103msgstr "Standaardselectie:"
    104104
    105 #: admin/settings.php:220
     105#: admin/settings.php:246
    106106msgid "Customer chooses packaging"
    107107msgstr "Klant kiest tussen herbruikbaar of wegwerp"
    108108
    109 #: admin/order.php:128
    110 #: admin/settings.php:222
     109#: admin/order.php:185
     110#: admin/settings.php:248
    111111msgid "Reusable packaging"
    112112msgstr "Herbruikbare verzendverpakking"
    113113
    114 #: admin/order.php:138
    115 #: admin/settings.php:223
    116 #: checkout/checkout.php:264
     114#: admin/order.php:198
     115#: admin/settings.php:249
     116#: checkout/checkout.php:214
    117117msgid "Disposable packaging"
    118118msgstr "Wegwerpverpakking"
    119119
    120 #: admin/settings.php:250
     120#: admin/settings.php:279
    121121msgid "Will open in a new tab when the customer clicks the information button. Defaults to BOXO Return homepage."
    122122msgstr "Wordt geopend in een nieuw tabblad wanneer de klant op de informatieknop klikt. Standaard wordt de BOXO Return-homepagina geopend."
    123123
    124 #: admin/settings.php:275
     124#: admin/settings.php:307
    125125msgid "All products"
    126126msgstr "Alle producten"
    127127
    128 #: admin/settings.php:276
     128#: admin/settings.php:308
    129129msgid "All products except:"
    130130msgstr "Alle producten behalve:"
    131131
    132 #: admin/settings.php:277
     132#: admin/settings.php:309
    133133msgid "Only these products:"
    134134msgstr "Alleen specifieke producten:"
    135135
    136 #: admin/settings.php:221
     136#: admin/settings.php:247
    137137msgid "Always use reusable packaging when possible"
    138138msgstr "Gebruik altijd herbruikbaar indien mogelijk"
    139139
    140 #: admin/product-picker.php:49
     140#: admin/product-picker.php:48
    141141msgid "Product name"
    142142msgstr "Productnaam"
    143143
    144 #: admin/order.php:38
    145 #: admin/order.php:80
    146 #: admin/order.php:105
    147 #: checkout/checkout.php:195
    148 #: checkout/checkout.php:196
     144#: admin/order.php:49
     145#: admin/order.php:127
     146#: admin/order.php:159
     147#: checkout/checkout.php:76
     148#: checkout/checkout.php:77
    149149msgid "Packaging"
    150150msgstr "Verzendverpakking"
    151151
    152 #: admin/order.php:60
     152#: admin/order.php:94
    153153msgid "Reusable"
    154154msgstr "Herbruikbaar"
    155155
    156 #: admin/order.php:63
    157 #: checkout/checkout.php:220
     156#: admin/order.php:97
     157#: checkout/checkout.php:105
    158158msgid "Disposable"
    159159msgstr "Wegwerp"
    160160
    161 #: checkout/checkout.php:252
     161#: checkout/checkout.php:201
    162162msgid "Deposit"
    163163msgstr "Statiegeld"
    164164
    165 #: checkout/checkout.php:289
    166 msgid "Select a packaging option."
    167 msgstr "Selecteer een verzendverpakking."
    168 
    169 #: admin/settings.php:115
     165#: admin/settings.php:122
    170166msgid "Cart limit"
    171167msgstr "Productenlimiet"
    172168
    173 #: admin/settings.php:317
     169#: admin/settings.php:352
    174170msgid "Enable cart limit"
    175171msgstr "Activeer productenlimiet"
    176172
    177 #: admin/settings.php:318
     173#: admin/settings.php:353
    178174msgid "If enabled, reusable packaging will not be available when the cart exceeds the set amount of items."
    179175msgstr "Wanneer deze optie is ingeschakeld wordt er geen herbruikbare verpakking aangeboden voor orders met meer dan dit aantal items."
    180176
    181 #: admin/settings.php:320
     177#: admin/settings.php:355
    182178msgid "Max items:"
    183179msgstr "Max aantal items:"
    184180
    185 #: checkout/checkout.php:202
     181#: checkout/checkout.php:165
    186182msgid "Reusable (deposit)"
    187183msgstr "Herbruikbaar (statiegeld)"
    188184
    189 #: checkout/checkout.php:205
     185#: checkout/checkout.php:169
    190186msgid "Nice, there is a return point near you! Return the packaging at a return point to get your deposit (€%s) back right away. Click for more information."
    191187msgstr "Yes, er is een inleverpunt in je buurt! Lever de verpakking in bij een inleverpunt en ontvang direct je statiegeld (€%s) terug. Klik voor meer informatie."
  • boxo-return/trunk/languages/boxo-return.pot

    r3317144 r3371141  
    33msgid ""
    44msgstr ""
    5 "Project-Id-Version: BOXO Return 0.0.53\n"
     5"Project-Id-Version: BOXO Return 0.0.61\n"
    66"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/boxo-return\n"
    77"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
     
    1010"Content-Type: text/plain; charset=UTF-8\n"
    1111"Content-Transfer-Encoding: 8bit\n"
    12 "POT-Creation-Date: 2025-06-12T14:12:16+00:00\n"
     12"POT-Creation-Date: 2025-10-01T13:42:31+00:00\n"
    1313"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    1414"X-Generator: WP-CLI 2.11.0\n"
     
    3535msgstr ""
    3636
    37 #: admin/admin.php:36
     37#: admin/admin.php:42
    3838msgid "Settings saved"
    3939msgstr ""
    4040
    41 #: admin/admin.php:48
     41#: admin/admin.php:54
    4242msgid "Save"
    4343msgstr ""
    4444
    45 #: admin/admin.php:58
    46 #: admin/settings.php:36
     45#: admin/admin.php:72
     46#: admin/settings.php:43
    4747msgid "Settings"
    4848msgstr ""
    4949
    50 #: admin/order.php:38
    51 #: admin/order.php:80
    52 #: admin/order.php:105
    53 #: checkout/checkout.php:195
    54 #: checkout/checkout.php:196
     50#: admin/order.php:49
     51#: admin/order.php:127
     52#: admin/order.php:159
     53#: checkout/checkout.php:76
     54#: checkout/checkout.php:77
    5555msgid "Packaging"
    5656msgstr ""
    5757
    58 #: admin/order.php:60
     58#: admin/order.php:94
    5959msgid "Reusable"
    6060msgstr ""
    6161
    62 #: admin/order.php:63
    63 #: checkout/checkout.php:220
     62#: admin/order.php:97
     63#: checkout/checkout.php:105
    6464msgid "Disposable"
    6565msgstr ""
    6666
    67 #: admin/order.php:128
    68 #: admin/settings.php:222
     67#: admin/order.php:185
     68#: admin/settings.php:248
    6969msgid "Reusable packaging"
    7070msgstr ""
    7171
    72 #: admin/order.php:138
    73 #: admin/settings.php:223
    74 #: checkout/checkout.php:264
     72#: admin/order.php:198
     73#: admin/settings.php:249
     74#: checkout/checkout.php:214
    7575msgid "Disposable packaging"
    7676msgstr ""
    7777
    78 #: admin/product-picker.php:48
     78#: admin/product-picker.php:47
    7979msgid "Add a product:"
    8080msgstr ""
    8181
    82 #: admin/product-picker.php:49
     82#: admin/product-picker.php:48
    8383msgid "Product name"
    8484msgstr ""
    8585
    86 #: admin/product-picker.php:51
     86#: admin/product-picker.php:50
    8787msgid "No products found."
    8888msgstr ""
    8989
    90 #: admin/product-picker.php:52
     90#: admin/product-picker.php:51
    9191msgid "Remove"
    9292msgstr ""
    9393
    94 #: admin/settings.php:43
     94#: admin/settings.php:50
    9595msgid "API Key"
    9696msgstr ""
    9797
    98 #: admin/settings.php:55
     98#: admin/settings.php:62
    9999msgid "Deposit amount in cents"
    100100msgstr ""
    101101
    102 #: admin/settings.php:67
     102#: admin/settings.php:74
    103103msgid "Disposable packaging fee"
    104104msgstr ""
    105105
    106 #: admin/settings.php:79
     106#: admin/settings.php:86
    107107msgid "Packaging selection"
    108108msgstr ""
    109109
    110 #: admin/settings.php:91
     110#: admin/settings.php:98
    111111msgid "URL to information page (optional)"
    112112msgstr ""
    113113
    114 #: admin/settings.php:103
     114#: admin/settings.php:110
    115115msgid "Allowed in reusable packaging"
    116116msgstr ""
    117117
    118 #: admin/settings.php:115
     118#: admin/settings.php:122
    119119msgid "Cart limit"
    120120msgstr ""
    121121
    122 #: admin/settings.php:144
     122#: admin/settings.php:161
    123123msgid "Enter the API key provided by BOXO Return."
    124124msgstr ""
    125125
    126 #: admin/settings.php:170
     126#: admin/settings.php:193
    127127msgid "Enable disposable packaging fee"
    128128msgstr ""
    129129
    130 #: admin/settings.php:171
     130#: admin/settings.php:194
    131131msgid "If enabled, a fee is charged when the customer does not select reusable packaging."
    132132msgstr ""
    133133
    134 #: admin/settings.php:173
     134#: admin/settings.php:196
    135135msgid "Fee in cents:"
    136136msgstr ""
    137137
    138 #: admin/settings.php:219
     138#: admin/settings.php:245
    139139msgid "Default selection:"
    140140msgstr ""
    141141
    142 #: admin/settings.php:220
     142#: admin/settings.php:246
    143143msgid "Customer chooses packaging"
    144144msgstr ""
    145145
    146 #: admin/settings.php:221
     146#: admin/settings.php:247
    147147msgid "Always use reusable packaging when possible"
    148148msgstr ""
    149149
    150 #: admin/settings.php:250
     150#: admin/settings.php:279
    151151msgid "Will open in a new tab when the customer clicks the information button. Defaults to BOXO Return homepage."
    152152msgstr ""
    153153
    154 #: admin/settings.php:275
     154#: admin/settings.php:307
    155155msgid "All products"
    156156msgstr ""
    157157
    158 #: admin/settings.php:276
     158#: admin/settings.php:308
    159159msgid "All products except:"
    160160msgstr ""
    161161
    162 #: admin/settings.php:277
     162#: admin/settings.php:309
    163163msgid "Only these products:"
    164164msgstr ""
    165165
    166 #: admin/settings.php:317
     166#: admin/settings.php:352
    167167msgid "Enable cart limit"
    168168msgstr ""
    169169
    170 #: admin/settings.php:318
     170#: admin/settings.php:353
    171171msgid "If enabled, reusable packaging will not be available when the cart exceeds the set amount of items."
    172172msgstr ""
    173173
    174 #: admin/settings.php:320
     174#: admin/settings.php:355
    175175msgid "Max items:"
    176176msgstr ""
    177177
    178 #: checkout/checkout.php:202
     178#: checkout/checkout.php:165
    179179msgid "Reusable (deposit)"
    180180msgstr ""
    181181
    182 #: checkout/checkout.php:205
     182#: checkout/checkout.php:169
    183183msgid "Nice, there is a return point near you! Return the packaging at a return point to get your deposit (€%s) back right away. Click for more information."
    184184msgstr ""
    185185
    186 #: checkout/checkout.php:252
     186#: checkout/checkout.php:201
    187187msgid "Deposit"
    188188msgstr ""
    189 
    190 #: checkout/checkout.php:289
    191 msgid "Select a packaging option."
    192 msgstr ""
  • boxo-return/trunk/readme.txt

    r3349705 r3371141  
    22Contributors: boxodev
    33Tags: shipping, packaging
    4 Requires at least: 4.7
     4Requires at least: 6.5
    55Tested up to: 6.5.3
    6 Stable tag: 0.0.60
    7 Requires PHP: 7.0
     6Stable tag: 0.0.61
     7Requires PHP: 7.1
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    2727== Changelog ==
    2828
     29= 0.0.61 =
     30Improve checkout performance and theme support.
     31
    2932= 0.0.60 =
    3033Remove packaging selection requirement in checkout, which was made redundant by default selection.
     34
     35= 0.0.58 =
     36Improve checkout performance.
    3137
    3238= 0.0.57 =
Note: See TracChangeset for help on using the changeset viewer.