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(); ?>

    $id ) : ?>
  1. >

0 ) : ?> ←

$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 ''; if ( $box ) { echo '

' . esc_html__( 'Run the setup wizard', 'dayslot' ) . '

'; } }