Add Siti Delivery Time Notices plugin and related functionality
All checks were successful
Build & Release Plugin / release (push) Successful in 8s

- 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.
This commit is contained in:
2026-02-01 17:35:42 +00:00
parent 2be31ef74d
commit 67551a734c
4 changed files with 637 additions and 13 deletions

View File

@@ -9,7 +9,7 @@ on:
push: push:
branches: [ main ] branches: [ main ]
paths: paths:
- 'master-file.php' - 'siti-delivery-time-notices.php'
- 'includes/**' - 'includes/**'
- 'assets/**' - 'assets/**'
- 'languages/**' - 'languages/**'
@@ -18,8 +18,8 @@ jobs:
release: release:
uses: roberto/ci-workflows/.gitea/workflows/wp-plugin-release.yml@c6393ed47258d6f040ceeed3994b17b7faa3ef23 uses: roberto/ci-workflows/.gitea/workflows/wp-plugin-release.yml@c6393ed47258d6f040ceeed3994b17b7faa3ef23
with: with:
main_file: master-file.php main_file: siti-delivery-time-notices.php
slug: siti-plugin-template slug: siti-delivery-time-notices
release_body: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release_notes || '' }} release_body: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release_notes || '' }}
secrets: secrets:
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}

41
assets/js/levertijden.js Normal file
View File

@@ -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));

View File

