From 67551a734cde9dadf73030ce141ba7655016c286 Mon Sep 17 00:00:00 2001 From: Roberto Guagliardo Date: Sun, 1 Feb 2026 17:35:42 +0000 Subject: [PATCH] Add Siti Delivery Time Notices plugin and related functionality - Create main plugin file with metadata and initialization logic. - Implement SitiDeliveryTimeNotice class for managing delivery time settings. - Add JavaScript for updating delivery time notices on product pages. - Update GitHub Actions workflow to reflect new file structure. - Remove deprecated master-file.php. --- .gitea/workflows/release.yml | 6 +- assets/js/levertijden.js | 41 ++ includes/SitiDeliveryTimeNotice.php | 581 ++++++++++++++++++ ...file.php => siti-delivery-time-notices.php | 22 +- 4 files changed, 637 insertions(+), 13 deletions(-) create mode 100644 assets/js/levertijden.js create mode 100644 includes/SitiDeliveryTimeNotice.php rename master-file.php => siti-delivery-time-notices.php (64%) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 587109b..8bb67e7 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -9,7 +9,7 @@ on: push: branches: [ main ] paths: - - 'master-file.php' + - 'siti-delivery-time-notices.php' - 'includes/**' - 'assets/**' - 'languages/**' @@ -18,8 +18,8 @@ jobs: release: uses: roberto/ci-workflows/.gitea/workflows/wp-plugin-release.yml@c6393ed47258d6f040ceeed3994b17b7faa3ef23 with: - main_file: master-file.php - slug: siti-plugin-template + main_file: siti-delivery-time-notices.php + slug: siti-delivery-time-notices release_body: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release_notes || '' }} secrets: RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} \ No newline at end of file diff --git a/assets/js/levertijden.js b/assets/js/levertijden.js new file mode 100644 index 0000000..937b11d --- /dev/null +++ b/assets/js/levertijden.js @@ -0,0 +1,41 @@ +(function ($) { + $(function () { + var $card = $('.lb-levertijd-card'); + if (!$card.length) { + return; + } + + var $text = $card.find('.lb-levertijd-text'); + var defaultText = $card.data('defaultText') || ''; + var $form = $('.variations_form'); + + function updateCard(text) { + if (!text) { + $card.toggleClass('lb-levertijd-hidden', true).hide(); + return; + } + + $text.text(text); + $card.toggleClass('lb-levertijd-hidden', false).show(); + } + + if (!$form.length) { + updateCard(defaultText); + return; + } + + updateCard(defaultText); + + $form.on('found_variation', function (event, variation) { + if (variation && variation.lb_levertijd) { + updateCard(variation.lb_levertijd); + } else { + updateCard(defaultText); + } + }); + + $form.on('reset_data hide_variation', function () { + updateCard(defaultText); + }); + }); +}(jQuery)); diff --git a/includes/SitiDeliveryTimeNotice.php b/includes/SitiDeliveryTimeNotice.php new file mode 100644 index 0000000..2772cb2 --- /dev/null +++ b/includes/SitiDeliveryTimeNotice.php @@ -0,0 +1,581 @@ + + [ + 'min' => 1, + 'max' => 2, + ], + 'suppliers' => [], + ]; + + $options = get_option(self::OPTION_KEY, []); + if (!is_array($options)) { + $options = []; + } + + $options = wp_parse_args($options, $defaults); + $options['default_range'] = self::sanitize_range($options['default_range']); + + if (!is_array($options['suppliers'])) { + $options['suppliers'] = []; + } else { + foreach ($options['suppliers'] as $key => $range) { + $options['suppliers'][$key] = self::sanitize_range($range); + } + } + + $options['suppliers'] = self::ensure_known_suppliers($options['suppliers'], $options['default_range']); + + return $options; + } + + public static function register_settings_page(): void { + add_submenu_page( + 'woocommerce', + __('Levertijden', SITI_DELIVERY_TIME_NOTICES_SLUG), + __('Levertijden', SITI_DELIVERY_TIME_NOTICES_SLUG), + 'manage_woocommerce', + 'lb-levertijden', + [__CLASS__, 'render_settings_page'] + ); + } + + public static function maybe_save_settings(): void { + if (!isset($_POST['lb_levertijden_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['lb_levertijden_nonce'])), 'lb_save_levertijden')) { + return; + } + + if (!current_user_can('manage_woocommerce')) { + return; + } + + $default_min = isset($_POST['lb_default_min']) ? absint($_POST['lb_default_min']) : 1; + $default_max = isset($_POST['lb_default_max']) ? absint($_POST['lb_default_max']) : $default_min; + + $default_range = self::sanitize_range([ + 'min' => $default_min, + 'max' => $default_max, + ]); + + $suppliers = []; + if (!empty($_POST['lb_suppliers']) && is_array($_POST['lb_suppliers'])) { + $data = wp_unslash($_POST['lb_suppliers']); + $keys = $data['key'] ?? []; + $labels = $data['label'] ?? []; + $mins = $data['min'] ?? []; + $maxes = $data['max'] ?? []; + $count = max(count($keys), count($labels), count($mins), count($maxes)); + + for ($i = 0; $i < $count; $i++) { + $key = sanitize_key($keys[$i] ?? ''); + $label = sanitize_text_field($labels[$i] ?? ''); + $min = absint($mins[$i] ?? 0); + $max = absint($maxes[$i] ?? 0); + + if ($key === '' || $label === '') { + continue; + } + + $range = self::sanitize_range([ + 'min' => $min, + 'max' => $max, + 'label' => $label, + ]); + + $range['label'] = $label; + $suppliers[$key] = $range; + } + } + + update_option( + self::OPTION_KEY, + [ + 'default_range' => $default_range, + 'suppliers' => $suppliers, + ] + ); + + add_settings_error('lb_levertijden', 'lb_levertijden_saved', __('Levertijden opgeslagen.', SITI_DELIVERY_TIME_NOTICES_SLUG), 'updated'); + } + + public static function render_settings_page(): void { + $settings = self::get_settings(); + $suppliers = $settings['suppliers']; + $select_options = self::get_supplier_dropdown_options(array_keys($suppliers)); + settings_errors('lb_levertijden'); + ?> +
+

+
+ + +

+ + + + + + + + + +
+ +

+

+ + + + + + + + + + + + + $range) : ?> + + + + +
+

+ +

+ +

+ +

+
+
+ + + + $label, 'min' => $range['min'], 'max' => $range['max']], $select_options); ?> + + + + + + + + '', + 'min' => $default_range['min'] ?? 1, + 'max' => $default_range['max'] ?? 1, + ], $select_options); + + return $content; + } + + private static function sanitize_range(array $range): array { + $min = isset($range['min']) ? absint($range['min']) : 0; + $max = isset($range['max']) ? absint($range['max']) : 0; + + if ($min === 0 && $max === 0) { + $min = 1; + $max = 1; + } + + if ($max === 0) { + $max = $min; + } + + if ($min === 0) { + $min = $max; + } + + if ($max < $min) { + $temp = $min; + $min = $max; + $max = $temp; + } + + return [ + 'min' => max(1, $min), + 'max' => max(1, $max), + 'label' => $range['label'] ?? '', + ]; + } + + private static function ensure_known_suppliers(array $current, array $default_range): array { + $known = self::get_distinct_supplier_sources(); + foreach ($known as $key => $label) { + if (!isset($current[$key])) { + $current[$key] = [ + 'min' => $default_range['min'], + 'max' => $default_range['max'], + 'label' => $label ?: $key, + ]; + continue; + } + + if (empty($current[$key]['label']) && $label) { + $current[$key]['label'] = $label; + } + } + + ksort($current); + + return $current; + } + + private static function get_supplier_dropdown_options(array $current_keys = []): array { + $options = [ + '' => __('Selecteer leverancier', SITI_DELIVERY_TIME_NOTICES_SLUG), + ]; + + foreach (self::get_distinct_supplier_sources() as $key => $label) { + $options[$key] = $label ?: $key; + } + + foreach ($current_keys as $key) { + if ($key !== '' && !isset($options[$key])) { + $options[$key] = $key; + } + } + + return $options; + } + + private static function render_supplier_select(string $selected, array $options): string { + if ($selected !== '' && !array_key_exists($selected, $options)) { + $options[$selected] = $selected; + } + + $html = ''; + + return $html; + } + + private static function get_distinct_supplier_sources(): array { + static $cache = null; + if ($cache !== null) { + return $cache; + } + + global $wpdb; + $table = $wpdb->postmeta; + $rows = $wpdb->get_col( + $wpdb->prepare( + "SELECT DISTINCT meta_value FROM {$table} WHERE meta_key = %s AND meta_value <> ''", + '_wpci_supplier' + ) + ); + + $cache = []; + if ($rows) { + foreach ($rows as $raw) { + foreach (self::normalize_supplier_values($raw) as $value) { + $key = sanitize_key($value); + if ($key === '' || isset($cache[$key])) { + continue; + } + $cache[$key] = $value; + } + } + } + + return $cache; + } + + private static function normalize_supplier_values($raw): array { + $result = []; + $value = maybe_unserialize($raw); + + if (is_array($value)) { + array_walk_recursive($value, function ($item) use (&$result) { + if (is_scalar($item)) { + $item = trim((string) $item); + if ($item !== '') { + $result[] = $item; + } + } + }); + } elseif (is_scalar($value)) { + $value = trim((string) $value); + if ($value !== '') { + $result[] = $value; + } + } + + return $result; + } + + public static function render_product_override_field(): void { + $settings = self::get_settings(); + $choices = [ + '' => __('Automatisch (voorraad / leverancier)', SITI_DELIVERY_TIME_NOTICES_SLUG), + 'default' => __('Altijd standaard levertijd', SITI_DELIVERY_TIME_NOTICES_SLUG), + ]; + + foreach ($settings['suppliers'] as $key => $range) { + $choices[$key] = sprintf( + '%s (%s)', + $range['label'] ?: $key, + self::format_range_text($range) + ); + } + + global $post; + $value = get_post_meta($post->ID, self::META_SUPPLIER_OVERRIDE, true); + + echo '
'; + woocommerce_wp_select([ + 'id' => self::META_SUPPLIER_OVERRIDE, + 'label' => __('Levertijd leverancier', SITI_DELIVERY_TIME_NOTICES_SLUG), + 'options' => $choices, + 'value' => $value, + 'desc_tip' => false, + 'description' => __('Kies een leverancier om de levertijd van dit product te forceren.', SITI_DELIVERY_TIME_NOTICES_SLUG), + ]); + echo '
'; + } + + public static function save_product_override_field(int $post_id): void { + if (!isset($_POST[self::META_SUPPLIER_OVERRIDE])) { + return; + } + + $value = sanitize_text_field(wp_unslash($_POST[self::META_SUPPLIER_OVERRIDE])); + update_post_meta($post_id, self::META_SUPPLIER_OVERRIDE, $value); + } + + public static function filter_availability_text(string $availability, WC_Product $product): string { + $text = self::get_levertijd_text($product, false); + if ($text) { + return sprintf(__('Verwachte levertijd: %s', SITI_DELIVERY_TIME_NOTICES_SLUG), $text); + } + + return $availability; + } + + public static function render_card_below_cart(): void { + if (!is_product()) { + return; + } + + global $product; + if (!$product instanceof WC_Product) { + return; + } + + if ($product->is_type('variable')) { + $text = ''; + } else { + $text = self::get_levertijd_text($product); + if (!$text) { + return; + } + } + + echo '
'; + echo '
' . esc_html($text) . '
'; + echo '
'; + } + + public static function add_variation_levertijd_data($variation_data, $product = null, $variation = null) { + if ($variation instanceof WC_Product_Variation) { + $text = self::get_levertijd_text($variation); + $variation_data['lb_levertijd'] = $text; + } + + return $variation_data; + } + + private static function get_product_meta_with_parent(WC_Product $product, string $key, bool $allow_array = false) { + $value = get_post_meta($product->get_id(), $key, true); + if (($value === '' || ($allow_array && empty($value))) && $product->get_parent_id()) { + $value = get_post_meta($product->get_parent_id(), $key, true); + } + + if (!$allow_array && is_array($value)) { + return ''; + } + + return $value; + } + + private static function get_levertijd_text(WC_Product $product, bool $with_label = true): string { + $range = self::resolve_range_for_product($product); + if (!$range) { + return ''; + } + + $text = self::format_range_text($range); + if ($text === '') { + return ''; + } + + return $with_label ? sprintf(__('Verwachte levertijd: %s', SITI_DELIVERY_TIME_NOTICES_SLUG), $text) : $text; + } + + private static function resolve_range_for_product(WC_Product $product): ?array { + $settings = self::get_settings(); + + $override = self::get_product_meta_with_parent($product, self::META_SUPPLIER_OVERRIDE); + if ($override && $override !== 'auto') { + if ($override === 'default') { + return $settings['default_range']; + } + + if (isset($settings['suppliers'][$override])) { + return $settings['suppliers'][$override]; + } + } + + if (self::has_local_stock($product)) { + return $settings['default_range']; + } + + $supplier = self::get_supplier_key($product); + if ($supplier && isset($settings['suppliers'][$supplier])) { + return $settings['suppliers'][$supplier]; + } + + return $settings['default_range']; + } + + private static function has_local_stock(WC_Product $product): bool { + $explicit_stock = self::get_product_meta_with_parent($product, '_stock'); + if ($explicit_stock !== '') { + $qty = function_exists('wc_stock_amount') ? wc_stock_amount($explicit_stock) : (int) $explicit_stock; + return $qty > 0; + } + + if ($product->managing_stock()) { + $qty = $product->get_stock_quantity(); + if ($qty !== null) { + return $qty > 0; + } + } + + return $product->is_in_stock(); + } + + private static function get_supplier_key(WC_Product $product): string { + $supplier_meta = self::get_product_meta_with_parent($product, '_wpci_supplier', true); + if ($supplier_meta === '' || $supplier_meta === null) { + return ''; + } + + $supplier = $supplier_meta; + if (is_string($supplier) && is_serialized($supplier)) { + $maybe = maybe_unserialize($supplier); + if (is_array($maybe)) { + $supplier = $maybe; + } + } + + if (is_array($supplier)) { + $supplier = reset($supplier); + } + + return sanitize_key((string) $supplier); + } + + private static function format_range_text(array $range): string { + $min = absint($range['min'] ?? 0); + $max = absint($range['max'] ?? 0); + + if ($min === 0 && $max === 0) { + return ''; + } + + if ($max === 0) { + $max = $min; + } + + if ($min === $max) { + $text = sprintf(_n('%d werkdag', '%d werkdagen', $min, SITI_DELIVERY_TIME_NOTICES_SLUG), $min); + } else { + $text = sprintf(__('van %d tot %d werkdagen', SITI_DELIVERY_TIME_NOTICES_SLUG), $min, $max); + } + + return $text; + } +} diff --git a/master-file.php b/siti-delivery-time-notices.php similarity index 64% rename from master-file.php rename to siti-delivery-time-notices.php index ab2c274..6f5b973 100644 --- a/master-file.php +++ b/siti-delivery-time-notices.php @@ -1,6 +1,6 @@ set_owner( 'roberto' ); -$updater->set_repository( 'siti-plugin-template' ); -$updater->initialize(); \ No newline at end of file +$updater->set_repository( SITI_DELIVERY_TIME_NOTICES_SLUG ); +$updater->initialize(); + +SitiDeliveryTimeNotice::init(); \ No newline at end of file