File "class-location-store-services-meta-box.php"
Full Path: /home/romayxjt/public_html/wp-content/plugins/orderable/inc/modules/location/admin/meta-boxes/class-location-store-services-meta-box.php
File size: 25.13 KB
MIME-type: text/x-php
Charset: utf-8
<?php
/**
* Module: Location.
*
* @since 1.18.0
* @package Orderable/Classes
*/
defined( 'ABSPATH' ) || exit;
/**
* Orderable_Location_Store_Services_Meta_Box class.
*/
class Orderable_Location_Store_Services_Meta_Box {
/**
* Init.
*/
public static function run() {
if ( ! is_admin() ) {
return;
}
// Runs after Orderable_Location_Open_Hours_Meta_Box::save_store_settings(), otherwise no location ID exists.
// This means service hours are saved when creating a new location.
add_action( 'orderable_location_save_data', array( __CLASS__, 'save_data' ), 20 );
add_filter( 'orderable_location_get_save_data', array( __CLASS__, 'get_save_data' ) );
}
/**
* Get the meta box title.
*
* @return string
*/
public static function get_title() {
return __( 'Services', 'orderable' );
}
/**
* Add the Meta Box.
*
* @return void
*/
public static function add() {
add_meta_box(
'orderable_multi_location_store_services_meta_box',
self::get_title(),
array( __CLASS__, 'output' )
);
}
/**
* Output the meta box.
*
* @return void
*/
public static function output() {
$location = new Orderable_Location_Single();
$is_delivery_enabled = $location->is_service_enabled( 'delivery' );
$is_pickup_enabled = $location->is_service_enabled( 'pickup' );
$store_services_toggle_class = array(
'orderable-toggle-field',
'woocommerce-input-toggle',
);
$store_services_delivery_toggle_class = array_merge(
$store_services_toggle_class,
array( 'orderable-delivery-toggle-field' )
);
$store_services_pickup_toggle_class = array_merge(
$store_services_toggle_class,
array( 'orderable-pickup-toggle-field' )
);
if ( $is_delivery_enabled ) {
$store_services_delivery_toggle_class[] = 'woocommerce-input-toggle--enabled';
} else {
$store_services_delivery_toggle_class[] = 'woocommerce-input-toggle--disabled';
}
if ( $is_pickup_enabled ) {
$store_services_pickup_toggle_class[] = 'woocommerce-input-toggle--enabled';
} else {
$store_services_pickup_toggle_class[] = 'woocommerce-input-toggle--disabled';
}
$store_services_delivery_toggle_class = join( ' ', $store_services_delivery_toggle_class );
$store_services_pickup_toggle_class = join( ' ', $store_services_pickup_toggle_class );
?>
<div class="orderable-fields-row orderable-fields-row--meta">
<div class="orderable-fields-row__body">
<div class="orderable-fields-row__body-row orderable-store-services">
<div class="orderable-fields-row__body-row-left">
<h3><?php echo esc_html_x( 'Enable Services', 'Location Services', 'orderable-pro' ); ?></h3>
<p>
<?php
echo esc_html__( 'Which services does this location offer?', 'orderable-pro' );
?>
</p>
</div>
<div class="orderable-fields-row__body-row-right orderable-store-services__enable-services">
<div class="orderable-store-services__enable-service orderable-store-services__enable-service-delivery">
<span
class="<?php echo esc_attr( $store_services_delivery_toggle_class ); ?>"
>
<?php echo esc_html( 'Delivery' ); ?>
</span>
<span class="orderable-store-services__enable-service-label">
<?php echo esc_html( 'Delivery' ); ?>
</span>
<input
type="hidden"
name="orderable_location_store_services_delivery"
value="<?php echo esc_attr( $is_delivery_enabled ? 'yes' : 'no' ); ?>"
class="orderable-toggle-field__input"
/>
</div>
<div class="orderable-store-services__enable-service orderable-store-services__enable-service-pickup">
<span
class="<?php echo esc_attr( $store_services_pickup_toggle_class ); ?>"
>
<?php echo esc_html__( 'Pickup', 'orderable-pro' ); ?>
</span>
<span class="orderable-store-services__enable-service-label">
<?php echo esc_html__( 'Pickup', 'orderable-pro' ); ?>
</span>
<input
type="hidden"
name="orderable_location_store_services_pickup"
value="<?php echo esc_attr( $is_pickup_enabled ? 'yes' : 'no' ); ?>"
class="orderable-toggle-field__input"
/>
</div>
</div>
</div>
<div class="orderable-fields-row__body-row orderable-store-services__service-hours">
<div class="orderable-fields-row__body-row-left">
<h3><?php echo esc_html_x( 'Service Hours', 'Location Services', 'orderable-pro' ); ?></h3>
<p>
<?php echo esc_html( __( 'Set Service Hours and Delivery Zones for your Delivery service.', 'orderable-pro' ) ); ?>
</p>
<br />
<p>
<?php
echo esc_html(
__(
'The days and hours where you offer delivery/pickup services.',
'orderable-pro'
)
);
?>
</p>
</div>
<div class="orderable-fields-row__body-row-right orderable-fields-row__body-row-right--slug orderable-store-services__service-hours-fields">
<?php self::service_hours_fields(); ?>
</div>
</div>
</div>
</div>
<?php
}
/**
* Get the default data.
*
* @param array $data The default data will be appended to $data.
* @return array
*/
public static function get_default_data( $data = array() ) {
if ( ! is_array( $data ) ) {
return $data;
}
$default_service_hours = array(
array(
'time_slot_id' => '',
'days' => array(),
'period' => 'all-day',
'frequency' => '',
'cutoff' => '',
'max_orders' => '',
'time_from' => '',
'time_to' => '',
'from' => array(
'hour' => '9',
'minute' => '00',
'period' => 'AM',
),
'to' => array(
'hour' => '5',
'minute' => '00',
'period' => 'PM',
),
),
);
$default_data = array(
'store_general_services' => array(),
'store_general_service_hours_delivery' => $default_service_hours,
'store_general_service_hours_pickup_same' => '1',
'store_general_service_hours_pickup' => $default_service_hours,
);
return array_merge( $data, $default_data );
}
/**
* Return the data to be saved.
*
* @param array $data The data sent via POST will be appended to $data.
* @return array
*/
public static function get_save_data( $data ) {
if ( ! is_array( $data ) ) {
return $data;
}
$store_services_data = array(
'store_general_services' => self::get_store_services(),
'store_general_service_hours_delivery' => self::get_location_service_hours( 'delivery' ),
'store_general_service_hours_pickup_same' => Orderable_Location_Admin::get_posted_value( 'orderable_location_service_hours_pickup_same_as_delivery' ),
'store_general_service_hours_pickup' => self::get_location_service_hours( 'pickup' ),
);
return array_merge( $data, $store_services_data );
}
/**
* Output service hours fields.
*
* @return void
*/
protected static function service_hours_fields() {
$location = new Orderable_Location_Single();
$services = $location->get_services();
$pickup_same_as_delivery = $location->get_pickup_hours_same_as_delivery();
$is_delivery_enabled = $location->is_service_enabled( 'delivery' );
$is_pickup_enabled = $location->is_service_enabled( 'pickup' );
$active_service = false;
if ( $is_delivery_enabled ) {
$active_service = 'delivery';
} elseif ( $is_pickup_enabled ) {
$active_service = 'pickup';
}
$button_delivery_class = join(
' ',
array_keys(
array_filter(
array(
'orderable-trigger-element--active' => 'delivery' === $active_service,
'orderable-ui-hide' => ! $is_delivery_enabled,
)
)
)
);
$button_pickup_class = join(
' ',
array_keys(
array_filter(
array(
'orderable-trigger-element--active' => 'pickup' === $active_service,
'orderable-ui-hide' => ! $is_pickup_enabled,
)
)
)
);
$wrapper_pickup_class = join(
' ',
array_keys(
array_filter(
array(
'orderable-element--disabled' => (bool) $pickup_same_as_delivery,
'orderable-toggle-wrapper--active' => 'pickup' === $active_service,
)
)
)
);
?>
<p
class="orderable-notice orderable-notice--select-service <?php echo empty( $services ) ? '' : 'orderable-ui-hide'; ?>"
>
<?php esc_html_e( 'Please select services available for this location.', 'orderable' ); ?>
</p>
<div class="orderable-toolbar">
<button
class="orderable-admin-button orderable-admin-button--delivery <?php echo esc_attr( $button_delivery_class ); ?>"
data-orderable-trigger="toggle-wrapper"
data-orderable-wrapper="delivery"
data-orderable-wrapper-group="service"
>
<?php esc_html_e( 'Delivery', 'orderable' ); ?>
</button>
<button
class="orderable-admin-button orderable-admin-button--pickup <?php echo esc_attr( $button_pickup_class ); ?>"
data-orderable-trigger="toggle-wrapper"
data-orderable-wrapper="pickup"
data-orderable-wrapper-group="service"
>
<?php esc_html_e( 'Pickup', 'orderable' ); ?>
</button>
<div class="orderable-toolbar__actions">
<span
class="orderable-toggle-wrapper orderable-toggle-wrapper--delivery <?php echo 'delivery' === $active_service ? 'orderable-toggle-wrapper--active' : ''; ?>"
data-orderable-wrapper-group="service"
>
<button
class="orderable-admin-button orderable-admin-button--primary"
data-orderable-trigger="new-row"
data-orderable-target=".orderable-table--service-hours-delivery"
>
<span class="dashicons dashicons-plus"></span> <?php esc_html_e( 'Add service hours', 'orderable' ); ?>
</button>
</span>
<span
class="orderable-element--pickup orderable-toggle-wrapper orderable-toggle-wrapper--pickup <?php echo esc_attr( $wrapper_pickup_class ); ?>"
data-orderable-wrapper-group="service"
>
<button
class="orderable-admin-button orderable-admin-button--primary"
data-orderable-trigger="new-row"
data-orderable-target=".orderable-table--service-hours-pickup"
>
<span class="dashicons dashicons-plus"></span> <?php esc_html_e( 'Add service hours', 'orderable' ); ?>
</button>
</span>
</div>
</div>
<div
class="orderable-toggle-wrapper orderable-toggle-wrapper--delivery <?php echo ( 'delivery' === $active_service ) ? 'orderable-toggle-wrapper--active' : ''; ?>"
data-orderable-wrapper-group="service"
>
<?php
self::service_hour_table( 'delivery' );
/**
* Render content inside the delivery wrapper.
*
* @since 1.8.0
*/
do_action( 'orderable_store_services_delivery_wrapper' );
?>
</div>
<div
class="orderable-toggle-wrapper orderable-toggle-wrapper--pickup <?php echo ( 'pickup' === $active_service ) ? 'orderable-toggle-wrapper--active' : ''; ?>"
data-orderable-wrapper-group="service"
>
<label
for="orderable_location_service_hours_pickup_same_as_delivery"
id="orderable_location_service_hours_pickup_same_as_delivery_label"
class="orderable-store-services__pickup-same-as-delivery <?php echo esc_attr( ( ! $is_delivery_enabled ) ? 'orderable-ui-hide' : '' ); ?>">
<input
type="checkbox"
id="orderable_location_service_hours_pickup_same_as_delivery"
name="orderable_location_service_hours_pickup_same_as_delivery"
value="1"
<?php checked( $pickup_same_as_delivery ); ?>
data-orderable-trigger="toggle-element"
data-orderable-target=".orderable-element--pickup"
data-orderable-toggle-class="orderable-element--disabled"
>
<?php esc_html_e( 'Same as delivery service hours', 'orderable' ); ?>
</label>
<?php
self::service_hour_table( 'pickup', (bool) $pickup_same_as_delivery );
/**
* Render content inside the pickup wrapper.
*
* @since 1.8.0
*/
do_action( 'orderable_store_services_pickup_wrapper' );
?>
</div>
<?php
}
/**
* Output service hour table.
*
* @param string $type The type of service: `delivery` or `pickup`.
* @param boolean $disabled If table sould be disabled. Default: false.
* @return void
*/
protected static function service_hour_table( $type, $disabled = false ) {
$location = new Orderable_Location_Single();
$days_of_the_week = Orderable_Timings::get_days_of_the_week( 'short' );
$target_data = wp_json_encode(
array(
'all-day' => array(
'hide' => '[data-orderable-period="time-slots"]',
),
'time-slots' => array(
'show' => '[data-orderable-period="time-slots"]',
),
)
);
$open_hours = $location->get_open_hours();
$service_hours_type_settings = $location->get_service_hours( $type, true );
if ( empty( $service_hours_type_settings ) ) {
$service_hours_type_settings = self::get_default_data()[ 'store_general_service_hours_' . $type ];
}
?>
<table
class="orderable-table orderable-table--service-hours-<?php echo esc_attr( $type ); ?> orderable-element--<?php echo esc_attr( $type ); ?> <?php echo $disabled ? 'orderable-element--disabled' : ''; ?>"
cellpadding="0"
cellspacing="0"
>
<tbody class="orderable-table__body">
<?php
foreach ( $service_hours_type_settings as $index => $settings ) :
$time_slot_id = ! empty( $settings['time_slot_id'] ) ? absint( $settings['time_slot_id'] ) : '';
$delivery_zones = $time_slot_id ? Orderable_Location_Zones::get_zones( $time_slot_id ) : array();
?>
<tr class="orderable-table__row orderable-table__row--repeatable" data-orderable-index="<?php echo esc_attr( $index ); ?>" data-orderable-time-slot="<?php echo esc_attr( $time_slot_id ); ?>">
<td class="orderable-table__cell orderable-table__cell--no-padding">
<input
type="hidden"
name="service_hours[<?php echo esc_attr( $type ); ?>][<?php echo esc_attr( $index ); ?>][time_slot_id]"
value="<?php echo esc_attr( $time_slot_id ); ?>"
/>
<table class="orderable-table orderable-table--child orderable-table--compact" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<th class="orderable-table__column orderable-table__column--medium"><?php esc_html_e( 'Days', 'orderable' ); ?></th>
<td>
<select
class="orderable-select orderable-select--multi-select orderable-select--days"
name="service_hours[<?php echo esc_attr( $type ); ?>][<?php echo esc_attr( $index ); ?>][days][]"
multiple
data-orderable-select-none-option="<?php esc_attr_e( 'Select "Open Hours" first', 'orderable' ); ?>"
>
<?php
foreach ( $days_of_the_week as $day_number => $day_label ) :
?>
<?php
$is_day_enabled = ! empty( $open_hours[ $day_number ]['enabled'] );
?>
<option
value="<?php echo esc_attr( $day_number ); ?>"
<?php selected( ! $disabled && in_array( (string) $day_number, $settings['days'], true ) && $is_day_enabled ); ?>
<?php disabled( ! $is_day_enabled ); ?>
>
<?php echo esc_attr( $day_label ); ?>
</option>
<?php endforeach; ?>
</select>
</td>
</tr>
<tr
class="<?php echo 'all-day' === $settings['period'] ? 'orderable-table__row--last' : ''; ?>"
>
<th class="orderable-table__column orderable-table__column--medium">
<?php esc_html_e( 'Period', 'orderable' ); ?>
</th>
<td>
<select
class="orderable-select"
name="service_hours[<?php echo esc_attr( $type ); ?>][<?php echo esc_attr( $index ); ?>][period]"
data-orderable-trigger="toggle-element-select"
data-orderable-parent=".orderable-table__row"
data-orderable-target="<?php echo esc_attr( $target_data ); ?>"
>
<option
value="all-day"
<?php selected( $settings['period'], 'all-day' ); ?>
>
<?php esc_html_e( 'All Day', 'orderable' ); ?>
</option>
<option
value="time-slots"
<?php selected( $settings['period'], 'time-slots' ); ?>
>
<?php esc_html_e( 'Time Slots', 'orderable' ); ?>
</option>
</select>
</td>
</tr>
<?php
echo Orderable_Helpers::kses( Orderable_Timings_Settings::get_time_slot_fields( 'service_hours[' . $type . '][' . $index . ']', $settings ), 'form' );
/**
* Render content at the end of the time slots table.
*
* @since 1.8.0
*/
do_action(
'orderable_before_time_slots_table_end',
$type,
absint( $time_slot_id ),
absint( $index ),
array(
'delivery_zones' => $delivery_zones,
)
);
?>
</table>
</td>
<td class="orderable-table__column orderable-table__column--remove">
<a href="javascript: void(0);" class="orderable-table__remove-row" data-orderable-trigger="remove-row"><span class="dashicons dashicons-trash"></span></a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php
}
/**
* Get location open hours settings sent via POST.
*
* @param string $service The service name. E.g.: delivery or pickup.
* @return array
*/
protected static function get_location_service_hours( $service ) {
$service_hours = array();
$nonce = empty( $_POST['_wpnonce_orderable_location'] ) ? false : sanitize_text_field( wp_unslash( $_POST['_wpnonce_orderable_location'] ) );
if ( ! wp_verify_nonce( $nonce, 'orderable_location_save' ) ) {
return $service_hours;
}
if ( empty( $_POST['service_hours'][ $service ] ) ) {
return $service_hours;
}
$default_settings = array(
'time_slot_id' => '',
'days' => array(),
'period' => 'all-day',
'from' => '',
'to' => '',
'frequency' => '',
'cutoff' => '',
'max_orders' => '',
'has_zones' => 0,
);
$data = map_deep(
wp_unslash( $_POST['service_hours'][ $service ] ),
'sanitize_text_field'
);
if ( ! empty( $data ) && is_array( $data ) ) {
$service_hours = array_map(
function( $item ) use ( $default_settings ) {
return wp_parse_args( $item, $default_settings );
},
$data
);
}
return $service_hours;
}
/**
* Get location services settings sent via POST.
*
* @return array
*/
public static function get_store_services() {
$store_general_services = array();
if ( 'yes' === Orderable_Location_Admin::get_posted_value( 'orderable_location_store_services_delivery' ) ) {
$store_general_services[] = 'delivery';
}
if ( 'yes' === Orderable_Location_Admin::get_posted_value( 'orderable_location_store_services_pickup' ) ) {
$store_general_services[] = 'pickup';
}
return $store_general_services;
}
/**
* Save service hours.
*
* @return void
*/
public static function save_data() {
global $post, $wpdb;
if ( empty( $wpdb->orderable_location_time_slots ) ) {
return;
}
$post_id = ! empty( $post ) ? $post->ID : false;
self::save_service_hour( 'delivery', $post_id );
self::save_service_hour( 'pickup', $post_id );
}
/**
* Save service hours settings in the Orderable custom table.
*
* @param string $service_type The service type: `delivery` or `pickup`.
* @param int|null $post_id The location ID. If null, the data is saved for the main store.
* @return void
*/
protected static function save_service_hour( $service_type, $post_id ) {
global $wpdb;
if ( empty( $service_type ) ) {
return;
}
$location = new Orderable_Location_Single();
/**
* Fires before saving service hours.
*
* @since 1.8.0
* @hook orderable_before_save_service_hours
* @param int $location_id The location ID.
* @param string $service_type The service type: `delivery` or `pickup`.
*/
do_action( 'orderable_before_save_service_hours', $location->get_location_id(), $service_type );
$service_hours = self::get_location_service_hours( $service_type );
$updated_service_hours = array();
// Remove all service hours.
if ( empty( $service_hours ) ) {
$where_format = array(
'service_type' => '%s',
);
if ( ! empty( $post ) ) {
$where_format[] = '%d';
}
$wpdb->delete(
$wpdb->orderable_location_time_slots,
array(
'service_type' => $service_type,
'location_id' => $location->get_location_id(),
),
$where_format
);
return;
}
// try to update the service_hours that have an ID.
foreach ( $service_hours as $key => $service_hour ) {
if ( empty( $service_hour['time_slot_id'] ) ) {
continue;
}
$updated = $wpdb->update(
$wpdb->orderable_location_time_slots,
array(
'days' => maybe_serialize( $service_hour['days'] ),
'period' => $service_hour['period'],
'time_from' => maybe_serialize( $service_hour['from'] ),
'time_to' => maybe_serialize( $service_hour['to'] ),
'frequency' => $service_hour['frequency'],
'cutoff' => $service_hour['cutoff'],
'max_orders' => $service_hour['max_orders'],
'has_zones' => ! empty( $service_hour['zones'] ),
),
array(
'service_type' => $service_type,
'time_slot_id' => $service_hour['time_slot_id'],
'location_id' => $location->get_location_id(),
)
);
/**
* Fires after updating a service hour.
*
* @since 1.8.0
* @hook orderable_location_service_hour_updated
* @param array $service_hour The service hour data.
* @param string $service_type The service type: `delivery` or `pickup`.
* @param int $location_id The location ID.
*/
do_action( 'orderable_location_service_hour_updated', $service_hour, $service_type, $location->get_location_id() );
// If successful (i.e. not false. 0 === nothing updated), remove the service hour from the array.
// The remaining service hours will be inserted.
if ( false !== $updated ) {
unset( $service_hours[ $key ] );
$updated_service_hours[] = $service_hour['time_slot_id'];
}
}
$conditions = array(
'service_type' => 'service_type = %s',
'location_id' => 'location_id = %d',
);
$prepare_values = array(
$service_type,
$location->get_location_id(),
);
foreach ( $updated_service_hours as $updated_service_hour ) {
$updated_service_hours_placeholders[] = '%d';
$prepare_values[] = $updated_service_hour;
}
if ( ! empty( $updated_service_hours_placeholders ) ) {
$conditions['time_slot_id'] = 'time_slot_id NOT IN ( ' . join( ', ', $updated_service_hours_placeholders ) . ' )';
}
$conditions = array_filter( $conditions );
if ( empty( $conditions ) ) {
return;
}
$conditions = implode( ' AND ', $conditions );
$delete_sql = "
DELETE
FROM
`$wpdb->orderable_location_time_slots`
WHERE
$conditions
";
/**
* Delete remaining service hours.
*
* After updating the service hours, we delete remaining service hours.
* That is, services hours that couldn't be updated (don't have an ID or
* were not found). That way, we will not leave service hours that were
* not sent in the request.
*/
$wpdb->query(
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$delete_sql,
$prepare_values
)
);
// there is no new service hours.
if ( empty( $service_hours ) ) {
return;
}
$default_settings = array(
'days' => array(),
'period' => 'all-day',
'from' => '',
'to' => '',
'frequency' => '',
'cutoff' => '',
'max_orders' => '',
'has_zones' => 0,
);
$service_hours = array_map(
function( $item ) use ( $default_settings ) {
return wp_parse_args( $item, $default_settings );
},
$service_hours
);
foreach ( $service_hours as $service_hour ) {
// If all of these are empty, then let's nopt save the service hour.
if ( empty( $service_hour['days'] ) && empty( $service_hour['time_slot_id'] ) && empty( $service_hour['zones'] ) ) {
continue;
}
$wpdb->insert(
$wpdb->orderable_location_time_slots,
array(
'location_id' => $location->get_location_id(),
'service_type' => $service_type,
'days' => maybe_serialize( $service_hour['days'] ),
'period' => $service_hour['period'],
'time_from' => maybe_serialize( $service_hour['from'] ),
'time_to' => maybe_serialize( $service_hour['to'] ),
'frequency' => $service_hour['frequency'],
'cutoff' => $service_hour['cutoff'],
'max_orders' => $service_hour['max_orders'],
'has_zones' => ! empty( $service_hour['zones'] ),
)
);
$time_slot_id = $wpdb->insert_id;
if ( ! empty( $service_hour['zones'] ) ) {
$service_hour['zones'] = array_map(
function ( $zone ) use ( $time_slot_id ) {
$zone = json_decode( $zone, true );
if ( ! $zone ) {
return $zone;
}
$zone['time_slot_id'] = $time_slot_id;
return wp_json_encode( $zone );
},
$service_hour['zones']
);
}
/**
* Fires after inserting a service hour.
*
* @since 1.8.0
* @hook orderable_location_service_hour_inserted
* @param array $service_hour The service hour data.
* @param string $service_type The service type: `delivery` or `pickup`.
* @param int $location_id The location ID.
*/
do_action( 'orderable_location_service_hour_inserted', $service_hour, $service_type, $location->get_location_id() );
}
}
}