@@ -0,0 +1,581 @@
<?php
final class SitiDeliveryTimeNotice {
private const OPTION_KEY = 'lb_levertijd_settings';
private const META_SUPPLIER_OVERRIDE = '_lb_levertijd_supplier_override';
public static function init(): void {
add_action('init', [__CLASS__, 'load_textdomain']);
add_action('admin_menu', [__CLASS__, 'register_settings_page']);
add_action('admin_init', [__CLASS__, 'maybe_save_settings']);
add_action('wp_enqueue_scripts', [__CLASS__, 'enqueue_assets']);
add_action('woocommerce_product_options_general_product_data', [__CLASS__, 'render_product_override_field']);
add_action('woocommerce_process_product_meta', [__CLASS__, 'save_product_override_field']);
add_filter('woocommerce_get_availability_text', [__CLASS__, 'filter_availability_text'], 10, 2);
add_action('woocommerce_after_add_to_cart_form', [__CLASS__, 'render_card_below_cart'], 5);
add_filter('woocommerce_available_variation', [__CLASS__, 'add_variation_levertijd_data'], 10, 3);
}
public static function load_textdomain(): void {
load_plugin_textdomain(SITI_DELIVERY_TIME_NOTICES_SLUG, false, dirname(plugin_basename(__FILE__)) . '/languages');
}
public static function enqueue_assets(): void {
wp_enqueue_script(
'lb-levertijden',
plugins_url('assets/js/levertijden.js', __FILE__),
['jquery'],
'1.0.2',
true
);
}
private static function get_settings(): array {
$defaults = [
'default_range' => [
'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');
?>
<div class="wrap">
<h1><?php esc_html_e('Levertijden configuratie', SITI_DELIVERY_TIME_NOTICES_SLUG); ?></h1>
<form method="post">
<?php wp_nonce_field('lb_save_levertijden', 'lb_levertijden_nonce'); ?>
<h2><?php esc_html_e('Standaard levertijd (voorraad aanwezig)', SITI_DELIVERY_TIME_NOTICES_SLUG); ?></h2>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e('Minimale werkdagen', SITI_DELIVERY_TIME_NOTICES_SLUG); ?></th>
<td><input type="number" min="1" name="lb_default_min" value="<?php echo esc_attr($settings['default_range']['min']); ?>" /></td>
</tr>
<tr>
<th scope="row"><?php esc_html_e('Maximale werkdagen', SITI_DELIVERY_TIME_NOTICES_SLUG); ?></th>
<td><input type="number" min="1" name="lb_default_max" value="<?php echo esc_attr($settings['default_range']['max']); ?>" /></td>
</tr>
</table>
<h2><?php esc_html_e('Levertijd per leverancier', SITI_DELIVERY_TIME_NOTICES_SLUG); ?></h2>
<p><?php esc_html_e('Voeg elke leverancier toe zoals de waarde in het veld _wpci_supplier wordt opgeslagen.', SITI_DELIVERY_TIME_NOTICES_SLUG); ?></p>
<table class="widefat fixed striped">
<thead>
<tr>
<th><?php esc_html_e('Leverancier sleutel', SITI_DELIVERY_TIME_NOTICES_SLUG); ?></th>
<th><?php esc_html_e('Label', SITI_DELIVERY_TIME_NOTICES_SLUG); ?></th>
<th><?php esc_html_e('Min. werkdagen', SITI_DELIVERY_TIME_NOTICES_SLUG); ?></th>
<th><?php esc_html_e('Max. werkdagen', SITI_DELIVERY_TIME_NOTICES_SLUG); ?></th>
<th><?php esc_html_e('Acties', SITI_DELIVERY_TIME_NOTICES_SLUG); ?></th>
</tr>
</thead>
<tbody id="lb-supplier-rows">
<?php if (!empty($suppliers)) : ?>
<?php foreach ($suppliers as $key => $range) : ?>
<?php self::render_supplier_row($key, $range, $select_options); ?>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<p>
<button type="button" class="button" id="lb-add-supplier"><?php esc_html_e('Leverancier toevoegen', SITI_DELIVERY_TIME_NOTICES_SLUG); ?></button>
</p>
<p class="submit">
<button type="submit" class="button button-primary"><?php esc_html_e('Opslaan', SITI_DELIVERY_TIME_NOTICES_SLUG); ?></button>
</p>
</form>
</div>
<script>
(function(){
const container = document.getElementById('lb-supplier-rows');
const addButton = document.getElementById('lb-add-supplier');
if(!container || !addButton){ return; }
const template = <?php echo wp_json_encode(self::get_supplier_row_template($select_options, $settings['default_range'])); ?>;
addButton.addEventListener('click', function(){
const row = document.createElement('tr');
row.innerHTML = template;
container.appendChild(row);
});
container.addEventListener('click', function(event){
if(event.target.classList.contains('lb-remove-supplier')){
event.preventDefault();
const row = event.target.closest('tr');
if(row){
row.remove();
}
}
});
}());
</script>
<?php
}
private static function render_supplier_row(string $key, array $range, array $select_options): void {
$label = $range['label'] ?? $key;
?>
<tr>
<?php echo self::get_supplier_row_inner_html($key, ['label' => $label, 'min' => $range['min'], 'max' => $range['max']], $select_options); ?>
</tr>
<?php
}
private static function get_supplier_row_inner_html(string $key, array $range, array $select_options): string {
$label = $range['label'] ?? '';
ob_start();
?>
<td><?php echo self::render_supplier_select($key, $select_options); ?></td>
<td><input type="text" name="lb_suppliers[label][]" value="<?php echo esc_attr($label); ?>" /></td>
<td><input type="number" min="1" name="lb_suppliers[min][]" value="<?php echo esc_attr($range['min']); ?>" /></td>
<td><input type="number" min="1" name="lb_suppliers[max][]" value="<?php echo esc_attr($range['max']); ?>" /></td>
<td><button type="button" class="button lb-remove-supplier"><?php esc_html_e('Verwijderen', SITI_DELIVERY_TIME_NOTICES_SLUG); ?></button></td>
<?php
return ob_get_clean();
}
private static function get_supplier_row_template(array $select_options, array $default_range): string {
$content = self::get_supplier_row_inner_html('', [
'label' => '',
'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 = '<select name="lb_suppliers[key][]">';
foreach ($options as $value => $label) {
$html .= sprintf(
'<option value="%s"%s>%s</option>',
esc_attr($value),
selected($selected, (string) $value, false),
esc_html($label)
);
}
$html .= '</select>';
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 '<div class="options_group">';
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 '</div>';
}
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 '<div class="lb-levertijd-card list-group list-group-flush mb-4" data-default-text="' . esc_attr($text) . '" style="' . ($text === '' ? 'display:none;' : '') . '">';
echo '<div class="list-group-item"><i class="fa fa-truck" aria-hidden="true"></i> <span class="lb-levertijd-text">' . esc_html($text) . '</span></div>';
echo '</div>';
}
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;
}
}

View File

@@ -1,6 +1,6 @@
<?php <?php
/** /**
* Plugin Name: Siti Plugin Template * Plugin Name: Siti Delivery Time Notices
* *
* @package PluginPackage * @package PluginPackage
* @author Roberto Guagliardo | SitiWeb * @author Roberto Guagliardo | SitiWeb
@@ -8,25 +8,25 @@
* @license GPL-2.0-or-later * @license GPL-2.0-or-later
* *
* @wordpress-plugin * @wordpress-plugin
* Plugin Name: Siti Plugin Template * Plugin Name: Siti Delivery Time Notices
* Plugin URI: https://plugins.robert.ooo/plugin/ * Plugin URI: https://plugins.robert.ooo/plugin/
* Description: Description of the plugin. * Description: A plugin to show delivery time notices on WooCommerce products.
* Version: 1.0.0 * Version: 1.0.0
* Requires at least: 5.2 * Requires at least: 5.2
* Requires PHP: 7.2 * Requires PHP: 7.2
* Author: Roberto Guagliardo | SitiWeb * Author: Roberto Guagliardo | SitiWeb
* Author URI: https://sitiweb.nl/ * Author URI: https://sitiweb.nl/
* Text Domain: plugin-slug * Text Domain: siti-delivery-time-notices
* License: GPL v2 or later * License: GPL v2 or later
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
* Update URI: https://example.com/my-plugin/
* Requires Plugins: my-plugin, yet-another-plugin
*/ */
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
if ( ! defined( 'PLUGIN_SLUG_VERSION' ) ) { define('SITI_DELIVERY_TIME_NOTICES_SLUG', 'siti-delivery-time-notices' );
if ( ! defined( 'SITI_DELIVERY_TIME_NOTICES_VERSION' ) ) {
$plugin_data = get_file_data( $plugin_data = get_file_data(
__FILE__, __FILE__,
[ [
@@ -36,7 +36,7 @@ if ( ! defined( 'PLUGIN_SLUG_VERSION' ) ) {
); );
$plugin_version = isset( $plugin_data['Version'] ) && $plugin_data['Version'] ? $plugin_data['Version'] : '1.0.0'; $plugin_version = isset( $plugin_data['Version'] ) && $plugin_data['Version'] ? $plugin_data['Version'] : '1.0.0';
define( 'PLUGIN_SLUG_VERSION', $plugin_version ); define( 'SITI_DELIVERY_TIME_NOTICES_VERSION', $plugin_version );
} }
if( ! class_exists( 'SitiWebUpdater2' ) ){ if( ! class_exists( 'SitiWebUpdater2' ) ){
@@ -45,5 +45,7 @@ if( ! class_exists( 'SitiWebUpdater2' ) ){
$updater = new SitiWebUpdater2( __FILE__ ); $updater = new SitiWebUpdater2( __FILE__ );
$updater->set_owner( 'roberto' ); $updater->set_owner( 'roberto' );
$updater->set_repository( 'siti-plugin-template' ); $updater->set_repository( SITI_DELIVERY_TIME_NOTICES_SLUG );
$updater->initialize(); $updater->initialize();
SitiDeliveryTimeNotice::init();