array( title, render( $settings ), save( $post ) → settings changes ).
*/
function dayslot_wizard_steps() {
$steps = array(
'days' => array(
'title' => __( 'Days and limits', 'dayslot' ),
'render' => 'dayslot_wizard_days',
'save' => 'dayslot_wizard_save_days',
),
'delivery' => array(
'title' => __( 'Delivery times and prices', 'dayslot' ),
'render' => 'dayslot_wizard_delivery',
'save' => 'dayslot_wizard_save_delivery',
),
'pickup' => array(
'title' => __( 'Pickup', 'dayslot' ),
'render' => 'dayslot_wizard_pickup',
'save' => 'dayslot_wizard_save_pickup',
),
'look' => array(
'title' => __( 'Place and look', 'dayslot' ),
'render' => 'dayslot_wizard_look',
'save' => 'dayslot_wizard_save_look',
),
'done' => array(
'title' => __( 'Ready', 'dayslot' ),
'render' => 'dayslot_wizard_done',
'save' => null,
),
);
return apply_filters( 'dayslot_wizard_steps', $steps );
}
/**
* Step "Pickup" (Pro adds the opening hours: action dayslot_wizard_pickup_rows).
*/
function dayslot_wizard_pickup( $s ) {
?>
>
! empty( $post['pickup_enabled'] ) ? 'yes' : 'no',
'pickup_address' => isset( $post['pickup_address'] ) ? sanitize_text_field( $post['pickup_address'] ) : '',
);
return apply_filters( 'dayslot_wizard_save_pickup', $changes, $post );
}
/**
* Put steps after a given step (for add-ons).
*/
function dayslot_wizard_insert_after( $steps, $after, $added ) {
$out = array();
foreach ( $steps as $id => $step ) {
$out[ $id ] = $step;
if ( $id === $after ) {
$out = array_merge( $out, $added );
}
}
return $out + $added;
}
/**
* Wizard page URL (optionally a step).
*/
function dayslot_wizard_url( $step = '' ) {
return admin_url( 'admin.php?page=dayslot-wizard' . ( $step ? '&step=' . $step : '' ) );
}
add_action(
'admin_menu',
function () {
add_submenu_page( 'dayslot', __( 'Setup wizard', 'dayslot' ), __( 'Setup wizard', 'dayslot' ), 'manage_woocommerce', 'dayslot-wizard', 'dayslot_render_wizard' );
},
20
);
// First activation on a fresh site: open the wizard once (not on bulk activation, not on updates).
add_action(
'admin_init',
function () {
if ( ! get_transient( 'dayslot_wizard_redirect' ) || ! current_user_can( 'manage_woocommerce' ) || wp_doing_ajax() ) {
return;
}
delete_transient( 'dayslot_wizard_redirect' );
if ( isset( $_GET['activate-multi'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check of WordPress's own flag.
return;
}
wp_safe_redirect( dayslot_wizard_url() );
exit;
}
);
// --------------------------------------------------------------------------
// Saving a step
// --------------------------------------------------------------------------
add_action(
'admin_post_dayslot_wizard',
function () {
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_die( esc_html__( 'You are not allowed to do this.', 'dayslot' ) );
}
check_admin_referer( 'dayslot_wizard' );
$steps = dayslot_wizard_steps();
$step = isset( $_POST['step'] ) ? sanitize_key( wp_unslash( $_POST['step'] ) ) : '';
if ( isset( $steps[ $step ] ) && is_callable( $steps[ $step ]['save'] ) ) {
// Wizard fields are plain text, numbers and keys: clean everything first; each step checks its own values again.
$posted = map_deep( wp_unslash( $_POST ), 'sanitize_text_field' );
$changes = call_user_func( $steps[ $step ]['save'], $posted );
if ( is_array( $changes ) && $changes ) {
dayslot_update_settings( $changes );
}
}
$ids = array_keys( $steps );
$next = $ids[ min( count( $ids ) - 1, (int) array_search( $step, $ids, true ) + 1 ) ];
if ( 'done' === $next ) {
update_option( 'dayslot_wizard', 'done', false );
}
wp_safe_redirect( dayslot_wizard_url( $next ) );
exit;
}
);
/**
* Step "Days and limits": working days, preparation days, booking ahead, deliveries per day.
*/
function dayslot_wizard_save_days( $post ) {
$open = isset( $post['open'] ) ? array_map( 'absint', (array) $post['open'] ) : array();
$closed = array_values( array_diff( range( 0, 6 ), $open ) );
$min = isset( $post['min_days'] ) ? absint( $post['min_days'] ) : 0;
$max = isset( $post['max_days'] ) ? min( 730, max( 1, absint( $post['max_days'] ) ) ) : 365;
return array(
'closed_weekdays' => $closed,
'min_days' => $min,
'max_days' => max( $min, $max ),
'max_per_day' => isset( $post['max_per_day'] ) ? absint( $post['max_per_day'] ) : 0,
);
}
/**
* Step "Delivery times and prices": on/off, hours and prices of the existing kinds; the rest is kept.
*/
function dayslot_wizard_save_delivery( $post ) {
$rows = array();
$sent = isset( $post['v'] ) ? (array) $post['v'] : array();
foreach ( dayslot_variants( true ) as $v ) {
$in = isset( $sent[ $v['id'] ] ) && is_array( $sent[ $v['id'] ] ) ? $sent[ $v['id'] ] : array();
$rows[] = array(
'id' => $v['id'],
'type' => $v['type'],
'label' => $v['label'],
'note' => $v['note'],
'from' => isset( $in['from'] ) ? $in['from'] : $v['from'],
'to' => isset( $in['to'] ) ? $in['to'] : $v['to'],
'every' => 'interval' === $v['type'] ? $v['length'] : $v['step'],
'limit' => $v['limit'],
'price_weekday' => isset( $in['price_weekday'] ) ? $in['price_weekday'] : $v['price_weekday'],
'price_sunday' => isset( $in['price_sunday'] ) ? $in['price_sunday'] : $v['price_sunday'],
'price_holiday' => $v['price_holiday'],
'enabled' => ! empty( $in['enabled'] ),
);
}
return array(
'variants' => dayslot_sanitize_variants( $rows ),
'holiday_country' => ! empty( $post['pl_holidays'] ) ? 'PL' : '',
);
}
/**
* Step "Place and look": placement and a ready-made look.
*/
function dayslot_wizard_save_look( $post ) {
$changes = array();
if ( isset( $post['placement'] ) && in_array( $post['placement'], array( 'before_address', 'after_address', 'before_notes', 'after_notes' ), true ) ) {
$changes['placement'] = $post['placement'];
}
$presets = dayslot_style_presets();
$preset = isset( $post['preset'] ) ? sanitize_key( $post['preset'] ) : '';
if ( 'theme' === $preset ) {
$changes['style'] = dayslot_style_defaults();
} elseif ( isset( $presets[ $preset ] ) ) {
$changes['style'] = dayslot_sanitize_style( array_merge( dayslot_style_defaults(), $presets[ $preset ], array( 'preset' => $preset ) ) );
}
return apply_filters( 'dayslot_wizard_save_look', $changes, $post );
}
// --------------------------------------------------------------------------
// Page
// --------------------------------------------------------------------------
/**
* The wizard page.
*/
function dayslot_render_wizard() {
if ( ! current_user_can( 'manage_woocommerce' ) ) {
return;
}
$steps = dayslot_wizard_steps();
$ids = array_keys( $steps );
$step = isset( $_GET['step'] ) && isset( $steps[ sanitize_key( wp_unslash( $_GET['step'] ) ) ] ) ? sanitize_key( wp_unslash( $_GET['step'] ) ) : $ids[0]; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$pos = (int) array_search( $step, $ids, true );
$s = dayslot_get_settings();
?>
$name ) : ?>
>
__( 'The customer picks a time window, e.g. 10:00–12:00.', 'dayslot' ),
'exact' => __( 'The customer picks an exact time, e.g. 14:15.', 'dayslot' ),
'night' => __( 'An exact time over midnight, e.g. 02:15 — for surprises at night.', 'dayslot' ),
);
$cur = get_woocommerce_currency_symbol();
?>
>
__( 'After the address (recommended)', 'dayslot' ),
'before_address' => __( 'Before the address', 'dayslot' ),
'before_notes' => __( 'Before the order notes', 'dayslot' ),
'after_notes' => __( 'After the order notes', 'dayslot' ),
) as $value => $label ) :
?>
>
>
$p ) : ?>
>
—
↗
prefix . 'wc_orders_meta' : $wpdb->postmeta;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- admin settings page only.
return (bool) $wpdb->get_var( $wpdb->prepare( 'SELECT 1 FROM %i WHERE meta_key = %s LIMIT 1', $table, DAYSLOT_META ) );
}
/**
* Is our shipping method in a WooCommerce shipping zone?
*/
function dayslot_method_in_a_zone() {
if ( ! class_exists( 'WC_Shipping_Zones' ) ) {
return false;
}
$zones = WC_Shipping_Zones::get_zones();
$zones[] = array( 'shipping_methods' => ( new WC_Shipping_Zone( 0 ) )->get_shipping_methods( true ) );
foreach ( $zones as $zone ) {
foreach ( (array) $zone['shipping_methods'] as $method ) {
if ( 'dayslot_delivery' === $method->id && 'yes' === $method->enabled ) {
return true;
}
}
}
return false;
}
/**
* What is set up: array of array( label, ok, fix_url, fix_label, optional ).
*/
function dayslot_setup_checklist() {
$s = dayslot_get_settings();
$items = array();
if ( dayslot_feature_on( 'shipping' ) ) {
$items['shipping'] = array(
'label' => __( 'The "Delivery by date" method is in a WooCommerce shipping zone', 'dayslot' ),
'ok' => dayslot_method_in_a_zone(),
'fix_url' => admin_url( 'admin.php?page=wc-settings&tab=shipping' ),
'fix_label' => __( 'Add it in WooCommerce → Shipping', 'dayslot' ),
);
}
$items['days'] = array(
'label' => __( 'Working days are set', 'dayslot' ),
'ok' => count( (array) $s['closed_weekdays'] ) < 7,
'fix_url' => dayslot_wizard_url( 'days' ),
'fix_label' => __( 'Choose the days', 'dayslot' ),
);
if ( dayslot_feature_on( 'times' ) ) {
$on = array_filter(
dayslot_variants( true ),
function ( $v ) {
return 'yes' === $v['enabled'];
}
);
$items['kinds'] = array(
'label' => __( 'At least one kind of delivery with a price', 'dayslot' ),
'ok' => (bool) $on,
'fix_url' => dayslot_wizard_url( 'delivery' ),
'fix_label' => __( 'Set times and prices', 'dayslot' ),
);
}
if ( 'yes' === $s['pickup_enabled'] ) {
$items['pickup'] = array(
'label' => __( 'Pickup address is filled in', 'dayslot' ),
'ok' => '' !== trim( (string) $s['pickup_address'] ),
'fix_url' => dayslot_wizard_url( 'pickup' ),
'fix_label' => __( 'Fill in the address', 'dayslot' ),
);
}
$items['checkout'] = array(
'label' => __( 'The shop has a checkout page', 'dayslot' ),
'ok' => wc_get_page_id( 'checkout' ) > 0,
'fix_url' => admin_url( 'admin.php?page=wc-settings&tab=advanced' ),
'fix_label' => __( 'Choose it in WooCommerce → Advanced', 'dayslot' ),
);
$items['order'] = array(
'label' => __( 'A first order with a delivery date (a test order is enough)', 'dayslot' ),
'ok' => dayslot_any_dated_order(),
'fix_url' => wc_get_page_permalink( 'shop' ),
'fix_label' => __( 'Open the shop and test', 'dayslot' ),
'optional' => true,
);
return apply_filters( 'dayslot_setup_checklist', $items );
}
/**
* The checklist; $box = true wraps it in a collapsible "Quick start" box for the settings page.
*/
function dayslot_render_checklist( $box = true ) {
$items = dayslot_setup_checklist();
$required = array_filter(
$items,
function ( $i ) {
return empty( $i['optional'] );
}
);
$ok = count(
array_filter(
$items,
function ( $i ) {
return $i['ok'];
}
)
);
$all_req = ! array_filter(
$required,
function ( $i ) {
return ! $i['ok'];
}
);
if ( $box ) {
echo '';
/* translators: 1: items done, 2: all items */
echo esc_html( sprintf( __( 'Quick start: %1$d of %2$d done', 'dayslot' ), $ok, count( $items ) ) );
echo ' ';
}
echo '';
foreach ( $items as $id => $item ) {
$class = ( $item['ok'] ? 'is-ok' : '' ) . ( empty( $item['optional'] ) ? '' : ' is-optional' );
echo '' . ( $item['ok'] ? '✓' : ( empty( $item['optional'] ) ? '!' : '·' ) ) . ' ';
echo '' . esc_html( $item['label'] ) . ( $item['ok'] ? '' : ' — ' . esc_html__( 'not done yet', 'dayslot' ) . ' ' ) . ' ';
if ( ! empty( $item['fix_url'] ) ) {
echo '' . esc_html( $item['fix_label'] ) . ' → ';
}
echo ' ';
}
echo ' ';
if ( $box ) {
echo '' . esc_html__( 'Run the setup wizard', 'dayslot' ) . '
';
}
}